From f4dc1f1babd4832f70e3df12eb398f667d4e2566 Mon Sep 17 00:00:00 2001 From: Adam Strickland Date: Wed, 6 May 2020 18:04:44 -0500 Subject: [PATCH] feat: allow Pact Broker to run on Heroku * ignore .data directory * add support for connecting to a database via URL * remove old database connection fcn * add url provider mechanism --- .dockerignore | 1 + .gitignore | 1 + pact_broker/config.ru | 5 +- pact_broker/database_connection.rb | 21 +--- pact_broker/docker_configuration.rb | 59 ++++++++++- spec/docker_configuration_spec.rb | 150 ++++++++++++++++++++++++++-- 6 files changed, 209 insertions(+), 28 deletions(-) diff --git a/.dockerignore b/.dockerignore index 243a708..a890ace 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,4 @@ test.sh .gitkeep pact_broker/pact_broker.sqlite tmp +.data diff --git a/.gitignore b/.gitignore index b7eb724..f27f628 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ pact_broker/log pact_broker/tmp pact_broker/.bundle tmp +.data diff --git a/pact_broker/config.ru b/pact_broker/config.ru index a17aa3b..8b33d2d 100644 --- a/pact_broker/config.ru +++ b/pact_broker/config.ru @@ -10,7 +10,10 @@ dc.pact_broker_environment_variables.each{ |key, value| $logger.info "#{key}=#{v app = PactBroker::App.new do | config | config.logger = $logger - config.database_connection = create_database_connection(config.logger) + + db_config = dc.database_configuration + config.database_connection = create_database_connection_from_config(config.logger, db_config) + config.database_connection.timezone = :utc config.base_url = dc.base_url config.webhook_host_whitelist = dc.webhook_host_whitelist diff --git a/pact_broker/database_connection.rb b/pact_broker/database_connection.rb index fd6ad87..3fff026 100644 --- a/pact_broker/database_connection.rb +++ b/pact_broker/database_connection.rb @@ -1,26 +1,7 @@ require 'sequel' require_relative 'database_logger' -def create_database_connection(logger) - database_adapter = ENV.fetch('PACT_BROKER_DATABASE_ADAPTER','') != '' ? ENV['PACT_BROKER_DATABASE_ADAPTER'] : 'postgres' - - config = { - adapter: database_adapter, - user: ENV['PACT_BROKER_DATABASE_USERNAME'], - password: ENV['PACT_BROKER_DATABASE_PASSWORD'], - host: ENV['PACT_BROKER_DATABASE_HOST'], - database: ENV['PACT_BROKER_DATABASE_NAME'], - encoding: 'utf8' - } - - if ENV.fetch('PACT_BROKER_DATABASE_SSLMODE','') != '' - config[:sslmode] = ENV['PACT_BROKER_DATABASE_SSLMODE'] - end - - if ENV['PACT_BROKER_DATABASE_PORT'] =~ /^\d+$/ - config[:port] = ENV['PACT_BROKER_DATABASE_PORT'].to_i - end - +def create_database_connection_from_config(logger, config) ## # 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 diff --git a/pact_broker/docker_configuration.rb b/pact_broker/docker_configuration.rb index 6c84da7..03c851b 100644 --- a/pact_broker/docker_configuration.rb +++ b/pact_broker/docker_configuration.rb @@ -27,6 +27,14 @@ def webhook_http_method_whitelist space_delimited_string_list_or_default(:webhook_http_method_whitelist) end + def database_configuration + if configure_database_via_url? + database_configuration_from_url + else + database_configuration_from_parts + end.merge(encoding: 'utf8', sslmode: env(:database_sslmode)).compact + end + def base_equality_only_on_content_that_affects_verification_results if env_populated?(:base_equality_only_on_content_that_affects_verification_results) env(:base_equality_only_on_content_that_affects_verification_results) == 'true' @@ -98,5 +106,54 @@ def to_s end.join(' ') end end + + private + + def configure_database_via_url? + database_url + end + + def database_url + if env_populated?(:database_url) + env(:database_url) + elsif env_populated?(:database_url_provider) + @env[env(:database_url_provider)] + end + end + + def database_configuration_from_parts + database_adapter = if env_populated?(:database_adapter) + env(:database_adapter) + else + 'postgres' + end + + config = { + adapter: database_adapter, + user: env(:database_username), + password: env(:database_password), + host: env(:database_host), + database: env(:database_name), + } + + if env(:database_port) =~ /^\d+$/ + config[:port] = env(:database_port).to_i + end + + config + end + + def database_configuration_from_url + uri = URI(database_url) + + { + adapter: uri.scheme, + user: uri.user, + password: uri.password, + host: uri.host, + database: uri.path.sub(/^\//, ''), + port: (uri.port || 5432).to_i, + }.compact + end end -end \ No newline at end of file +end diff --git a/spec/docker_configuration_spec.rb b/spec/docker_configuration_spec.rb index 120e068..6ec7313 100644 --- a/spec/docker_configuration_spec.rb +++ b/spec/docker_configuration_spec.rb @@ -30,14 +30,152 @@ } end - let(:expected_environment_variables) do - { - "PACT_BROKER_FOO" => "foo", - "PACT_BROKER_PASSWORD" => "*****" - } + context "when the environment variables contains an arbitrary key" do + let(:expected_environment_variables) do + { + "PACT_BROKER_FOO" => "foo", + "PACT_BROKER_PASSWORD" => "*****" + } + end + + its(:pact_broker_environment_variables) { is_expected.to eq expected_environment_variables } end - its(:pact_broker_environment_variables) { is_expected.to eq expected_environment_variables } + context "when the environment variables contain database keys" do + let(:db_env) do + { + "PACT_BROKER_DATABASE_HOST" => "localhost" + } + end + let(:env) do + super().merge(db_env) + end + + let(:expected_environment_variables) do + { + "PACT_BROKER_FOO" => "foo", + "PACT_BROKER_PASSWORD" => "*****" + } + end + + its(:pact_broker_environment_variables) { is_expected.to have_key "PACT_BROKER_FOO" } + its(:pact_broker_environment_variables) { is_expected.to have_key "PACT_BROKER_PASSWORD" } + its(:pact_broker_environment_variables) { is_expected.to have_key "PACT_BROKER_DATABASE_HOST" } + its(:pact_broker_environment_variables) { is_expected.not_to have_key "SOMETHING" } + it { expect(subject.pact_broker_environment_variables["PACT_BROKER_PASSWORD"]).to eq "*****" } + end + + context "when the environment variables contain a database url key" do + let(:db_env) do + { + "PACT_BROKER_DATABASE_URL" => "postgresql://pactbrokeruser:TheUserPassword@localhost:5432/pactbroker" + } + end + let(:env) do + super().merge(db_env) + end + + let(:expected_environment_variables) do + { + "PACT_BROKER_FOO" => "foo", + "PACT_BROKER_PASSWORD" => "*****" + } + end + + its(:pact_broker_environment_variables) { is_expected.to have_key "PACT_BROKER_DATABASE_URL" } + its(:pact_broker_environment_variables) { is_expected.not_to have_key "SOMETHING" } + it { expect(subject.pact_broker_environment_variables["PACT_BROKER_PASSWORD"]).to eq "*****" } + end + end + + describe "database_configuration" do + let(:env) { super().merge(db_env) } + + context "when then configuration is provided as a URL" do + context "using the default env var" do + let(:db_env) do + { + "PACT_BROKER_DATABASE_URL" => "postgresql://pactbrokeruser:TheUserPassword@localhost:5432/pactbroker" + } + end + + its(:database_configuration) { is_expected.to be_a Hash } + its("database_configuration.keys") { are_expected.to include :adapter, :user, :password, :host, :database, :encoding, :port } + it { expect(subject.database_configuration[:user]).to eq "pactbrokeruser" } + it { expect(subject.database_configuration[:password]).to eq "TheUserPassword" } + it { expect(subject.database_configuration[:host]).to eq "localhost" } + it { expect(subject.database_configuration[:database]).to eq "pactbroker" } + it { expect(subject.database_configuration[:encoding]).to eq "utf8" } + it { expect(subject.database_configuration[:port]).to eq 5432 } + end + + context "using a configured provider and an arbitrary env var" do + let(:db_env) do + { + "PACT_BROKER_DATABASE_URL_PROVIDER" => "DATABASE_URL", + "DATABASE_URL" => "postgresql://pactbrokeruser:TheUserPassword@localhost:5432/pactbroker" + } + end + + its(:database_configuration) { is_expected.to be_a Hash } + its("database_configuration.keys") { are_expected.to include :adapter, :user, :password, :host, :database, :encoding, :port } + it { expect(subject.database_configuration[:user]).to eq "pactbrokeruser" } + it { expect(subject.database_configuration[:password]).to eq "TheUserPassword" } + it { expect(subject.database_configuration[:host]).to eq "localhost" } + it { expect(subject.database_configuration[:database]).to eq "pactbroker" } + it { expect(subject.database_configuration[:encoding]).to eq "utf8" } + it { expect(subject.database_configuration[:port]).to eq 5432 } + end + end + + context "when then configuration is provided in separate env vars" do + let(:db_env) do + { + "PACT_BROKER_DATABASE_USERNAME" => "pactbrokeruser", + "PACT_BROKER_DATABASE_PASSWORD" => "TheUserPassword", + "PACT_BROKER_DATABASE_HOST" => "localhost", + "PACT_BROKER_DATABASE_NAME" => "pactbroker", + } + end + + its(:database_configuration) { is_expected.to be_a Hash } + its("database_configuration.keys") { are_expected.to include :adapter, :user, :password, :host, :database, :encoding } + it { expect(subject.database_configuration[:user]).to eq "pactbrokeruser" } + it { expect(subject.database_configuration[:password]).to eq "TheUserPassword" } + it { expect(subject.database_configuration[:host]).to eq "localhost" } + it { expect(subject.database_configuration[:database]).to eq "pactbroker" } + it { expect(subject.database_configuration[:encoding]).to eq "utf8" } + + context "when an adapter is supplied" do + let(:db_env) { super().merge("PACT_BROKER_DATABASE_ADAPTER" => "mysql") } + + it { expect(subject.database_configuration[:adapter]).to eq "mysql" } + end + + context "when an adapter is not supplied" do + it { expect(subject.database_configuration[:adapter]).to eq "postgres" } + end + + context "when a port is supplied" do + let(:db_env) { super().merge("PACT_BROKER_DATABASE_PORT" => port) } + + context "and the value is numeric" do + let(:port) { "1234" } + + it { expect(subject.database_configuration[:port]).to eq 1234 } + end + + context "and the value is not numeric" do + let(:port) { "abc" } + + its("database_configuration.keys") { are_expected.not_to include :port } + end + end + + context "when a port is not supplied" do + its("database_configuration.keys") { are_expected.not_to include :port } + end + end end describe "order_versions_by_date" do