Skip to content
This repository has been archived by the owner on Dec 31, 2022. It is now read-only.

Commit

Permalink
feat(database): add support for multiple databases per environment
Browse files Browse the repository at this point in the history
Rails 6 introduced multiple database support in `config/database.yml`.
This commit adds support of it, while being backwards compatible.

Fixes #226
  • Loading branch information
ajgon committed Nov 12, 2019
1 parent a52341a commit 0c7f89f
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 40 deletions.
9 changes: 7 additions & 2 deletions .kitchen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,12 @@ suites:
max_pool_size: 3
min_instances: 2
database:
adapter: 'null'
primary:
adapter: 'sqlite3'
database: 'db/primary.sqlite3'
secondary:
adapter: 'sqlite3'
database: 'db/secondary.sqlite3'
framework:
adapter: 'rails'
assets_precompilation_command: '/bin/true'
Expand Down Expand Up @@ -211,4 +216,4 @@ suites:
webserver:
adapter: 'apache2'
port: 8081
'ruby-version': '2.3'
'ruby-version': '2.6'
14 changes: 7 additions & 7 deletions docs/source/attributes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,13 @@ Global parameters apply to the whole application, and can be used by any section
database
~~~~~~~~

| Those parameters will be passed without any alteration to the
``database.yml``
| file. Keep in mind, that if you have RDS connected to your OpsWorks
application,
| you don’t need to use them. The chef will do all the job, and
determine them
| for you.
Those parameters will be passed without any alteration to the ``database.yml`` file. Keep in mind, that if you have
RDS connected to your OpsWorks application, you don’t need to use them. The chef will do all the job, and determine
them for you.

**Important** Rails 6 introduced multiple database support. This configuration option supports that, by adding
extra key to ``database`` which is grouping the fields, for example: ``app['database']['primary']['adapter']``.
For backward compatibility old format is also supported.

- ``app['database']['adapter']``

Expand Down
37 changes: 27 additions & 10 deletions libraries/drivers_db_base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,29 @@ def setup
handle_packages
end

# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/AbcSize:
def out
if configuration_data_source == :node_engine
return out_defaults.merge(
database: out_defaults[:database] || app['data_sources'].first.try(:[], 'database_name')
)
end
return out_defaults if multiple_databases?
return out_node_engine if configuration_data_source == :node_engine

out_defaults.merge(
adapter: adapter, username: options[:rds]['db_user'], password: options[:rds]['db_password'],
host: options[:rds]['address'], port: options[:rds]['port'],
database: app['data_sources'].first.try(:[], 'database_name')
).reject { |_k, v| v.blank? }
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/AbcSize:

def out_node_engine
out_defaults.merge(
database: out_defaults[:database] || app['data_sources'].first.try(:[], 'database_name')
)
end

def out_defaults
base = JSON.parse((node['deploy'][app['shortname']][driver_type] || {}).to_json, symbolize_names: true)
base = JSON.parse((driver_config || {}).to_json, symbolize_names: true)
return base if multiple_databases?

defaults.merge(base).merge(adapter: adapter)
end

Expand All @@ -52,15 +57,27 @@ def url(_deploy_dir)
"#{out[:adapter]}://#{out[:username]}:#{out[:password]}@#{out[:host]}#{show_port}/#{out[:database]}"
end

def multiple_databases?
(driver_config || {}).values.detect { |child| child.is_a?(Hash) }.present?
end

protected

def app_engine
options.try(:[], :rds).try(:[], 'engine')
end

def node_engine
node['deploy'][app['shortname']][driver_type].try(:[], 'adapter') ||
node['defaults'].try(:[], driver_type).try(:[], 'adapter')
adapter = if multiple_databases?
driver_config.values.detect { |child| child.is_a?(Hash) }.try(:[], 'adapter')
else
driver_config.try(:[], 'adapter')
end
adapter || node['defaults'].try(:[], driver_type).try(:[], 'adapter')
end

def driver_config
@driver_config ||= node['deploy'][app['shortname']][driver_type]
end
end
end
Expand Down
38 changes: 22 additions & 16 deletions libraries/drivers_db_sqlite.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def validate_node_engine
end

def out
return handle_output(super) if multiple_databases?

output = super
output[:database] ||= 'db/data.sqlite3'
handle_output(output)
Expand All @@ -34,25 +36,29 @@ def url(deploy_dir)

# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
def link_sqlite_database
deploy_to = deploy_dir(app)
relative_db_path = out[:database]
shared_directory_path = File.join(deploy_to, 'shared', relative_db_path.sub(%r{/[^/]+\.sqlite3?$}, ''))
release_path = Dir[File.join(deploy_to, 'releases', '*')].last
database_configs = multiple_databases? ? out.values : [out]

context.directory shared_directory_path do
recursive true
not_if { ::File.exist?(shared_directory_path) }
end
database_configs.each do |database_config|
deploy_to = deploy_dir(app)
relative_db_path = database_config[:database]
shared_directory_path = File.join(deploy_to, 'shared', relative_db_path.sub(%r{/[^/]+\.sqlite3?$}, ''))
release_path = Dir[File.join(deploy_to, 'releases', '*')].last

db_path = File.join(deploy_to, 'shared', relative_db_path)
context.file db_path do
action :create
not_if { ::File.exist?(File.join(deploy_to, 'shared', relative_db_path)) }
end
context.directory shared_directory_path do
recursive true
not_if { ::File.exist?(shared_directory_path) }
end

db_path = File.join(deploy_to, 'shared', relative_db_path)
context.file db_path do
action :create
not_if { ::File.exist?(File.join(deploy_to, 'shared', relative_db_path)) }
end

context.link File.join(release_path, relative_db_path) do
to db_path
not_if { ::File.exist?(::File.join(release_path, relative_db_path)) }
context.link File.join(release_path, relative_db_path) do
to db_path
not_if { ::File.exist?(::File.join(release_path, relative_db_path)) }
end
end
end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
Expand Down
2 changes: 1 addition & 1 deletion metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
depends 'apt', '< 7.0'
depends 'chef_client_updater'
depends 'deploy_resource'
depends 'logrotate'
depends 'logrotate', '2.2.1' # 2.2.2 breaks tests for whatever reason
depends 'nginx', '< 9.0'
depends 'nodejs'
depends 'ohai', '< 5.3'
Expand Down
22 changes: 21 additions & 1 deletion spec/unit/recipes/configure_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
end

context 'Postgresql + Git + Unicorn + Nginx + Rails + Sidekiq' do
it 'creates proper database.yml template' do
it 'creates proper database.yml template with connection options' do
db_config = Drivers::Db::Postgresql.new(chef_run, aws_opsworks_app, rds: aws_opsworks_rds_db_instance).out
expect(db_config[:adapter]).to eq 'postgresql'
expect(chef_run)
Expand All @@ -74,6 +74,26 @@
)
end

context 'custom database config' do
let(:chef_runner) do
ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '14.04') do |solo_node|
app_name = aws_opsworks_app['shortname']
solo_node.set['deploy'][app_name]['database'] = {
'primary' => { 'test' => 1 },
'secondary' => { 'test' => 2 }
}
end
end

it 'creates proper database.yml template when multi-level config is provided' do
db_config = { primary: { test: 1 }, secondary: { test: 2 } }
expect(chef_run)
.to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/config/database.yml").with_content(
JSON.parse({ development: db_config, production: db_config }.to_json).to_yaml
)
end
end

it 'creates logrotate file for rails' do
expect(chef_run)
.to enable_logrotate_app("#{aws_opsworks_app['shortname']}-rails-staging")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
require 'spec_helper'

describe 'opsworks_ruby::setup' do
describe package('ruby2.3') do
describe package('ruby2.6') do
it { should be_installed }
end

describe package('libsqlite3-dev') do
it { should_not be_installed }
it { should be_installed }
end

describe package('git') do
Expand Down Expand Up @@ -250,7 +250,9 @@

context 'framework' do
describe file('/srv/www/other_project/shared/config/database.yml') do
it { should_not exist }
its(:content) { should include 'primary:' }
its(:content) { should include 'secondary:' }
its(:content) { should include 'adapter: sqlite3' }
end

describe file('/srv/www/yet_another_project/current/config/database.yml') do
Expand Down

0 comments on commit 0c7f89f

Please sign in to comment.