From 86ebc10779ffa2b88ba4eb1ab8f3b10bceb5efc5 Mon Sep 17 00:00:00 2001 From: Igor Rzegocki Date: Tue, 25 Apr 2017 13:21:27 +0200 Subject: [PATCH] feat(logrotate): implement logrotate Currently, frameworks (rails) and webservers (apache2 and nginx) are supported. Resolves #78 --- attributes/default.rb | 1 + docs/source/attributes.rst | 6 +++ libraries/drivers_dsl_logrotate.rb | 48 +++++++++++++++++++ libraries/drivers_framework_base.rb | 5 ++ libraries/drivers_framework_hanami.rb | 1 + libraries/drivers_framework_rails.rb | 4 ++ libraries/drivers_webserver_apache2.rb | 6 +++ libraries/drivers_webserver_base.rb | 5 ++ libraries/drivers_webserver_nginx.rb | 6 +++ metadata.rb | 3 +- spec/unit/recipes/configure_spec.rb | 43 ++++++++++++++--- spec/unit/recipes/deploy_spec.rb | 3 ++ .../default/serverspec/default_spec.rb | 27 +++++++++++ .../unicorn_apache_hanami_resque_spec.rb | 15 ++++++ 14 files changed, 165 insertions(+), 8 deletions(-) create mode 100644 libraries/drivers_dsl_logrotate.rb diff --git a/attributes/default.rb b/attributes/default.rb index 63135e53..76cbf02b 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -24,6 +24,7 @@ %w(tmp public config ../../shared/cache ../../shared/assets) default['defaults']['global']['purge_before_symlink'] = %w(log tmp/cache tmp/pids public/system public/assets) default['defaults']['global']['rollback_on_error'] = true +default['defaults']['global']['logrotate_rotate'] = 30 # database ## common diff --git a/docs/source/attributes.rst b/docs/source/attributes.rst index 2d62c248..d13644af 100644 --- a/docs/source/attributes.rst +++ b/docs/source/attributes.rst @@ -89,6 +89,12 @@ Global parameters apply to the whole application, and can be used by any section - **Default:** ``true`` - When set to true, any failed deploy will be removed from ``releases`` directory. +- ``app['global']['logrotate_rotate']`` + + - **Type:** integer + - **Default:** ``30`` + - **Important Notice:** The parameter is in days + database ~~~~~~~~ diff --git a/libraries/drivers_dsl_logrotate.rb b/libraries/drivers_dsl_logrotate.rb new file mode 100644 index 00000000..18d95a7d --- /dev/null +++ b/libraries/drivers_dsl_logrotate.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Drivers + module Dsl + module Logrotate + def self.included(klass) + klass.instance_eval do + def log_paths(*log_paths) + @log_paths ||= [] + @log_paths += Array.wrap(log_paths) + @log_paths + end + end + end + # rubocop:enable Metrics/MethodLength + + def log_paths + self.class.log_paths.presence || + (self.class.superclass.respond_to?(:log_paths) && self.class.superclass.log_paths) + end + + def configure_logrotate + return if (log_paths || []).empty? + lr_path = logrotate_log_paths + lr_rotate = logrotate_rotate + + context.logrotate_app "#{app['shortname']}-#{adapter}-#{deploy_env}" do + path lr_path + frequency 'daily' + rotate lr_rotate + options %w[missingok compress delaycompress notifempty copytruncate sharedscripts] + end + end + + def logrotate_log_paths + log_paths.map do |log_path| + next log_path.call(self) if log_path.is_a?(Proc) + next log_path if log_path.start_with?('/') + File.join(deploy_dir(app), log_path) + end.flatten.uniq + end + + def logrotate_rotate + globals(:logrotate_rotate, app['shortname']) + end + end + end +end diff --git a/libraries/drivers_framework_base.rb b/libraries/drivers_framework_base.rb index 5490d783..83ffa018 100644 --- a/libraries/drivers_framework_base.rb +++ b/libraries/drivers_framework_base.rb @@ -2,6 +2,7 @@ module Drivers module Framework class Base < Drivers::Base + include Drivers::Dsl::Logrotate include Drivers::Dsl::Output include Drivers::Dsl::Packages @@ -9,6 +10,10 @@ def setup handle_packages end + def configure + configure_logrotate + end + def deploy_before_migrate link_sqlite_database end diff --git a/libraries/drivers_framework_hanami.rb b/libraries/drivers_framework_hanami.rb index 3736dd92..a8a764e9 100644 --- a/libraries/drivers_framework_hanami.rb +++ b/libraries/drivers_framework_hanami.rb @@ -24,6 +24,7 @@ def raw_out def configure build_env + super end def deploy_before_restart diff --git a/libraries/drivers_framework_rails.rb b/libraries/drivers_framework_rails.rb index 3cf8d779..eccf8d70 100644 --- a/libraries/drivers_framework_rails.rb +++ b/libraries/drivers_framework_rails.rb @@ -9,6 +9,9 @@ class Rails < Drivers::Framework::Base :envs_in_console ] packages debian: 'zlib1g-dev', rhel: 'zlib-devel' + log_paths lambda { |context| + File.join(context.send(:deploy_dir, context.app), 'shared', 'log', "#{context.send(:deploy_env)}.log") + } def raw_out super.merge(deploy_environment: { 'RAILS_ENV' => deploy_env }) @@ -20,6 +23,7 @@ def configure rdses.each do |rds| database_yml(Drivers::Db::Factory.build(context, app, rds: rds)) end + super end def deploy_after_restart diff --git a/libraries/drivers_webserver_apache2.rb b/libraries/drivers_webserver_apache2.rb index e33551b2..3f6b747e 100644 --- a/libraries/drivers_webserver_apache2.rb +++ b/libraries/drivers_webserver_apache2.rb @@ -13,6 +13,11 @@ class Apache2 < Drivers::Webserver::Base action: :restart, resource: { debian: 'service[apache2]', rhel: 'service[httpd]' }, timer: :delayed notifies :undeploy, action: :restart, resource: { debian: 'service[apache2]', rhel: 'service[httpd]' }, timer: :delayed + log_paths lambda { |context| + %w[access.log error.log].map do |log_type| + File.join(context.raw_out[:log_dir], "#{context.app[:domains].first}.#{log_type}") + end + } def raw_out output = node['defaults']['webserver'].merge( @@ -40,6 +45,7 @@ def configure remove_defaults add_appserver_config enable_appserver_config + super end def before_deploy diff --git a/libraries/drivers_webserver_base.rb b/libraries/drivers_webserver_base.rb index 68626fd0..b8d341d3 100644 --- a/libraries/drivers_webserver_base.rb +++ b/libraries/drivers_webserver_base.rb @@ -2,10 +2,15 @@ module Drivers module Webserver class Base < Drivers::Base + include Drivers::Dsl::Logrotate include Drivers::Dsl::Notifies include Drivers::Dsl::Output include Drivers::Dsl::Packages + def configure + configure_logrotate + end + def out handle_output(raw_out) end diff --git a/libraries/drivers_webserver_nginx.rb b/libraries/drivers_webserver_nginx.rb index 63f30a2f..ff2f8c0b 100644 --- a/libraries/drivers_webserver_nginx.rb +++ b/libraries/drivers_webserver_nginx.rb @@ -11,6 +11,11 @@ class Nginx < Drivers::Webserver::Base ] notifies :deploy, action: :restart, resource: 'service[nginx]', timer: :delayed notifies :undeploy, action: :restart, resource: 'service[nginx]', timer: :delayed + log_paths lambda { |context| + %w[access.log error.log].map do |log_type| + File.join(context.raw_out[:log_dir], "#{context.app[:domains].first}.#{log_type}") + end + } def raw_out output = node['defaults']['webserver'].merge(node['nginx']).merge( @@ -36,6 +41,7 @@ def configure add_appserver_config enable_appserver_config + super end def before_deploy diff --git a/metadata.rb b/metadata.rb index ffc4bd9a..414c95ab 100644 --- a/metadata.rb +++ b/metadata.rb @@ -8,8 +8,9 @@ version '1.4.0' depends 'deployer' -depends 'ruby-ng' depends 'chef_nginx' +depends 'logrotate' +depends 'ruby-ng' supports 'amazon', '>= 2015.03' supports 'ubuntu', '>= 12.04' diff --git a/spec/unit/recipes/configure_spec.rb b/spec/unit/recipes/configure_spec.rb index 920c9846..29604026 100644 --- a/spec/unit/recipes/configure_spec.rb +++ b/spec/unit/recipes/configure_spec.rb @@ -64,6 +64,16 @@ ) end + it 'creates logrotate file for rails' do + expect(chef_run) + .to enable_logrotate_app("#{aws_opsworks_app['shortname']}-rails-staging") + end + + it 'creates logrotate file for nginx' do + expect(chef_run) + .to enable_logrotate_app("#{aws_opsworks_app['shortname']}-nginx-staging") + end + it 'creates proper unicorn.conf file' do expect(chef_run) .to render_file("/srv/www/#{aws_opsworks_app['shortname']}/shared/config/unicorn.conf") @@ -332,6 +342,11 @@ stub_search(:aws_opsworks_rds_db_instance, '*:*').and_return([aws_opsworks_rds_db_instance(engine: 'mysql')]) end + it 'creates logrotate file for apache2' do + expect(chef_run) + .to enable_logrotate_app("#{aws_opsworks_app['shortname']}-apache2-staging") + end + it 'creates proper .env.*' do db_config = Drivers::Db::Mysql.new(chef_run, aws_opsworks_app, rds: aws_opsworks_rds_db_instance(engine: 'mysql')).out @@ -895,13 +910,27 @@ end end - it 'empty node[\'deploy\']' do - chef_run = ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '14.04') do |solo_node| - solo_node.set['lsb'] = node['lsb'] - end.converge(described_recipe) + context 'empty node[\'deploy\']' do + let(:chef_run) do + ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '14.04') do |solo_node| + solo_node.set['lsb'] = node['lsb'] + end.converge(described_recipe) + end + + it 'not raises error' do + expect do + chef_run + end.not_to raise_error + end - expect do - chef_run - end.not_to raise_error + it 'creates logrotate file for rails' do + expect(chef_run) + .to enable_logrotate_app("#{aws_opsworks_app['shortname']}-rails-production") + end + + it 'creates logrotate file for rails' do + expect(chef_run) + .to enable_logrotate_app("#{aws_opsworks_app['shortname']}-nginx-production") + end end end diff --git a/spec/unit/recipes/deploy_spec.rb b/spec/unit/recipes/deploy_spec.rb index e1c2103a..5de7bdca 100644 --- a/spec/unit/recipes/deploy_spec.rb +++ b/spec/unit/recipes/deploy_spec.rb @@ -220,6 +220,9 @@ expect(chef_run).to create_template('/srv/www/a1/shared/scripts/puma.service') expect(chef_run).to create_template('/etc/nginx/sites-available/a1.conf') expect(chef_run).to create_link('/etc/nginx/sites-enabled/a1.conf') + expect(chef_run).to enable_logrotate_app('a1-nginx-production') + expect(chef_run).to enable_logrotate_app('a1-rails-production') + expect(service).to do_nothing expect(chef_run).to deploy_deploy('a1') expect(chef_run).not_to deploy_deploy('a2') diff --git a/test/integration/default/serverspec/default_spec.rb b/test/integration/default/serverspec/default_spec.rb index 07bcd013..52d57d72 100644 --- a/test/integration/default/serverspec/default_spec.rb +++ b/test/integration/default/serverspec/default_spec.rb @@ -29,6 +29,21 @@ describe 'opsworks_ruby::configure' do context 'webserver' do + describe file('/etc/logrotate.d/dummy_project-nginx-production') do + its(:content) do + should include '"/var/log/nginx/dummy-project.example.com.access.log" ' \ + '"/var/log/nginx/dummy-project.example.com.error.log" {' + end + its(:content) { should include ' daily' } + its(:content) { should include ' rotate 30' } + its(:content) { should include ' missingok' } + its(:content) { should include ' compress' } + its(:content) { should include ' delaycompress' } + its(:content) { should include ' notifempty' } + its(:content) { should include ' copytruncate' } + its(:content) { should include ' sharedscripts' } + end + describe file('/etc/nginx/ssl/dummy-project.example.com.key') do its(:content) { should include '-----BEGIN RSA PRIVATE KEY-----' } end @@ -68,6 +83,18 @@ end context 'framework' do + describe file('/etc/logrotate.d/dummy_project-rails-production') do + its(:content) { should include '"/srv/www/dummy_project/shared/log/production.log" {' } + its(:content) { should include ' daily' } + its(:content) { should include ' rotate 30' } + its(:content) { should include ' missingok' } + its(:content) { should include ' compress' } + its(:content) { should include ' delaycompress' } + its(:content) { should include ' notifempty' } + its(:content) { should include ' copytruncate' } + its(:content) { should include ' sharedscripts' } + end + describe file('/srv/www/dummy_project/current/config/database.yml') do its(:content) { should include 'adapter: sqlite3' } end diff --git a/test/integration/unicorn_apache_hanami_resque/serverspec/unicorn_apache_hanami_resque_spec.rb b/test/integration/unicorn_apache_hanami_resque/serverspec/unicorn_apache_hanami_resque_spec.rb index 1d1cb871..5ea7a0a5 100644 --- a/test/integration/unicorn_apache_hanami_resque/serverspec/unicorn_apache_hanami_resque_spec.rb +++ b/test/integration/unicorn_apache_hanami_resque/serverspec/unicorn_apache_hanami_resque_spec.rb @@ -29,6 +29,21 @@ describe 'opsworks_ruby::configure' do context 'webserver' do + describe file('/etc/logrotate.d/dummy_project-apache2-production') do + its(:content) do + should include '"/var/log/apache2/dummy-project.example.com.access.log" ' \ + '"/var/log/apache2/dummy-project.example.com.error.log" {' + end + its(:content) { should include ' daily' } + its(:content) { should include ' rotate 30' } + its(:content) { should include ' missingok' } + its(:content) { should include ' compress' } + its(:content) { should include ' delaycompress' } + its(:content) { should include ' notifempty' } + its(:content) { should include ' copytruncate' } + its(:content) { should include ' sharedscripts' } + end + describe file('/etc/apache2/ssl/dummy-project.example.com.key') do its(:content) { should include '-----BEGIN RSA PRIVATE KEY-----' } end