Permalink
Browse files

Appserver implemented (unicorn)

  • Loading branch information...
ajgon committed Apr 11, 2016
1 parent bcab3d7 commit bbb79cc814246aa8ffe3b437d0f47c3375395ebe
View
@@ -0,0 +1,17 @@
# frozen_string_literal: true
# appserver
## common
default['appserver']['worker_processes'] = 4
## unicorn
default['appserver']['accept_filter'] = 'httpready'
default['appserver']['backlog'] = 1024
default['appserver']['delay'] = 0.5
default['appserver']['preload_app'] = true
default['appserver']['tcp_nodelay'] = true
default['appserver']['tcp_nopush'] = false
default['appserver']['tries'] = 5
default['appserver']['timeout'] = 60
View
@@ -45,6 +45,24 @@ def self.wrap(object)
end
end
class Hash
def symbolize_keys
each_with_object({}) do |(key, value), options|
options[(begin
key.to_sym
rescue
key
end) || key] = value
end
end
def stringify_keys
each_with_object({}) do |(key, value), options|
options[key.to_s] = value
end
end
end
class String
def constantize
split('::').inject(Object) { |a, e| a.const_get(e) }
@@ -0,0 +1,27 @@
# frozen_string_literal: true
module Drivers
module Appserver
class Base < Drivers::Base
include Drivers::Dsl::Notifies
include Drivers::Dsl::Output
def out
handle_output(raw_out)
end
def raw_out
node_engine.symbolize_keys
end
protected
def validate_app_engine
:node_engine
end
def node_engine
node['appserver']
end
end
end
end
@@ -0,0 +1,18 @@
# frozen_string_literal: true
module Drivers
module Appserver
class Factory
def self.build(app, node, options = {})
engine = detect_engine(app, node, options)
raise StandardError, 'There is no supported Appserver driver for given configuration.' if engine.blank?
engine.new(app, node, options)
end
def self.detect_engine(_app, node, _options)
Drivers::Appserver::Base.descendants.detect do |appserver_driver|
appserver_driver.allowed_engines.include?(node['appserver']['adapter'].presence || 'unicorn')
end
end
end
end
end
@@ -0,0 +1,63 @@
# frozen_string_literal: true
module Drivers
module Appserver
class Unicorn < Drivers::Appserver::Base
adapter :unicorn
allowed_engines :unicorn
output filter: [
:accept_filter, :backlog, :delay, :preload_app, :tcp_nodelay, :tcp_nopush, :tries, :timeout, :worker_processes
]
notifies action: :restart,
resource: proc { |app| "service[unicorn_#{app['shortname']}]" },
timer: :immediately
def configure(context)
add_unicorn_config(context)
add_unicorn_service_script(context)
add_unicorn_service_context(context)
end
private
def add_unicorn_config(context)
deploy_to = deploy_dir(app)
environment = app['environment']
output = out
context.template File.join(deploy_to, File.join('shared', 'config', 'unicorn.conf')) do
owner node['deployer']['user']
group www_group
mode '0644'
source 'unicorn.conf.erb'
variables environment: environment, deploy_dir: deploy_to, out: output
end
end
def add_unicorn_service_script(context)
deploy_to = deploy_dir(app)
app_shortname = app['shortname']
rails_env = app['attributes']['rails_env'] || 'production'
context.template File.join(deploy_to, File.join('shared', 'scripts', 'unicorn.service')) do
owner node['deployer']['user']
group www_group
mode '0755'
source 'unicorn.service.erb'
variables app_shortname: app_shortname, deploy_dir: deploy_to, rails_env: rails_env
end
end
def add_unicorn_service_context(context)
deploy_to = deploy_dir(app)
context.service "unicorn_#{app['shortname']}" do
start_command "#{deploy_to}/shared/scripts/unicorn.service start"
stop_command "#{deploy_to}/shared/scripts/unicorn.service stop"
restart_command "#{deploy_to}/shared/scripts/unicorn.service restart"
status_command "#{deploy_to}/shared/scripts/unicorn.service status"
action :nothing
end
end
end
end
end
@@ -0,0 +1,25 @@
# frozen_string_literal: true
module Drivers
module Dsl
module Notifies
def self.included(klass)
klass.instance_eval do
def notifies(options = {})
@notifies ||= []
@notifies.push(options) if options.present?
@notifies
end
end
end
def notifies
self.class.notifies.presence || (self.class.superclass.respond_to?(:notifies) && self.class.superclass.notifies)
end
def handle_notifies(out)
out = out.select { |k, _v| notifies[:filter].include?(k.to_sym) } if notifies[:filter].present?
out
end
end
end
end
View
@@ -20,16 +20,15 @@ def www_group
end
def create_deploy_dir(application, subdir = '/')
subdirs = subdir.split(File::SEPARATOR).select(&:present?)
(0..(subdirs.length - 1)).each do |i|
directory File.join(deploy_dir(application), subdirs[0..i]) do
mode '0755'
recursive true
owner node['deployer']['user'] || 'root'
group www_group
end
dir = File.join(deploy_dir(application), subdir)
directory dir do
mode '0755'
recursive true
owner node['deployer']['user'] || 'root'
group www_group
not_if { File.directory?(dir) }
end
File.join(deploy_dir(application), subdir)
dir
end
def deploy_dir(application)
View
@@ -5,10 +5,18 @@
#
every_enabled_application do |application, _deploy|
create_deploy_dir(application, File.join('shared'))
create_deploy_dir(application, File.join('shared', 'config'))
create_deploy_dir(application, File.join('shared', 'log'))
create_deploy_dir(application, File.join('shared', 'pids'))
create_deploy_dir(application, File.join('shared', 'scripts'))
create_deploy_dir(application, File.join('shared', 'sockets'))
every_enabled_rds do |rds|
database = Drivers::Db::Factory.build(application, node, rds: rds)
database.configure(self)
end
appserver = Drivers::Appserver::Factory.build(application, node)
appserver.configure(self)
end
View
@@ -4,6 +4,7 @@
every_enabled_application do |application, _deploy|
scm = Drivers::Scm::Factory.build(application, node)
scm.before_deploy(self)
appserver = Drivers::Appserver::Factory.build(application, node)
deploy application['shortname'] do
deploy_to deploy_dir(application)
@@ -13,6 +14,12 @@
scm.out.each do |scm_key, scm_value|
send(scm_key, scm_value)
end
appserver.notifies.each do |config|
notifies config[:action],
config[:resource].respond_to?(:call) ? config[:resource].call(application) : config[:resource],
config[:timer]
end
end
scm.after_deploy(self)
View
@@ -4,6 +4,8 @@
# Recipe:: setup
#
include_recipe 'deployer'
every_enabled_application do |application|
scm = Drivers::Scm::Factory.build(application, node)
scm.setup(self)
@@ -16,18 +16,75 @@
before do
stub_search(:aws_opsworks_app, '*:*').and_return([aws_opsworks_app])
stub_search(:aws_opsworks_rds_db_instance, '*:*').and_return([aws_opsworks_rds_db_instance])
stub_node { |n| n.merge(node) }
end
context 'Database' do
context 'Postgresql' 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(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
context 'context savvy' do
it 'creates shared' do
expect(chef_run).to create_directory("/srv/www/#{aws_opsworks_app['shortname']}/shared")
end
it 'creates shared/config' do
expect(chef_run).to create_directory("/srv/www/#{aws_opsworks_app['shortname']}/shared/config")
end
it 'creates shared/log' do
expect(chef_run).to create_directory("/srv/www/#{aws_opsworks_app['shortname']}/shared/log")
end
it 'creates shared/pids' do
expect(chef_run).to create_directory("/srv/www/#{aws_opsworks_app['shortname']}/shared/pids")
end
it 'creates shared/scripts' do
expect(chef_run).to create_directory("/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts")
end
it 'creates shared/sockets' do
expect(chef_run).to create_directory("/srv/www/#{aws_opsworks_app['shortname']}/shared/sockets")
end
end
context 'Postgresql + Git + Unicorn' 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(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
it 'creates proper unicorn.conf file' do
expect(chef_run)
.to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/config/unicorn.conf")
.with_content('ENV[\'ENV_VAR1\'] = "test"')
expect(chef_run)
.to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/config/unicorn.conf")
.with_content('worker_processes 4')
end
it 'creates proper unicorn.service file' do
expect(chef_run)
.to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/unicorn.service")
.with_content("APP_NAME=\"#{aws_opsworks_app['shortname']}\"")
expect(chef_run)
.to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/unicorn.service")
.with_content("ROOT_PATH=\"/srv/www/#{aws_opsworks_app['shortname']}\"")
expect(chef_run)
.to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/unicorn.service")
.with_content('unicorn_rails --env production')
end
it 'defines unicorn service' do
service = chef_run.service("unicorn_#{aws_opsworks_app['shortname']}")
expect(service).to do_nothing
expect(service.start_command)
.to eq "/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/unicorn.service start"
expect(service.stop_command)
.to eq "/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/unicorn.service stop"
expect(service.restart_command)
.to eq "/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/unicorn.service restart"
expect(service.status_command)
.to eq "/srv/www/#{aws_opsworks_app['shortname']}/shared/scripts/unicorn.service status"
end
end
end
@@ -20,7 +20,11 @@
stub_search(:aws_opsworks_rds_db_instance, '*:*').and_return([aws_opsworks_rds_db_instance])
end
context 'Postgresql + Git' do
it 'includes recipes' do
expect(chef_run).to include_recipe('opsworks_ruby::configure')
end
context 'Postgresql + Git + Unicorn' do
it 'creates git wrapper script' do
expect(chef_run).to create_template('/tmp/ssh-git-wrapper.sh')
end
@@ -31,13 +35,16 @@
end
it 'performs a deploy' do
deploy = chef_run.deploy(aws_opsworks_app['shortname'])
expect(chef_run).to deploy_deploy('dummy_project').with(
repository: 'git@git.example.com:repo/project.git',
revision: 'master',
scm_provider: Chef::Provider::Git,
enable_submodules: false,
ssh_wrapper: '/tmp/ssh-git-wrapper.sh'
)
expect(deploy).to notify("service[unicorn_#{aws_opsworks_app['shortname']}]").to(:restart).immediately
end
end
end
@@ -14,13 +14,17 @@
end.converge(described_recipe)
end
context 'Postgresql + git' do
before do
stub_search(:aws_opsworks_app, '*:*').and_return([aws_opsworks_app])
stub_search(:aws_opsworks_rds_db_instance, '*:*').and_return([aws_opsworks_rds_db_instance])
stub_node { |n| n.merge(node) }
end
before do
stub_search(:aws_opsworks_app, '*:*').and_return([aws_opsworks_app])
stub_search(:aws_opsworks_rds_db_instance, '*:*').and_return([aws_opsworks_rds_db_instance])
stub_node { |n| n.merge(node) }
end
it 'includes recipes' do
expect(chef_run).to include_recipe('deployer')
end
context 'Postgresql + git' do
it 'installs required packages' do
expect(chef_run).to install_package('git')
expect(chef_run).to install_package('libpq-dev')
Oops, something went wrong.

0 comments on commit bbb79cc

Please sign in to comment.