From 12dee111b4cda4e0f21117d08886648c4e51e180 Mon Sep 17 00:00:00 2001
From: spaquet <176050+spaquet@users.noreply.github.com>
Date: Sat, 22 Jul 2023 12:58:55 -0700
Subject: [PATCH] Crontasks (#95)
* Adding support Faraday-retry
* Annontate is not loaded by default
* Upgrading to report close_events task status
* Improved db_cleanup task
* Improved clean_active_sessions task
* Adding Whenever gem
* Setting required:false
* Initial schedule file used by whenever
* First scheduled jobs
* Code cleaning and misc fixes to better deploy
* Testing a new Capistrano config
* Making sure whenever write jobs that have proper access to the bundler
* Experimenting with Capistrano and Sidekiq
* Better Poll list display on small screens
* Fixing a typo
---
Capfile | 13 ++++----
Gemfile | 21 ++++++++----
Gemfile.lock | 12 +++++++
Puma-Service.md | 6 ++--
app/views/polls/_poll.html.erb | 4 +--
app/views/polls/index.html.erb | 6 ++--
config/deploy.rb | 24 ++++++--------
config/deploy/production.rb | 2 +-
config/schedule.rb | 35 ++++++++++++++++++++
lib/capistrano/tasks/sidekiq.rake | 20 ++++++++++++
lib/tasks/clean_active_sessions.rake | 27 +++++++++++++++-
lib/tasks/close_events.rake | 48 ++++++++++++++++++++++++----
lib/tasks/db_cleanup.rake | 25 ++++++++++++++-
13 files changed, 199 insertions(+), 44 deletions(-)
create mode 100644 config/schedule.rb
create mode 100644 lib/capistrano/tasks/sidekiq.rake
diff --git a/Capfile b/Capfile
index c0d12eb4..5d9077b6 100644
--- a/Capfile
+++ b/Capfile
@@ -19,19 +19,20 @@ install_plugin Capistrano::SCM::Git
# https://github.com/capistrano/rails
# https://github.com/capistrano/passenger
#
-# require "capistrano/rvm"
require "capistrano/rails"
require "capistrano/rbenv"
-# require "capistrano/chruby"
require "capistrano/bundler"
require "capistrano/rails/assets"
require "capistrano/rails/migrations"
-# require "capistrano/passenger"
+require "whenever/capistrano"
-require "capistrano/sidekiq"
-install_plugin Capistrano::Sidekiq
-install_plugin Capistrano::Sidekiq::Systemd
+# require "capistrano/sidekiq"
+# install_plugin Capistrano::Sidekiq
+# install_plugin Capistrano::Sidekiq::Systemd
+require 'capistrano/puma'
+install_plugin Capistrano::Puma # Default puma tasks
+# install_plugin Capistrano::Puma::Systemd
set :rbenv_type, :user
set :rbenv_ruby, "3.2.2"
diff --git a/Gemfile b/Gemfile
index 611bb025..ccb59864 100644
--- a/Gemfile
+++ b/Gemfile
@@ -153,6 +153,12 @@ gem 'pg_search', '~> 2.3.6'
# Stripe (payment, subscription processing) [https://github.com/stripe/stripe-ruby]
gem 'stripe', '~> 8.6.0'
+# To enable retry in Faraday v2.0+
+gem 'faraday-retry', '~> 2.2.0'
+
+# Whenever gem to mamage crontab & tasks [https://github.com/javan/whenever]
+gem 'whenever', '~> 1.0.0', require: false
+
group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem 'debug', platforms: %i[mri mingw x64_mingw]
@@ -180,8 +186,8 @@ group :development do
# Vulnerability scanner
gem 'brakeman', '~> 6.0.1'
- # Add Model annotations
- gem 'annotate', '~>3.2.0'
+ # Add Model annotations [https://github.com/ctran/annotate_models]
+ gem 'annotate', '~>3.2.0', require: false
# Add Bullet to monitor and help fix N+1 DB queries
gem 'bullet'
@@ -191,11 +197,12 @@ group :development do
# facilitate the deployment to Digital #
# Ocean.
# [https://gorails.com/deploy/ubuntu/22.04]
- gem 'capistrano', '~> 3.17', require: false
- gem 'capistrano-rails', '~> 1.6.3', require: false
- gem 'capistrano-rbenv', '~> 2.2.0', require: false
- gem 'capistrano-bundler', require: false
- gem 'capistrano-sidekiq', '~> 3.0.0.alpha.2', require: false
+ gem 'capistrano', '~> 3.17', require: false
+ gem 'capistrano-rails', '~> 1.6.3', require: false
+ gem 'capistrano-rbenv', '~> 2.2.0', require: false
+ gem 'capistrano-bundler', require: false
+ gem 'capistrano-sidekiq', '~> 3.0.0.alpha.2', require: false
+ gem 'capistrano3-puma', '~> 6.0.0.beta.1', require: false
end
group :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index 1efaf575..0dca93a2 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -140,6 +140,10 @@ GEM
capistrano (>= 3.9.0)
capistrano-bundler
sidekiq (>= 6.0.6)
+ capistrano3-puma (6.0.0.beta.1)
+ capistrano (~> 3.7)
+ capistrano-bundler
+ puma (>= 5.1, < 7.0)
capybara (3.39.2)
addressable
matrix
@@ -158,6 +162,7 @@ GEM
actionpack (>= 3.1)
caxlsx (>= 3.0)
chartkick (5.0.2)
+ chronic (0.10.2)
concurrent-ruby (1.2.2)
connection_pool (2.4.1)
countries (5.5.0)
@@ -182,6 +187,8 @@ GEM
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (3.0.2)
+ faraday-retry (2.2.0)
+ faraday (~> 2.0)
ffi (1.15.5)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
@@ -457,6 +464,8 @@ GEM
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
+ whenever (1.0.0)
+ chronic (>= 0.6.3)
xpath (3.2.0)
nokogiri (~> 1.8)
xsv (1.2.1)
@@ -482,6 +491,7 @@ DEPENDENCIES
capistrano-rails (~> 1.6.3)
capistrano-rbenv (~> 2.2.0)
capistrano-sidekiq (~> 3.0.0.alpha.2)
+ capistrano3-puma (~> 6.0.0.beta.1)
capybara
caxlsx
caxlsx_rails
@@ -491,6 +501,7 @@ DEPENDENCIES
debug
down (~> 5.0)
faker!
+ faraday-retry (~> 2.2.0)
groupdate
hiredis (~> 0.6.3)
honeybadger (~> 5.0)
@@ -532,6 +543,7 @@ DEPENDENCIES
view_component
web-console
webdrivers
+ whenever (~> 1.0.0)
xsv
RUBY VERSION
diff --git a/Puma-Service.md b/Puma-Service.md
index f78299b0..c35f3399 100644
--- a/Puma-Service.md
+++ b/Puma-Service.md
@@ -23,9 +23,9 @@ sudo systemctl daemon-reload
sudo systemctl enable puma.service
sudo systemctl start puma.service
-sudo systemctl start puma-thepew51
-sudo systemctl stop puma-thepew51
-sudo systemctl status puma-thepew51
+sudo systemctl start puma
+sudo systemctl stop puma
+sudo systemctl status puma
ps -eo pid,comm,euser,supgrp | grep nginx
When using SSL add the following to ExecStart to enable Puma over HTTPS
diff --git a/app/views/polls/_poll.html.erb b/app/views/polls/_poll.html.erb
index 0e9e888f..1c89e221 100644
--- a/app/views/polls/_poll.html.erb
+++ b/app/views/polls/_poll.html.erb
@@ -2,10 +2,10 @@
<%= poll.title %>
|
-
+ |
<%= poll.user.profile.nickname %>
|
-
+ |
<%= poll.created_at.strftime('%b %d, %y') %>
|
diff --git a/app/views/polls/index.html.erb b/app/views/polls/index.html.erb
index 02bbaf3c..0ddfb0ab 100644
--- a/app/views/polls/index.html.erb
+++ b/app/views/polls/index.html.erb
@@ -15,13 +15,13 @@
|
Poll name
|
-
+ |
Created by
|
-
+ |
Created on
|
-
+ |
Participants
|
diff --git a/config/deploy.rb b/config/deploy.rb
index e38f4730..4a314842 100644
--- a/config/deploy.rb
+++ b/config/deploy.rb
@@ -11,17 +11,12 @@
# changing the branch... as master no longer exists on GitHub
set :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }
+set :rails_env, "production"
+set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
+
# Default deploy_to directory is /var/www/my_app_name
-# set :deploy_to, "/var/www/my_app_name"
set :deploy_to, "/home/deploy/#{fetch :application}"
-# Default value for :format is :airbrussh.
-# set :format, :airbrussh
-
-# You can configure the Airbrussh format using :format_options.
-# These are the defaults.
-# set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto
-
# Default value for :pty is false
# set :pty, true
@@ -42,15 +37,16 @@
# set :keep_releases, 5
set :keep_releases, 2
+set :use_sudo, true
+
# Uncomment the following to require manually verifying the host key before first deploy.
# set :ssh_options, verify_host_key: :secure
-# Puma configuration
-# set :use_sudo, true
-# set :linked_files, %w{config/master.key config/database.yml}
-set :rails_env, "production"
-set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
-# set :linked_files, %w{config/database.yml config/master.key}
+# Puma
+# Commented out as we control puma manually for the moment.
+# set :puma_service_unit_name, "puma.service"
+# set :puma_user, fetch(:user)
+# set :puma_role, :web
# Sidekiq
# set :sidekiq_service_unit_name, "sidekiq"
diff --git a/config/deploy/production.rb b/config/deploy/production.rb
index 871fecf0..fe2181a4 100644
--- a/config/deploy/production.rb
+++ b/config/deploy/production.rb
@@ -7,7 +7,7 @@
# server "example.com", user: "deploy", roles: %w{app web}, other_property: :other_value
# server "db.example.com", user: "deploy", roles: %w{db}
-server 'demo.thepew.io', user: 'deploy', roles: %w{app web}
+server 'demo.thepew.io', user: 'deploy', roles: %w{app db web}
# role-based syntax
# ==================
diff --git a/config/schedule.rb b/config/schedule.rb
new file mode 100644
index 00000000..9e272c37
--- /dev/null
+++ b/config/schedule.rb
@@ -0,0 +1,35 @@
+# Use this file to easily define all of your cron jobs.
+#
+# It's helpful, but not entirely necessary to understand cron before proceeding.
+# http://en.wikipedia.org/wiki/Cron
+
+# Example:
+#
+# set :output, "/path/to/my/cron_log.log"
+#
+# every 2.hours do
+# command "/usr/bin/some_great_command"
+# runner "MyModel.some_method"
+# rake "some:great:rake:task"
+# end
+#
+# every 4.days do
+# runner "AnotherModel.prune_old_records"
+# end
+
+# Learn more: http://github.com/javan/whenever
+
+# Fix an issue when using rbenv (at least)
+env :PATH, ENV['PATH']
+
+set :output, "log/cron.log"
+
+# Clear Active Sessions from sessions that are tooooo long
+every 6.hours do
+ rake 'clean_active_sessions:clean[false]'
+end
+
+# Close Events
+every 1.day do
+ rake 'close_events:close_events[false]'
+end
\ No newline at end of file
diff --git a/lib/capistrano/tasks/sidekiq.rake b/lib/capistrano/tasks/sidekiq.rake
new file mode 100644
index 00000000..b669cee0
--- /dev/null
+++ b/lib/capistrano/tasks/sidekiq.rake
@@ -0,0 +1,20 @@
+namespace :sidekiq do
+ after 'deploy:starting', 'sidekiq:stop'
+ after 'deploy:finished', 'sidekiq:start'
+
+ task :stop do
+ on roles(:app) do
+ within current_path do
+ # execute(:sudo, 'systemctl kill -s TSTP sidekiq')
+ execute(:sudo, 'systemctl stop sidekiq')
+ end
+ end
+ end
+
+ task :start do
+ on roles(:app) do |host|
+ execute(:sudo, 'systemctl start sidekiq')
+ info "Host #{host} (#{host.roles.to_a.join(', ')}):\t#{capture(:uptime)}"
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/tasks/clean_active_sessions.rake b/lib/tasks/clean_active_sessions.rake
index 623d29a5..eafd7599 100644
--- a/lib/tasks/clean_active_sessions.rake
+++ b/lib/tasks/clean_active_sessions.rake
@@ -7,15 +7,40 @@ namespace :clean_active_sessions do
# - rake "clean_active_sessions:clean[false]"
desc 'Remove session that are older than 20 days'
task :clean, [:dry_run] => :environment do |_t, args|
+ start_at= Time.now.utc
+ failed_list = []
+ session_count = 0
dry_run = true unless args[:dry_run] == 'false'
+ Rails.logger.error "[clean_active_sessions:clean] Starting at #{start_at}."
puts("[#{Time.now.utc}] Running clean session :: INI#{' (dry_run activated)' if dry_run}")
ActiveSession.where('active_sessions.created_at <= ?', 2.days.ago).find_each do |session|
- puts("Deleting Session: #{session.id}#{' (dry_run activated)' if dry_run}")
+ Rails.logger.error "[clean_active_sessions:clean] Deleting Session: #{session.id}#{' (dry_run activated)' if dry_run}."
+
session.destroy! unless dry_run
+
+ session_count += 1
+
+ rescue StandardError => e
+ failed_list.push({ session_id: session.id, reason: session.inspect })
+
+ Rails.logger.error "[clean_active_sessions:clean] session_id: #{session.id} failed to update user name."
+ Rails.logger.error "[clean_active_sessions:clean] session_id: #{session.id} failed reason: #{e.inspect}"
end
+ if failed_list.count > 0
+ Rails.logger.info "[clean_active_sessions:clean] Failed list: #{failed_list}"
+ p "[#{Time.now.utc}] [clean_active_sessions:clean] Failed list: #{failed_list}"
+ end
+
+ # End the task
+ # Compute task duration
+ end_at= Time.now.utc
+ duration = ((end_at - start_at) / 60.seconds).to_i
+
+ # Display closing messages and report to Rails logger for centralized logs
+ Rails.logger.error "[clean_active_sessions:clean] Ending at #{end_at}. Sessions terminated: #{session_count} in #{duration}"
puts("[#{Time.now.utc}] Running clean session :: END#{' (dry_run activated)' if dry_run}")
end
end
diff --git a/lib/tasks/close_events.rake b/lib/tasks/close_events.rake
index deec1a51..c4d53878 100644
--- a/lib/tasks/close_events.rake
+++ b/lib/tasks/close_events.rake
@@ -8,8 +8,12 @@ namespace :close_events do
# - rake "close_events:close_events[false]"
desc 'Close events'
task :close_events, [:dry_run] => :environment do |_t, args|
+ start_at= Time.now.utc
dry_run = true unless args[:dry_run] == 'false'
+ failed_list = []
+ event_closed_count = 0
+ Rails.logger.error "[close_events] Starting at #{start_at}."
puts("[#{Time.now.utc}] Running close_events :: INI#{' (dry_run activated)' if dry_run}")
# List all the opened events and can be closed, meaning the always_on flag is set to false
@@ -17,14 +21,46 @@ namespace :close_events do
# Current rule is to close an event 24h after its end_date.
@events = Event.where("always_on = false AND end_date < ?", 1.days.ago).opened
- @events.each do |event|
- # Closing the event
- event.closed!
+ puts("[#{Time.now.utc}] Events to be closed: #{@events.count}")
+
+ # If there is no event to be closed we simply exit the task.
+ if @events.count > 0 then
+ @events.each do |event|
+ # Closing the event
+ event.closed!
+
+ # Removing the PIN
+ event.pin = nil
+
+ # Updating the event
+ event.update!
+
+ # Update the counter
+ event_closed_count += 1
+
+ # Rescue used to log errors and report to Rails looger
+ rescue StandardError => e
+ failed_list.push({ event_id: event.id, reason: event.inspect })
+
+ Rails.logger.error "[close_events] event_id: #{event.id} failed to update user name."
+ Rails.logger.error "[close_events] event_id: #{event.id} failed reason: #{e.inspect}"
+ end
- # Removing the PIN
- event.pin = nil
+ if failed_list.count > 0
+ Rails.logger.info "[close_events] Failed list: #{failed_list}"
+ p "[#{Time.now.utc}] [close_events] Failed list: #{failed_list}"
+ end
+
+ puts("[#{Time.now.utc}] #{event_closed_count} events have been closed")
end
-
+
+ # End the task
+ # Compute task duration
+ end_at= Time.now.utc
+ duration = ((end_at - start_at) / 60.seconds).to_i
+ # Display closing messages and report to Rails logger for centralized logs
+ Rails.logger.error "[close_events] Ending at #{end_at}. Closed #{event_closed_count} event(s) in #{duration}"
+ puts("[#{Time.now.utc}] Running close_events :: duration: #{duration} :: END#{' (dry_run activated)' if dry_run}")
puts("[#{Time.now.utc}] Running close_events :: END#{' (dry_run activated)' if dry_run}")
end
diff --git a/lib/tasks/db_cleanup.rake b/lib/tasks/db_cleanup.rake
index 40087adc..892622b4 100644
--- a/lib/tasks/db_cleanup.rake
+++ b/lib/tasks/db_cleanup.rake
@@ -8,8 +8,12 @@ namespace :db_cleanup do
# - rake "db_cleanup:remove_orphan_members_and_accounts[false]"
desc 'Remove accounts that have no user'
task :remove_orphan_members_and_accounts, [:dry_run] => :environment do |_t, args|
+ start_at= Time.now.utc
+ failed_list = []
+ member_count = 0
dry_run = true unless args[:dry_run] == 'false'
+ Rails.logger.error "[remove_orphan_members_and_accounts] Starting at #{start_at}."
puts("[#{Time.now.utc}] Running remove_orphan_members_and_accounts :: INI#{' (dry_run activated)' if dry_run}")
# List all the rows in members which do not have a matching user
@@ -24,9 +28,28 @@ namespace :db_cleanup do
# Delete the orphan member entry
Member.destroy!(member.id)
+
+ # Increment the counter
+ member_count += 1
+ rescue StandardError => e
+ failed_list.push({ member_id: member.id, reason: member.inspect })
+
+ Rails.logger.error "[remove_orphan_members_and_accounts] member_id: #{member.id} failed to update user name."
+ Rails.logger.error "[remove_orphan_members_and_accounts] member_id: #{member.id} failed reason: #{e.inspect}"
end
+ if failed_list.count > 0
+ Rails.logger.info "[remove_orphan_members_and_accounts] Failed list: #{failed_list}"
+ p "[#{Time.now.utc}] [remove_orphan_members_and_accounts] Failed list: #{failed_list}"
+ end
+ # End the task
+ # Compute task duration
+ end_at= Time.now.utc
+ duration = ((end_at - start_at) / 60.seconds).to_i
+
+ # Display closing messages and report to Rails logger for centralized logs
+ Rails.logger.error "[remove_orphan_members_and_accounts] Ending at #{end_at}. Members removed: #{member_count} in #{duration}"
puts("[#{Time.now.utc}] Running remove_orphan_members_and_accounts :: END#{' (dry_run activated)' if dry_run}")
end
@@ -39,7 +62,7 @@ namespace :db_cleanup do
puts("[#{Time.now.utc}] Running remove_orphan_accounts :: INI#{' (dry_run activated)' if dry_run}")
- # List all the rows in members which do not have a matching user
+ # List all the rows in accounts which do not have a matching user
# In order to avoid deleting objects that are freshly created and might still be in review
# it has been decided to only check objects for which the created_at value is greater or equal
# to 7 days from the current date.
|