module PactBroker

TODO remove this class

Not required now we have the auto_detect_main_branch feature

Data migrations run every time the broker starts up, after the schema migrations. Their purpose is to ensure that data integrity is maintained during rolling migrations in architectures with multiple application instances running against the same database (eg. EC2 autoscaling group) where “old” data might be inserted by the application instance running the previous version of the code AFTER the schema migrations have been run on the first application instance with the new version of the code.

This class most accurately represents a PactPublication

The Wisper implementation of temporary listeners clears all listeners at the end of the block, rather the just the ones that were supplied in block. This implementation just clears the specified ones, allowing multiple temporary overlapping listeners.

A collection of matrix rows with the same pact publication id It's basically a normalised view of a denormalised view :( A pact publication may be the overall latest, and/or the latest for a tag

Represents the integration relationship between a consumer and a provider in the context of a matrix or can-i-deploy query. If the required flag is set, then one of the pacticipants (consumers) specified in the HTTP query requires the provider. It would not be required if a provider was specified, and it had an integration with a consumer.

The difference between `join_verifications_for` and `join_verifications` is that the left outer join is done on a pre-filtered dataset in `join_verifications_for`, so that we get a row with null verification fields for a pact that has been verified by a different version of the provider we're interested in, rather than being excluded from the dataset altogether.

A selector with the pacticipant id, name, version number, and version id set This is created from either specified or inferred data, based on the user's query eg. can-i-deploy –pacticipant Foo –version 1 (this is a specified selector)

--to prod (this is used to create inferred selectors)

TODO DELETE THIS!!!

A head pact is the pact for the latest consumer version with the specified tag (ignoring later versions that might have the specified tag but no pact)

All of these pacts have the same underlying pact_version_sha (content) No point verifying them multiple times, so squash all the relevant info into one “verifiable pact”

Splits all index_items up into groups of non-connecting index_items.

require 'pact_broker/tasks'

PactBroker::DB::DataMigrationTask.new do | task |

require 'my_app/db'
task.database_connection = MyApp::DB

end

require 'pact_broker/tasks'

PactBroker::DB::MigrationTask.new do | task |

require 'my_app/db'
task.database_connection = MyApp::DB

end

require 'pact_broker/tasks'

PactBroker::DB::VersionTask.new do | task |

require 'my_app/db'
task.database_connection = MyApp::DB

end

TODO handle 404 gracefully

The concept of “stale” (the pact used to be verified but then it changed and we haven't got a new verification result yet) only really make sense if we're trying to summarise the state of an integration or pseudo branch. Once we start showing multiple pacts for each integration (ie. the latest for each tag) then each pact version is either verified, or it's not verified.

Represents the relationship between a webhook and the event and object that caused it to be triggered. eg a pact publication

Constants

CONSUMER_VERSION_HEADER
DO_NOT_ROLLBACK
INTEGRATIONS_TABLES
PACT_PARSING_OPTIONS
VERSION

Public Class Methods

build_api(application_context = PactBroker::ApplicationContext.default_application_context) click to toggle source

rubocop: disable Metrics/MethodLength

# File lib/pact_broker/api.rb, line 27
def self.build_api(application_context = PactBroker::ApplicationContext.default_application_context)
  pact_api = Webmachine::Application.new do |app|
    app.routes do
      add(["trace", :*], Webmachine::Trace::TraceResource) unless ENV["RACK_ENV"] == "production"

      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "versions"], Api::Resources::PactVersions, {resource_name: "pact_publications"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "versions", :consumer_version_number], Api::Resources::Pact, {resource_name: "pact_publication", deprecated: true} # Not the standard URL, but keep for backwards compatibility
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "tag", :tag], Api::Resources::TaggedPactVersions, {resource_name: "tagged_pact_publications"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "branch", :branch_name], Api::Resources::PactVersionsForBranch, {resource_name: "pact_publications_for_branch"}

      # Pacts
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "version", :consumer_version_number], Api::Resources::Pact, {resource_name: "pact_publication"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "pact-version", :pact_version_sha], Api::Resources::PactVersion, {resource_name: "pact_publication"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "pact-version", :pact_version_sha, "metadata", :metadata], Api::Resources::PactVersion, {resource_name: "pact_publication"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "version", :consumer_version_number, "previous-distinct"], Api::Resources::PreviousDistinctPactVersion, {resource_name: "previous_distinct_pact_version"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "version", :consumer_version_number, "diff", "previous-distinct"], Api::Resources::PactContentDiff, {resource_name: "previous_distinct_pact_version_diff"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "version", :consumer_version_number, "diff", "version", :comparison_consumer_version], Api::Resources::PactContentDiff, {resource_name: "pact_version_diff_by_consumer_version"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "pact-version", :pact_version_sha, "diff", "pact-version", :comparison_pact_version_sha], Api::Resources::PactContentDiff, {resource_name: "pact_version_diff_by_pact_version_sha"}

      # Verifications
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "pact-version", :pact_version_sha, "verification-results"], Api::Resources::Verifications, {resource_name: "verification_results"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "pact-version", :pact_version_sha, "metadata", :metadata, "verification-results"], Api::Resources::Verifications, {resource_name: "verification_results"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "version", :consumer_version_number, "verification-results", "latest"], Api::Resources::LatestVerificationForPact, {resource_name: "latest_verification_results_for_pact_publication"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "pact-version", :pact_version_sha, "verification-results", "latest"], Api::Resources::LatestVerificationForPact, {resource_name: "latest_verification_results_for_pact_version"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "pact-version", :pact_version_sha, "verification-results", :verification_number], Api::Resources::Verification, {resource_name: "verification_result"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "pact-version", :pact_version_sha, "metadata", :metadata, "verification-results", :verification_number], Api::Resources::Verification, {resource_name: "verification_result"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "pact-version", :pact_version_sha, "verification-results", :verification_number, "triggered-webhooks"], Api::Resources::VerificationTriggeredWebhooks, {resource_name: "verification_result_triggered_webhooks"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "latest", "verification-results","latest"], Api::Resources::LatestVerificationForLatestPact, {resource_name: "latest_verification_results_for_latest_pact_publication"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "latest", :tag, "verification-results","latest"], Api::Resources::LatestVerificationForLatestPact, {resource_name: "latest_verification_results_for_latest_tagged_pact_publication"}
      add ["verification-results", "consumer", :consumer_name, "version", :consumer_version_number,"latest"], Api::Resources::LatestVerificationsForConsumerVersion, {resource_name: "verification_results_for_consumer_version"}

      # Badges
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "latest", "badge"], Api::Resources::Badge, {resource_name: "latest_pact_badge"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "latest", :tag, "badge"], Api::Resources::Badge, {resource_name: "latest_tagged_pact_badge"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "latest-untagged", "badge"], Api::Resources::Badge, {resource_name: "latest_untagged_pact_badge", tag: :untagged}

      # Latest pacts
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "latest"], Api::Resources::LatestPact, {resource_name: "latest_pact_publication"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "latest", :tag], Api::Resources::LatestPact, {resource_name: "latest_tagged_pact_publication"}
      add ["pacts", "provider", :provider_name], Api::Resources::ProviderPacts, {resource_name: "provider_pact_publications"}
      add ["pacts", "provider", :provider_name, "tag", :tag], Api::Resources::ProviderPacts, {resource_name: "tagged_provider_pact_publications"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "latest-untagged"], Api::Resources::LatestPact, {resource_name: "latest_untagged_pact_publication", tag: :untagged}
      add ["pacts", "provider", :provider_name, "latest"], Api::Resources::LatestProviderPacts, {resource_name: "latest_provider_pact_publications"}
      add ["pacts", "provider", :provider_name, "latest", :tag], Api::Resources::LatestProviderPacts, {resource_name: "latest_tagged_provider_pact_publications"}
      add ["pacts", "latest"], Api::Resources::LatestPacts, {resource_name: "latest_pacts"}

      # Pacts for verification
      add ["pacts", "provider", :provider_name, "for-verification"], Api::Resources::ProviderPactsForVerification, {resource_name: "pacts_for_verification"}

      # Deprecated pact
      add ["pact", "provider", :provider_name, "consumer", :consumer_name, "version", :consumer_version_number], Api::Resources::Pact, {resource_name: "pact_publication", deprecated: "true"} # Deprecate, singular /pact
      add ["pact", "provider", :provider_name, "consumer", :consumer_name, "latest"], Api::Resources::LatestPact, {resource_name: "latest_pact_publications", deprecated: "true"}

      # Pacticipants
      add ["pacticipants"], Api::Resources::Pacticipants, {resource_name: "pacticipants"}
      add ["pacticipants", "label", :label_name], PactBroker::Api::Resources::PacticipantsForLabel, {resource_name: "pacticipants_for_label"}
      add ["pacticipants", :pacticipant_name], Api::Resources::Pacticipant, {resource_name: "pacticipant"}
      add ["pacticipants", :pacticipant_name, "versions"], Api::Resources::Versions, {resource_name: "pacticipant_versions"}
      add ["pacticipants", :pacticipant_name, "versions", :pacticipant_version_number], Api::Resources::Version, {resource_name: "pacticipant_version"}
      add ["pacticipants", :pacticipant_name, "latest-version", :tag], Api::Resources::LatestVersion, {resource_name: "latest_tagged_pacticipant_version"}
      add ["pacticipants", :pacticipant_name, "latest-version", :tag, "can-i-deploy", "to", :to], Api::Resources::CanIDeployPacticipantVersion, { resource_name: "can_i_deploy_latest_tagged_version" }
      add ["pacticipants", :pacticipant_name, "latest-version", :tag, "can-i-deploy", "to", :to, "badge"], Api::Resources::CanIDeployBadge, { resource_name: "can_i_deploy_badge" }
      add ["pacticipants", :pacticipant_name, "latest-version"], Api::Resources::LatestVersion, {resource_name: "latest_pacticipant_version"}
      add ["pacticipants", :pacticipant_name, "versions", :pacticipant_version_number, "tags", :tag_name], Api::Resources::Tag, {resource_name: "pacticipant_version_tag"}
      add ["pacticipants", :pacticipant_name, "labels", :label_name], Api::Resources::Label, {resource_name: "pacticipant_label"}
      add ["pacticipants", :pacticipant_name, "branches", :branch_name, "versions", :version_number], Api::Resources::BranchVersion, { resource_name: "branch_version" }

      # Webhooks
      add ["webhooks", "provider", :provider_name, "consumer", :consumer_name ], Api::Resources::PacticipantWebhooks, {resource_name: "pacticipant_webhooks"}
      add ["webhooks", "provider", :provider_name], Api::Resources::PacticipantWebhooks, {resource_name: "provider_webhooks"}
      add ["webhooks", "consumer", :consumer_name], Api::Resources::PacticipantWebhooks, {resource_name: "consumer_webhooks"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "webhooks"], Api::Resources::PactWebhooks, {resource_name: "pact_webhooks"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "webhooks", "status"], Api::Resources::PactWebhooksStatus, {resource_name: "pact_webhooks_status"}
      add ["pacts", "provider", :provider_name, "consumer", :consumer_name, "version", :consumer_version_number, "triggered-webhooks"], Api::Resources::PactTriggeredWebhooks, {resource_name: "pact_triggered_webhooks"}

      add ["webhooks", "execute" ], Api::Resources::WebhookExecution, {resource_name: "execute_unsaved_webhook"}
      add ["webhooks", :uuid ], Api::Resources::Webhook, {resource_name: "webhook"}
      add ["triggered-webhooks", :uuid, "logs" ], Api::Resources::TriggeredWebhookLogs, { resource_name: "triggered_webhook_logs" }
      add ["webhooks", :uuid, "execute" ], Api::Resources::WebhookExecution, {resource_name: "execute_webhook"}
      add ["webhooks"], Api::Resources::AllWebhooks, {resource_name: "webhooks"}

      add ["relationships"], Api::Resources::Relationships, {resource_name: "relationships"}
      add ["groups", :pacticipant_name], Api::Resources::Group, {resource_name: "group"}

      # matrix
      add ["matrix", "provider", :provider_name, "consumer", :consumer_name], Api::Resources::MatrixForConsumerAndProvider, {resource_name: "matrix_consumer_provider"}
      add ["matrix", "provider", :provider_name, "latest", :provider_tag, "consumer", :consumer_name, "latest", :tag, "badge"], Api::Resources::MatrixBadge, {resource_name: "matrix_tag_badge"}
      add ["matrix"], Api::Resources::Matrix, {resource_name: "matrix"}
      add ["can-i-deploy"], Api::Resources::CanIDeploy, {resource_name: "can_i_deploy"}

      add ["dashboard"], Api::Resources::Dashboard, {resource_name: "dashboard"}
      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"}

      add ["contracts", "publish"], Api::Resources::PublishContracts, { resource_name: "publish_contracts" }

      add ["environments"], Api::Resources::Environments, { resource_name: "environments" }
      add ["environments", :environment_uuid], Api::Resources::Environment, { resource_name: "environment" }
      add ["environments", :environment_uuid, "deployed-versions", "currently-deployed"], Api::Resources::CurrentlyDeployedVersionsForEnvironment, { resource_name: "environment_currently_deployed_deployed_versions" }
      add ["environments", :environment_uuid, "released-versions", "currently-supported"], Api::Resources::CurrentlySupportedVersionsForEnvironment, { resource_name: "environment_currently_supported_released_versions" }
      add ["pacticipants", :pacticipant_name, "versions", :pacticipant_version_number, "deployed-versions", "environment", :environment_uuid], Api::Resources::DeployedVersionsForVersionAndEnvironment, { resource_name: "deployed_versions_for_version_and_environment" }
      add ["pacticipants", :pacticipant_name, "versions", :pacticipant_version_number, "released-versions", "environment", :environment_uuid], Api::Resources::ReleasedVersionsForVersionAndEnvironment, { resource_name: "released_versions_for_version_and_environment" }
      add ["released-versions", :uuid], Api::Resources::ReleasedVersion, { resource_name: "released_version" }
      add ["deployed-versions", :uuid], Api::Resources::DeployedVersion, { resource_name: "deployed_version" }

      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"}
      add [], Api::Resources::Index, {resource_name: "index"}
    end
  end

  # naughty, but better than setting each route manually
  pact_api.routes.each do | route |
    route.instance_variable_get(:@bindings)[:application_context] = application_context
  end

  pact_api.configure do |config|
    config.adapter = :RackMapped
  end

  pact_api.adapter
end
configuration() click to toggle source
# File lib/pact_broker/configuration.rb, line 10
def self.configuration
  RequestStore.store[:pact_broker_configuration] ||= Configuration.default_configuration
end
create_database_connection(config, logger = nil) click to toggle source
# File lib/pact_broker/initializers/database_connection.rb, line 6
def self.create_database_connection(config, logger = nil)
  logger&.info "Connecting to database:", payload: "#{config.merge(password: "*****")}"

  sequel_config = config.dup
  max_retries = sequel_config.delete(:connect_max_retries) || 0
  connection_validation_timeout = config.delete(:connection_validation_timeout)
  configure_logger(sequel_config)
  create_sqlite_database_dir(config)

  connection = with_retries(max_retries, logger) do
    Sequel.connect(sequel_config)
  end

  logger&.info "Connected to database #{sequel_config[:database]}"

  configure_connection(connection, connection_validation_timeout)
end
feature_enabled?(feature, ignore_env = false) click to toggle source
# File lib/pact_broker/feature_toggle.rb, line 22
def self.feature_enabled?(feature, ignore_env = false)
  FeatureToggle.enabled?(feature, ignore_env)
end
policy!(*args) click to toggle source
# File lib/pact_broker/policies.rb, line 46
def self.policy!(*args)
  PactBroker.configuration.policy_builder.call(*args)
end
policy_scope!(*args) click to toggle source
# File lib/pact_broker/policies.rb, line 50
def self.policy_scope!(*args)
  PactBroker.configuration.policy_scope_builder.call(*args)
end
project_root() click to toggle source
# File lib/pact_broker/project_root.rb, line 4
def self.project_root
  @project_root ||= Pathname.new(File.expand_path("../../../",__FILE__)).freeze
end
reset_configuration() click to toggle source

@private, for testing only

# File lib/pact_broker/configuration.rb, line 23
def self.reset_configuration
  RequestStore.store[:pact_broker_configuration] = Configuration.default_configuration
end
routes() click to toggle source
# File lib/pact_broker/api.rb, line 156
def self.routes
  require "webmachine/describe_routes"
  @routes ||= Webmachine::DescribeRoutes.call([API.application])
end
set_configuration(configuration) click to toggle source
# File lib/pact_broker/configuration.rb, line 14
def self.set_configuration(configuration)
  RequestStore.store[:pact_broker_configuration] = configuration
end
with_runtime_configuration_overrides(overrides, &block) click to toggle source
# File lib/pact_broker/configuration.rb, line 18
def self.with_runtime_configuration_overrides(overrides, &block)
  self.configuration.with_runtime_configuration_overrides(overrides, &block)
end

Private Class Methods

configure_connection(connection, connection_validation_timeout) click to toggle source

Sequel by default does not test connections in its connection pool before handing them to a client. To enable connection testing you need to load the “connection_validator” extension like below. The connection validator extension is configurable, by default it only checks connections once per hour:

sequel.rubyforge.org/rdoc-plugins/files/lib/sequel/extensions/connection_validator_rb.html

A gotcha here is that it is not enough to enable the “connection_validator” extension, we also need to specify that we want to use the threaded connection pool, as noted in the documentation for the extension.

-1 means that connections will be validated every time, which avoids errors when databases are restarted and connections are killed. This has a performance penalty, so consider increasing this timeout if building a frequently accessed service.

# File lib/pact_broker/initializers/database_connection.rb, line 75
                     def self.configure_connection(connection, connection_validation_timeout)
  connection.extension(:connection_validator)
  connection.pool.connection_validation_timeout = connection_validation_timeout if connection_validation_timeout
  connection
end
configure_logger(sequel_config) click to toggle source
# File lib/pact_broker/initializers/database_connection.rb, line 49
                     def self.configure_logger(sequel_config)
  if sequel_config[:sql_log_level] == :none
    sequel_config.delete(:sql_log_level)
  elsif logger
    sequel_config[:logger] = PactBroker::DB::LogQuietener.new(logger)
  end
end
create_sqlite_database_dir(config) click to toggle source
# File lib/pact_broker/initializers/database_connection.rb, line 42
                     def self.create_sqlite_database_dir(config)
  if config[:adapter] == "sqlite" && config[:database] && !File.exist?(File.dirname(config[:database]))
    logger&.info "Creating directory #{File.expand_path(File.dirname(config[:database]))} for Sqlite database"
    FileUtils.mkdir_p(File.dirname(config[:database]))
  end
end
with_retries(max_retries, logger) { || ... } click to toggle source
# File lib/pact_broker/initializers/database_connection.rb, line 24
                     def self.with_retries(max_retries, logger)
  tries = 0
  max_tries = max_retries + 1
  wait = 3

  begin
    yield
  rescue StandardError => e
    if (tries += 1) < max_tries
      logger&.info "Error connecting to database (#{e.class}). Waiting #{wait} seconds and trying again. #{max_tries-tries} tries to go."
      sleep wait
      retry
    else
      raise e
    end
  end
end