From 72d4b9f2151dfbb9b5f32d7d62a6ba730fc44ebb Mon Sep 17 00:00:00 2001 From: Igor Rzegocki Date: Sat, 23 Apr 2016 18:06:35 +0200 Subject: [PATCH] Added MySQL Driver support --- README.md | 68 +++++---- libraries/drivers_db_mysql.rb | 10 ++ .../examples/db_parameters_and_connection.rb | 50 +++++++ .../db_validate_adapter_and_engine.rb | 93 +++++++++++++ spec/unit/libraries/drivers_db_mysql_spec.rb | 9 ++ .../libraries/drivers_db_postgresql_spec.rb | 131 +----------------- spec/unit/recipes/configure_spec.rb | 16 +++ spec/unit/recipes/setup_spec.rb | 10 ++ 8 files changed, 234 insertions(+), 153 deletions(-) create mode 100644 libraries/drivers_db_mysql.rb create mode 100644 spec/unit/examples/db_parameters_and_connection.rb create mode 100644 spec/unit/examples/db_validate_adapter_and_engine.rb create mode 100644 spec/unit/libraries/drivers_db_mysql_spec.rb diff --git a/README.md b/README.md index 401063c8..271b7896 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # opsworks_ruby Cookbook +[![Chef cookbook](https://img.shields.io/cookbook/v/opsworks_ruby.svg)](https://supermarket.chef.io/cookbooks/opsworks_ruby) [![Build Status](https://travis-ci.org/ajgon/opsworks_ruby.svg?branch=master)](https://travis-ci.org/ajgon/opsworks_ruby) [![Coverage Status](https://coveralls.io/repos/github/ajgon/opsworks_ruby/badge.svg?branch=master)](https://coveralls.io/github/ajgon/opsworks_ruby?branch=master) +[![license](https://img.shields.io/github/license/ajgon/opsworks_ruby.svg?maxAge=2592000)](https://opsworks-ruby.mit-license.org/) A [chef](https://www.chef.io/) cookbook to deploy Ruby applications to Amazon OpsWorks. @@ -11,8 +13,22 @@ This cookbook is design to "just work". So in base case scenario, all you have to do is create a layer and application with assigned RDS data source, then [add recipes to the corresponding OpsWorks actions](#recipes). -**Currently only PostgreSQL database, GIT SCM, Rails framework, Unicorn -appserver and nginx webserver are supported.** New drivers will be added soon. +## Support + +* Database + * MySQL + * PostgreSQL +* SCM + * git +* Framework + * Ruby on Rails +* App server + * Unicorn +* Web server + * nginx +* Worker + * Null (no worker) + * sidekiq ## Requirements @@ -51,10 +67,9 @@ you don't need to use them. The chef will do all the job, and determine them for you. * `app['database']['adapter']` - * **Supported values:** `postgresql` + * **Supported values:** `postgresql`, `mysql` * **Default:** `postgresql` - * ActiveRecord adapter which will be used for database connection. Currently - only PostgreSQL is supported. + * ActiveRecord adapter which will be used for database connection. * `app['database']['username']` * Username used to authenticate to the DB * `app['database']['password']` @@ -147,27 +162,6 @@ Configuration parameters for the ruby application server. Currently only * [`app['appserver']['worker_processes']`](https://unicorn.bogomips.org/TUNING.html) * **Default:** `4` -### worker - -Configuration for ruby workers. Currenty `Null` (no worker) and `Sidekiq` -are supported. Every worker is covered by `monitd` daemon out-of-the-box. - -* `app['worker']['adapter']` - * **Default:** `null` - * **Supported values:** `null`, `sidekiq` - * Worker used to perform background tasks. `null` means no worker enabled. -* `app['worker']['process_count']` - * ** Default:** `2` - * How many separate worker processes will be launched. -* `app['worker']['syslog']` - * **Default:** `true` - * **Supported values:** `true`, `false` - * Log worker output to syslog? -* `app['worker']['config']` - * Configuration parameters which will be directly passed to the worker. - For example, for `sidekiq` they will be serialized to - [`sidekiq.yml` config file](https://github.com/mperham/sidekiq/wiki/Advanced-Options#the-sidekiq-configuration-file). - ### webserver Webserver configuration. Proxy passing to application is handled out-of-the-box. @@ -215,6 +209,27 @@ Currently only nginx is supported. well. If your application needs a support for those browsers, set this parameter to `true`. +### worker + +Configuration for ruby workers. Currenty `Null` (no worker) and `Sidekiq` +are supported. Every worker is covered by `monitd` daemon out-of-the-box. + +* `app['worker']['adapter']` + * **Default:** `null` + * **Supported values:** `null`, `sidekiq` + * Worker used to perform background tasks. `null` means no worker enabled. +* `app['worker']['process_count']` + * ** Default:** `2` + * How many separate worker processes will be launched. +* `app['worker']['syslog']` + * **Default:** `true` + * **Supported values:** `true`, `false` + * Log worker output to syslog? +* `app['worker']['config']` + * Configuration parameters which will be directly passed to the worker. + For example, for `sidekiq` they will be serialized to + [`sidekiq.yml` config file](https://github.com/mperham/sidekiq/wiki/Advanced-Options#the-sidekiq-configuration-file). + Since this driver is basically a wrapper for [nginx cookbook](https://github.com/miketheman/nginx/tree/2.7.x), you can also configure [`node['nginx']` attributes](https://github.com/miketheman/nginx/tree/2.7.x#attributes) as well (notice that `node['deploy'][]` logic doesn't @@ -239,4 +254,5 @@ for details. ## Author and License Author: Igor Rzegocki <[igor@rzegocki.pl](mailto:igor@rzegocki.pl)> + License: [MIT](http://opsworks-ruby.mit-license.org/) diff --git a/libraries/drivers_db_mysql.rb b/libraries/drivers_db_mysql.rb new file mode 100644 index 00000000..f3021433 --- /dev/null +++ b/libraries/drivers_db_mysql.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +module Drivers + module Db + class Mysql < Base + adapter :mysql2 + allowed_engines :mysql, :mysql2 + packages debian: 'libmysqlclient-dev', rhel: 'mysql-devel' + end + end +end diff --git a/spec/unit/examples/db_parameters_and_connection.rb b/spec/unit/examples/db_parameters_and_connection.rb new file mode 100644 index 00000000..ae5d7d45 --- /dev/null +++ b/spec/unit/examples/db_parameters_and_connection.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true +RSpec.shared_examples 'db parameters and connection' do |rdbms, options = {}| + it 'receives and exposes app, node and database bag' do + driver = described_class.new(aws_opsworks_app, node, rds: aws_opsworks_rds_db_instance(engine: rdbms)) + + expect(driver.app).to eq aws_opsworks_app + expect(driver.node).to eq node + expect(driver.options[:rds]).to eq aws_opsworks_rds_db_instance(engine: rdbms) + end + + it 'raises error when no rds is present' do + expect do + described_class.new(aws_opsworks_app, node, dummy_option: true).out + end.to raise_error ArgumentError, ':rds option is not set.' + end + + context 'connection data' do + it 'taken from engine' do + item = described_class.new( + aws_opsworks_app, + node(deploy: { dummy_project: { database: { adapter: rdbms } } }), + rds: aws_opsworks_rds_db_instance(engine: rdbms) + ) + expect(item.out).to eq( + encoding: 'utf8', + reconnect: true, + adapter: options[:adapter] || rdbms, + username: 'dbuser', + password: '03c1bc98cdd5eb2f9c75', + host: 'dummy-project.c298jfowejf.us-west-2.rds.amazon.com', + database: 'dummydb' + ) + end + + it 'taken from adapter' do + item = described_class.new( + aws_opsworks_app, + node(deploy: { dummy_project: { database: { adapter: rdbms } } }), + rds: aws_opsworks_rds_db_instance(engine: nil) + ) + expect(item.out).to eq( + encoding: 'utf8', + reconnect: true, + adapter: options[:adapter] || rdbms, + host: 'localhost', + database: 'dummydb' + ) + end + end +end diff --git a/spec/unit/examples/db_validate_adapter_and_engine.rb b/spec/unit/examples/db_validate_adapter_and_engine.rb new file mode 100644 index 00000000..bc17c3de --- /dev/null +++ b/spec/unit/examples/db_validate_adapter_and_engine.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true +RSpec.shared_examples 'db validate adapter and engine' do |rdbms| + context "#{rdbms}: validate adapter and engine" do + it 'adapter = missing, engine = missing' do + expect do + described_class.new( + aws_opsworks_app, node(deploy: { dummy_project: {} }), rds: aws_opsworks_rds_db_instance(engine: nil) + ).out + end.to raise_error ArgumentError, + "Missing :app or :node engine, expected #{described_class.allowed_engines.inspect}." + end + + it 'adapter = missing, engine = wrong' do + expect do + described_class.new( + aws_opsworks_app, node(deploy: { dummy_project: {} }), rds: aws_opsworks_rds_db_instance(engine: 'wrong') + ).out + end.to raise_error ArgumentError, + "Incorrect :app engine, expected #{described_class.allowed_engines.inspect}, got 'wrong'." + end + + it 'adapter = missing, engine = correct' do + expect do + described_class.new( + aws_opsworks_app, node(deploy: { dummy_project: {} }), rds: aws_opsworks_rds_db_instance(engine: rdbms) + ).out + end.not_to raise_error + end + + it 'adapter = wrong, engine = missing' do + expect do + described_class.new( + aws_opsworks_app, + node(deploy: { dummy_project: { database: { adapter: 'wrong' } } }), + rds: aws_opsworks_rds_db_instance(engine: nil) + ).out + end.to raise_error ArgumentError, + "Incorrect :node engine, expected #{described_class.allowed_engines.inspect}, got 'wrong'." + end + + it 'adapter = wrong, engine = wrong' do + expect do + described_class.new( + aws_opsworks_app, + node(deploy: { dummy_project: { database: { adapter: 'wrong' } } }), + rds: aws_opsworks_rds_db_instance(engine: 'wrong') + ).out + end.to raise_error ArgumentError, + "Incorrect :app engine, expected #{described_class.allowed_engines.inspect}, got 'wrong'." + end + + it 'adapter = wrong, engine = correct' do + expect do + described_class.new( + aws_opsworks_app, + node(deploy: { dummy_project: { database: { adapter: 'wrong' } } }), + rds: aws_opsworks_rds_db_instance(engine: rdbms) + ).out + end.not_to raise_error + end + + it 'adapter = correct, engine = missing' do + expect do + described_class.new( + aws_opsworks_app, + node(deploy: { dummy_project: { database: { adapter: rdbms } } }), + rds: aws_opsworks_rds_db_instance(engine: nil) + ).out + end.not_to raise_error + end + + it 'adapter = correct, engine = wrong' do + expect do + described_class.new( + aws_opsworks_app, + node(deploy: { dummy_project: { database: { adapter: rdbms } } }), + rds: aws_opsworks_rds_db_instance(engine: 'wrong') + ).out + end.to raise_error ArgumentError, + "Incorrect :app engine, expected #{described_class.allowed_engines.inspect}, got 'wrong'." + end + + it 'adapter = correct, engine = correct' do + expect do + described_class.new( + aws_opsworks_app, + node(deploy: { dummy_project: { database: { adapter: rdbms } } }), + rds: aws_opsworks_rds_db_instance(engine: rdbms) + ).out + end.not_to raise_error + end + end +end diff --git a/spec/unit/libraries/drivers_db_mysql_spec.rb b/spec/unit/libraries/drivers_db_mysql_spec.rb new file mode 100644 index 00000000..aabe4ac2 --- /dev/null +++ b/spec/unit/libraries/drivers_db_mysql_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true +require 'spec_helper' +require 'unit/examples/db_validate_adapter_and_engine' +require 'unit/examples/db_parameters_and_connection' + +describe Drivers::Db::Mysql do + include_examples 'db validate adapter and engine', 'mysql' + include_examples 'db parameters and connection', 'mysql', adapter: 'mysql2' +end diff --git a/spec/unit/libraries/drivers_db_postgresql_spec.rb b/spec/unit/libraries/drivers_db_postgresql_spec.rb index f1d04471..2bc75520 100644 --- a/spec/unit/libraries/drivers_db_postgresql_spec.rb +++ b/spec/unit/libraries/drivers_db_postgresql_spec.rb @@ -1,132 +1,9 @@ # frozen_string_literal: true require 'spec_helper' +require 'unit/examples/db_validate_adapter_and_engine' +require 'unit/examples/db_parameters_and_connection' describe Drivers::Db::Postgresql do - it 'receives and exposes app, node and database bag' do - driver = described_class.new(aws_opsworks_app, node, rds: aws_opsworks_rds_db_instance) - - expect(driver.app).to eq aws_opsworks_app - expect(driver.node).to eq node - expect(driver.options[:rds]).to eq aws_opsworks_rds_db_instance - end - - it 'raises error when no rds is present' do - expect do - described_class.new(aws_opsworks_app, node, dummy_option: true).out - end.to raise_error ArgumentError, ':rds option is not set.' - end - - context 'validate adapter and engine' do - it 'adapter = missing, engine = missing' do - expect do - described_class.new( - aws_opsworks_app, node(deploy: { dummy_project: {} }), rds: aws_opsworks_rds_db_instance(engine: nil) - ).out - end.to raise_error ArgumentError, - "Missing :app or :node engine, expected #{described_class.allowed_engines.inspect}." - end - - it 'adapter = missing, engine = wrong' do - expect do - described_class.new( - aws_opsworks_app, node(deploy: { dummy_project: {} }), rds: aws_opsworks_rds_db_instance(engine: 'mysql') - ).out - end.to raise_error ArgumentError, - "Incorrect :app engine, expected #{described_class.allowed_engines.inspect}, got 'mysql'." - end - - it 'adapter = missing, engine = correct' do - expect do - described_class.new( - aws_opsworks_app, node(deploy: { dummy_project: {} }), rds: aws_opsworks_rds_db_instance - ).out - end.not_to raise_error - end - - it 'adapter = wrong, engine = missing' do - expect do - described_class.new( - aws_opsworks_app, - node(deploy: { dummy_project: { database: { adapter: 'mysql' } } }), - rds: aws_opsworks_rds_db_instance(engine: nil) - ).out - end.to raise_error ArgumentError, - "Incorrect :node engine, expected #{described_class.allowed_engines.inspect}, got 'mysql'." - end - - it 'adapter = wrong, engine = wrong' do - expect do - described_class.new( - aws_opsworks_app, - node(deploy: { dummy_project: { database: { adapter: 'mysql' } } }), - rds: aws_opsworks_rds_db_instance(engine: 'mysql') - ).out - end.to raise_error ArgumentError, - "Incorrect :app engine, expected #{described_class.allowed_engines.inspect}, got 'mysql'." - end - - it 'adapter = wrong, engine = correct' do - expect do - described_class.new( - aws_opsworks_app, - node(deploy: { dummy_project: { database: { adapter: 'mysql' } } }), - rds: aws_opsworks_rds_db_instance - ).out - end.not_to raise_error - end - - it 'adapter = correct, engine = missing' do - expect do - described_class.new( - aws_opsworks_app, - node(deploy: { dummy_project: { database: { adapter: 'postgresql' } } }), - rds: aws_opsworks_rds_db_instance(engine: nil) - ).out - end.not_to raise_error - end - - it 'adapter = correct, engine = wrong' do - expect do - described_class.new( - aws_opsworks_app, - node(deploy: { dummy_project: { database: { adapter: 'postgresql' } } }), - rds: aws_opsworks_rds_db_instance(engine: 'mysql') - ).out - end.to raise_error ArgumentError, - "Incorrect :app engine, expected #{described_class.allowed_engines.inspect}, got 'mysql'." - end - - it 'adapter = correct, engine = correct' do - expect do - described_class.new( - aws_opsworks_app, - node(deploy: { dummy_project: { database: { adapter: 'postgresql' } } }), - rds: aws_opsworks_rds_db_instance - ).out - end.not_to raise_error - end - end - - context 'connection data' do - after(:each) do - expect(@item.out).to eq( - encoding: 'utf8', - reconnect: true, - adapter: 'postgresql', - username: 'dbuser', - password: '03c1bc98cdd5eb2f9c75', - host: 'dummy-project.c298jfowejf.us-west-2.rds.amazon.com', - database: 'dummydb', - reaping_frequency: 10 - ) - end - - it 'taken from engine' do - @item = described_class.new(aws_opsworks_app, node, rds: aws_opsworks_rds_db_instance) - end - - it 'taken from adapter' do - @item = described_class.new(aws_opsworks_app, node, rds: aws_opsworks_rds_db_instance(engine: nil)) - end - end + include_examples 'db validate adapter and engine', 'postgresql' + include_examples 'db parameters and connection', 'postgresql' end diff --git a/spec/unit/recipes/configure_spec.rb b/spec/unit/recipes/configure_spec.rb index 258205ff..cc89d708 100644 --- a/spec/unit/recipes/configure_spec.rb +++ b/spec/unit/recipes/configure_spec.rb @@ -48,6 +48,7 @@ context 'Postgresql + Git + Unicorn + Nginx + Sidekiq' do it 'creates proper database.yml template' do db_config = Drivers::Db::Postgresql.new(aws_opsworks_app, node, rds: aws_opsworks_rds_db_instance).out + expect(db_config[:adapter]).to eq 'postgresql' 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 @@ -217,6 +218,21 @@ .with_content('group sidekiq_dummy_project_group') end + context 'Mysql' do + before do + stub_search(:aws_opsworks_rds_db_instance, '*:*').and_return([aws_opsworks_rds_db_instance(engine: 'mysql')]) + end + + it 'creates proper database.yml template' do + db_config = Drivers::Db::Mysql.new(aws_opsworks_app, node, rds: aws_opsworks_rds_db_instance(engine: 'mysql')).out + expect(db_config[:adapter]).to eq 'mysql2' + 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 'empty node[\'deploy\']' do chef_run = ChefSpec::SoloRunner.new do |solo_node| solo_node.set['lsb'] = node['lsb'] diff --git a/spec/unit/recipes/setup_spec.rb b/spec/unit/recipes/setup_spec.rb index 036253d4..67e2de81 100644 --- a/spec/unit/recipes/setup_spec.rb +++ b/spec/unit/recipes/setup_spec.rb @@ -85,6 +85,16 @@ end end + context 'Mysql' do + before do + stub_search(:aws_opsworks_rds_db_instance, '*:*').and_return([aws_opsworks_rds_db_instance(engine: 'mysql')]) + end + + it 'installs required packages' do + expect(chef_run).to install_package('libmysqlclient-dev') + end + end + it 'empty node[\'deploy\']' do chef_run = ChefSpec::SoloRunner.new do |solo_node| solo_node.set['lsb'] = node['lsb']