capistrano-ops 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +21 -0
- data/README.md +61 -13
- data/capistrano-ops.gemspec +5 -5
- data/lib/capistrano/ops/backup/api.rb +40 -0
- data/lib/capistrano/ops/backup/s3.rb +52 -0
- data/lib/capistrano/ops/backup.rb +8 -0
- data/lib/capistrano/ops/capistrano/v3/tasks/backup/backup_helper.rb +50 -0
- data/lib/capistrano/ops/capistrano/v3/tasks/backup/database/create.rake +18 -0
- data/lib/capistrano/ops/capistrano/v3/tasks/backup/database/pull.rake +28 -0
- data/lib/capistrano/ops/capistrano/v3/tasks/backup/storage/create.rake +18 -0
- data/lib/capistrano/ops/capistrano/v3/tasks/backup/storage/pull.rake +30 -0
- data/lib/capistrano/ops/capistrano/v3/tasks/backup.rake +10 -14
- data/lib/capistrano/ops/capistrano/v3/tasks/figaro_yml.rake +59 -60
- data/lib/capistrano/ops/capistrano/v3/tasks/invoke.rake +16 -17
- data/lib/capistrano/ops/capistrano.rb +2 -0
- data/lib/capistrano/ops/notification/api.rb +29 -25
- data/lib/capistrano/ops/notification/slack.rb +65 -60
- data/lib/capistrano/ops/notification/webhook.rb +43 -36
- data/lib/capistrano/ops/notification.rb +4 -2
- data/lib/capistrano/ops/railtie.rb +1 -1
- data/lib/capistrano/ops/tasks/pg/dump.rake +60 -27
- data/lib/capistrano/ops/tasks/pg/remove_old_dumps.rake +50 -11
- data/lib/capistrano/ops/tasks/storage/backup.rake +66 -0
- data/lib/capistrano/ops/tasks/storage/remove_old_backups.rake +58 -0
- data/lib/capistrano/ops/version.rb +2 -1
- data/lib/capistrano/ops.rb +19 -18
- metadata +45 -9
@@ -1,24 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
namespace :invoke do
|
4
|
+
# Defalut to :app roles
|
5
|
+
rake_roles = fetch(:rake_roles, :app)
|
2
6
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
within current_path do
|
11
|
-
with rails_env: fetch(:rails_env) do
|
12
|
-
execute :rake, ENV['TASK']
|
13
|
-
end
|
7
|
+
desc 'Execute a rake task on a remote server (cap invoke:rake TASK=db:migrate)'
|
8
|
+
task :rake do
|
9
|
+
if ENV['TASK']
|
10
|
+
on roles(rake_roles) do
|
11
|
+
within current_path do
|
12
|
+
with rails_env: fetch(:rails_env) do
|
13
|
+
execute :rake, ENV['TASK']
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
17
|
-
else
|
18
|
-
puts "\n\nFailed! You need to specify the 'TASK' parameter!",
|
19
|
-
"Usage: cap <stage> invoke:rake TASK=your:task"
|
20
16
|
end
|
17
|
+
|
18
|
+
else
|
19
|
+
puts "\n\nFailed! You need to specify the 'TASK' parameter!",
|
20
|
+
'Usage: cap <stage> invoke:rake TASK=your:task'
|
21
21
|
end
|
22
|
-
|
23
22
|
end
|
24
|
-
|
23
|
+
end
|
@@ -8,6 +8,8 @@ if defined?(Capistrano::VERSION) && Gem::Version.new(Capistrano::VERSION).releas
|
|
8
8
|
load File.expand_path('capistrano/v3/tasks/figaro_yml.rake', __dir__)
|
9
9
|
load File.expand_path('capistrano/v3/tasks/logs.rake', __dir__)
|
10
10
|
load File.expand_path('capistrano/v3/tasks/invoke.rake', __dir__)
|
11
|
+
path = File.expand_path(__dir__)
|
12
|
+
Dir.glob("#{path}/capistrano/v3/tasks/backup/**/*.rake").each { |f| load f }
|
11
13
|
else
|
12
14
|
puts 'Capistrano 3 is required to use this gem'
|
13
15
|
end
|
@@ -1,30 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Notification
|
2
|
-
|
3
|
-
|
4
|
+
class Api
|
5
|
+
attr_accessor :notification_type, :notification_level
|
6
|
+
|
7
|
+
def initialize(notification_type: ENV['NOTIFICATION_TYPE'], notification_level: ENV['NOTIFICATION_LEVEL'])
|
8
|
+
self.notification_type = notification_type
|
9
|
+
self.notification_level = notification_level || 'error'
|
10
|
+
end
|
4
11
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
12
|
+
def send_backup_notification(result, title, content, webhook_data = nil)
|
13
|
+
return if notification_type.nil?
|
14
|
+
|
15
|
+
case notification_type
|
16
|
+
when 'slack'
|
17
|
+
Slack.new.backup_notification(result, title, content, notification_level)
|
18
|
+
when 'webhook'
|
19
|
+
Webhook.new.backup_notification(result, webhook_data, notification_level)
|
20
|
+
end
|
21
|
+
end
|
9
22
|
|
10
|
-
|
11
|
-
|
12
|
-
case notification_type
|
13
|
-
when 'slack'
|
14
|
-
Slack.new.backup_notification(result, date, database, backup_path, notification_level)
|
15
|
-
when 'webhook'
|
16
|
-
Webhook.new.backup_notification(result, date, database, backup_path, notification_level)
|
17
|
-
end
|
18
|
-
end
|
23
|
+
def send_notification(message)
|
24
|
+
return if notification_type.nil?
|
19
25
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
p 'webhook'
|
27
|
-
end
|
28
|
-
end
|
26
|
+
case notification_type
|
27
|
+
when 'slack'
|
28
|
+
Slack.new.notify(message)
|
29
|
+
when 'webhook'
|
30
|
+
p 'webhook'
|
31
|
+
end
|
29
32
|
end
|
30
|
-
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,67 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'json'
|
5
|
+
|
1
6
|
module Notification
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
class Slack
|
8
|
+
def initialize
|
9
|
+
@slack_secret = ENV['SLACK_SECRET']
|
10
|
+
@slack_channel = ENV['SLACK_CHANNEL']
|
11
|
+
@conn = Faraday.new(url: 'https://slack.com/api/') do |faraday|
|
12
|
+
faraday.headers['Content-Type'] = 'application/json'
|
13
|
+
faraday.headers['Authorization'] = "Bearer #{@slack_secret}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def notify(message)
|
18
|
+
return if @slack_secret.nil? || @slack_channel.nil?
|
11
19
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
request.body = {
|
19
|
-
channel: @slack_channel,
|
20
|
-
text: message
|
21
|
-
}.to_json
|
22
|
-
response = http.request(request)
|
23
|
-
puts response.body
|
20
|
+
begin
|
21
|
+
res = @conn.post('chat.postMessage') do |req|
|
22
|
+
req.body = {
|
23
|
+
"channel": @slack_channel,
|
24
|
+
"text": message
|
25
|
+
}.to_json
|
24
26
|
end
|
27
|
+
response = JSON.parse(res.body)
|
28
|
+
raise Notification::Error, response['error'] if response['ok'] == false
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
if response['ok'] == false
|
59
|
-
raise Notification::Error, response['error']
|
60
|
-
end
|
61
|
-
response
|
62
|
-
rescue => e
|
63
|
-
puts "Slack error: \n\t#{e.message}"
|
64
|
-
end
|
30
|
+
response
|
31
|
+
rescue Notification::Error => e
|
32
|
+
puts "Slack error: \n\t#{e.message}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def backup_notification(result, title, content, notification_level)
|
37
|
+
return if @slack_secret.nil? || @slack_channel.nil?
|
38
|
+
return if notification_level == 'error' && result
|
39
|
+
|
40
|
+
begin
|
41
|
+
res = @conn.post('chat.postMessage') do |req|
|
42
|
+
req.body = {
|
43
|
+
channel: @slack_channel,
|
44
|
+
blocks: [
|
45
|
+
{
|
46
|
+
type: 'header',
|
47
|
+
text: {
|
48
|
+
type: 'plain_text',
|
49
|
+
text: title || "#{Rails.env} Message",
|
50
|
+
emoji: true
|
51
|
+
}
|
52
|
+
},
|
53
|
+
{
|
54
|
+
type: 'section',
|
55
|
+
text: {
|
56
|
+
type: 'mrkdwn',
|
57
|
+
text: content || 'No content'
|
58
|
+
}
|
59
|
+
}
|
60
|
+
]
|
61
|
+
}.to_json
|
65
62
|
end
|
63
|
+
response = JSON.parse(res.body)
|
64
|
+
raise Notification::Error, response['error'] if response['ok'] == false
|
65
|
+
|
66
|
+
response
|
67
|
+
rescue Notification::Error => e
|
68
|
+
puts "Slack error: \n\t#{e.message}"
|
69
|
+
end
|
66
70
|
end
|
71
|
+
end
|
67
72
|
end
|
@@ -1,40 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Notification
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
require 'openssl'
|
7
|
-
require 'json'
|
8
|
-
|
9
|
-
def initialize
|
10
|
-
@webhook_url = ENV['WEBHOOK_URL']
|
11
|
-
@secret = ENV['WEBHOOK_SECRET']
|
12
|
-
end
|
13
|
-
|
14
|
-
def generate_signature(payload_body)
|
15
|
-
"md5=#{OpenSSL::HMAC.hexdigest('md5', ENV['WEBHOOK_SECRET'], payload_body)}"
|
16
|
-
end
|
4
|
+
class Webhook
|
5
|
+
require 'faraday'
|
6
|
+
require 'openssl'
|
7
|
+
require 'json'
|
17
8
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
9
|
+
def initialize
|
10
|
+
@webhook_url = ENV['WEBHOOK_URL']
|
11
|
+
@secret = ENV['WEBHOOK_SECRET']
|
12
|
+
@conn = Faraday.new(url: @webhook_url) do |faraday|
|
13
|
+
faraday.headers['Content-Type'] = 'application/json'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_signature(payload_body)
|
18
|
+
"md5=#{OpenSSL::HMAC.hexdigest('md5', ENV['WEBHOOK_SECRET'], payload_body)}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def backup_notification(result, webhook_data, _notification_level)
|
22
|
+
return if @webhook_url.nil? || @secret.nil?
|
23
|
+
return if result && notification_level == 'error'
|
24
|
+
|
25
|
+
@date = webhook_data[:date]
|
26
|
+
@database = webhook_data[:database]
|
27
|
+
@backup_path = webhook_data[:backup_path]
|
28
|
+
|
29
|
+
@data = {
|
30
|
+
domain: ENV['DEFAULT_URL'] || "#{@database} Backup",
|
31
|
+
backupPath: result ? @backup_path : nil,
|
32
|
+
backupDate: @date
|
33
|
+
}.to_json
|
34
|
+
|
35
|
+
begin
|
36
|
+
@response = @conn.post do |req|
|
37
|
+
req.headers['x-hub-signature'] = generate_signature(@data.to_s)
|
38
|
+
req.body = @data
|
38
39
|
end
|
40
|
+
|
41
|
+
@response.to_hash
|
42
|
+
rescue StandardError => e
|
43
|
+
puts "Webhook error: \n\t#{e.message}"
|
44
|
+
end
|
39
45
|
end
|
40
|
-
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'capistrano/ops/notification/api'
|
2
4
|
require 'capistrano/ops/notification/slack'
|
3
5
|
require 'capistrano/ops/notification/webhook'
|
4
6
|
|
5
7
|
module Notification
|
6
|
-
|
7
|
-
end
|
8
|
+
class Error < StandardError; end
|
9
|
+
end
|
@@ -2,38 +2,71 @@
|
|
2
2
|
|
3
3
|
# rubocop:disable Metrics/BlockLength
|
4
4
|
namespace :pg do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
backup_path = Rails.root.join(default_backup_path).to_s
|
5
|
+
@database = Rails.configuration.database_configuration[Rails.env]['database']
|
6
|
+
@username = Rails.configuration.database_configuration[Rails.env]['username']
|
7
|
+
@password = Rails.configuration.database_configuration[Rails.env]['password']
|
8
|
+
@hostname = Rails.configuration.database_configuration[Rails.env]['host']
|
9
|
+
@portnumber = Rails.configuration.database_configuration[Rails.env]['port']
|
10
|
+
@backup_path = Rails.root.join(Rails.env.development? ? 'tmp/backups' : '../../shared/backups').to_s
|
12
11
|
backups_enabled = Rails.env.production? || ENV['BACKUPS_ENABLED'] == 'true'
|
13
|
-
|
12
|
+
external_backup = Rails.env.production? || ENV['EXTERNAL_BACKUP_ENABLED'] == 'true'
|
14
13
|
task :dump do
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
host = hostname.present? ? " -h #{hostname}" : ''
|
19
|
-
port = portnumber.present? ? " -p #{portnumber}" : ''
|
20
|
-
# rubocop:disable Layout/LineLength
|
21
|
-
dump_cmd = "export PGPASSWORD='#{password}' && cd #{backup_path} && pg_dump -Fc -d #{database}#{user}#{host}#{port} > #{database}_#{date}.dump"
|
22
|
-
# rubocop:enable Layout/LineLength
|
23
|
-
if backups_enabled
|
24
|
-
system "mkdir -p #{backup_path}" unless Dir.exist?(backup_path)
|
25
|
-
result = system(dump_cmd)
|
26
|
-
api.send_backup_notification(result, date, database, backup_path)
|
27
|
-
# Notification::Slack.new.backup_notification(result, date, database, backup_path)
|
14
|
+
unless backups_enabled
|
15
|
+
puts 'dump: Backups are disabled'
|
16
|
+
exit(0)
|
28
17
|
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
#
|
18
|
+
|
19
|
+
notification = Notification::Api.new
|
20
|
+
commandlist = dump_cmd
|
21
|
+
|
22
|
+
system "mkdir -p #{@backup_path}" unless Dir.exist?(@backup_path)
|
23
|
+
|
24
|
+
result = system(commandlist.join(' && '))
|
25
|
+
|
26
|
+
if ENV['BACKUP_PROVIDER'].present? && external_backup && result
|
27
|
+
puts "Uploading #{@filename} to #{ENV['BACKUP_PROVIDER']}..."
|
28
|
+
provider = Backup::Api.new
|
29
|
+
begin
|
30
|
+
provider.upload("#{@backup_path}/#{@filename}", @filename.to_s)
|
31
|
+
puts "#{@filename} uploaded to #{ENV['BACKUP_PROVIDER']}"
|
32
|
+
rescue StandardError => e
|
33
|
+
puts "#{@filename} upload failed: #{e.message}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
notification.send_backup_notification(result, title, content(result), { date: @date, database: @database, backup_path: @backup_path })
|
37
|
+
puts result ? "Backup created: #{@backup_path}/#{@filename}" : 'Backup failed removing dump file'
|
38
|
+
system "rm #{@backup_path}/#{@filename}" unless result
|
39
|
+
end
|
40
|
+
|
41
|
+
def title
|
42
|
+
ENV['DEFAULT_URL'] || "#{Rails.env} Backup"
|
43
|
+
end
|
44
|
+
|
45
|
+
def content(result)
|
46
|
+
messages = []
|
47
|
+
if result
|
48
|
+
messages << "Backup of #{@database} successfully finished at #{Time.now}"
|
49
|
+
messages << "Backup path:\`#{@backup_path}/#{@filename}\`"
|
34
50
|
else
|
35
|
-
|
51
|
+
messages << "Backup of #{@database} failed at #{Time.now}"
|
36
52
|
end
|
53
|
+
messages.join("\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
def dump_cmd
|
57
|
+
@date = Time.now.to_i
|
58
|
+
options = []
|
59
|
+
options << " -d #{@database}" if @database.present?
|
60
|
+
options << " -U #{@username}" if @username.present?
|
61
|
+
options << " -h #{@hostname}" if @hostname.present?
|
62
|
+
options << " -p #{@portnumber}" if @portnumber.present?
|
63
|
+
|
64
|
+
@filename = "#{@database}_#{@date}.dump"
|
65
|
+
|
66
|
+
commandlist = []
|
67
|
+
commandlist << "export PGPASSWORD='#{@password}'"
|
68
|
+
commandlist << "cd #{@backup_path}"
|
69
|
+
commandlist << "pg_dump -Fc #{options.join('')} > #{@filename}"
|
37
70
|
end
|
38
71
|
end
|
39
72
|
# rubocop:enable Metrics/BlockLength
|
@@ -1,19 +1,58 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'rake'
|
3
4
|
namespace :pg do
|
4
|
-
|
5
|
-
database = Rails.configuration.database_configuration[Rails.env]['database']
|
6
|
-
|
7
|
-
|
5
|
+
@backup_path = Rails.root.join(Rails.env.development? ? 'tmp/backups' : '../../shared/backups').to_s
|
6
|
+
@database = Rails.configuration.database_configuration[Rails.env]['database']
|
7
|
+
@env_local_no = ENV['NUMBER_OF_LOCAL_BACKUPS']
|
8
|
+
@env_external_no = ENV['NUMBER_OF_EXTERNAL_BACKUPS']
|
9
|
+
@total_local_backups_no = (@env_local_no || ENV['NUMBER_OF_BACKUPS'] || 7).to_i
|
10
|
+
@total_external_backups_no = (@env_external_no || ENV['NUMBER_OF_BACKUPS'] || 7).to_i
|
8
11
|
backups_enabled = Rails.env.production? || ENV['BACKUPS_ENABLED'] == 'true'
|
12
|
+
external_backup = Rails.env.production? || ENV['EXTERNAL_BACKUP_ENABLED'] == 'true'
|
9
13
|
|
10
14
|
task :remove_old_dumps do
|
11
|
-
bash_regex = "'#{database}.{0,}\.dump'"
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
bash_regex = "'#{@database}.{0,}\.dump'"
|
16
|
+
|
17
|
+
unless backups_enabled
|
18
|
+
puts 'remove_old_dumps: Backups are disabled'
|
19
|
+
exit(0)
|
20
|
+
end
|
21
|
+
unless @total_local_backups_no.positive?
|
22
|
+
puts "remove_old_dumps: No local cleanup because option '#{if @env_local_no
|
23
|
+
'NUMBER_OF_LOCAL_BACKUPS='
|
24
|
+
else
|
25
|
+
'NUMBER_OF_BACKUPS='
|
26
|
+
end}#{@total_local_backups_no}' sets unlimited backups"
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
commandlist = [
|
31
|
+
"cd #{@backup_path} && ls -lt ",
|
32
|
+
"grep -E -i #{bash_regex} ",
|
33
|
+
"tail -n +#{@total_local_backups_no + 1} ",
|
34
|
+
"awk '{print $9}' ",
|
35
|
+
'xargs rm -rf'
|
36
|
+
]
|
37
|
+
|
38
|
+
system(commandlist.join(' | '))
|
39
|
+
|
40
|
+
if ENV['BACKUP_PROVIDER'].present? && external_backup
|
41
|
+
unless @total_external_backups_no.positive?
|
42
|
+
puts "remove_old_dumps: No external cleanup because option '#{if @env_external_no
|
43
|
+
'NUMBER_OF_EXTERNAL_BACKUPS='
|
44
|
+
else
|
45
|
+
'NUMBER_OF_BACKUPS='
|
46
|
+
end}#{@total_external_backups_no}' sets unlimited backups"
|
47
|
+
exit(0)
|
48
|
+
end
|
49
|
+
provider = Backup::Api.new
|
50
|
+
begin
|
51
|
+
result = provider.remove_old_backups(@database, @total_external_backups_no)
|
52
|
+
rescue StandardError => e
|
53
|
+
puts "remove_old_dumps failed: #{e.message}"
|
54
|
+
end
|
55
|
+
puts 'remove_old_dumps finished' if result
|
56
|
+
end
|
18
57
|
end
|
19
58
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
4
|
+
namespace :storage do
|
5
|
+
@backup_path = Rails.root.join(Rails.env.development? ? 'tmp/backups' : '../../shared/backups').to_s
|
6
|
+
@storage_path = Rails.root.join(Rails.env.development? ? 'storage' : '../../shared/storage').to_s
|
7
|
+
backups_enabled = Rails.env.production? || ENV['BACKUPS_ENABLED'] == 'true'
|
8
|
+
external_backup = Rails.env.production? || ENV['EXTERNAL_BACKUP_ENABLED'] == 'true'
|
9
|
+
|
10
|
+
desc 'backup storage'
|
11
|
+
task :backup do
|
12
|
+
unless backups_enabled
|
13
|
+
puts 'storage: Backups are disabled'
|
14
|
+
exit(0)
|
15
|
+
end
|
16
|
+
notification = Notification::Api.new
|
17
|
+
|
18
|
+
date = Time.now.to_i
|
19
|
+
@filename = "storage_#{date}.tar.gz"
|
20
|
+
FileUtils.mkdir_p(@backup_path) unless Dir.exist?(@backup_path)
|
21
|
+
result = system "tar -zcf #{@backup_path}/#{@filename} -C #{@storage_path} ."
|
22
|
+
FileUtils.rm_rf("#{@backup_path}/#{filename}") unless result
|
23
|
+
puts result ? "Backup created: #{@backup_path}/#{@filename} (#{size_str(File.size("#{@backup_path}/#{@filename}"))})" : 'Backup failed removing dump file'
|
24
|
+
|
25
|
+
if ENV['BACKUP_PROVIDER'].present? && external_backup && result
|
26
|
+
puts "Uploading #{@filename} to #{ENV['BACKUP_PROVIDER']}..."
|
27
|
+
provider = Backup::Api.new
|
28
|
+
begin
|
29
|
+
provider.upload("#{@backup_path}/#{@filename}", @filename.to_s)
|
30
|
+
puts "#{@filename} uploaded to #{ENV['BACKUP_PROVIDER']}"
|
31
|
+
rescue StandardError => e
|
32
|
+
puts "#{@filename} upload failed: #{e.message}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
notification.send_backup_notification(result, title, message(result), { date: date, backup_path: @backup_path, database: 'storage' })
|
36
|
+
end
|
37
|
+
|
38
|
+
def title
|
39
|
+
ENV['DEFAULT_URL'] || "#{Rails.env} Backup"
|
40
|
+
end
|
41
|
+
|
42
|
+
def message(result)
|
43
|
+
messages = []
|
44
|
+
if result
|
45
|
+
messages << "Backup of storage folder successfully finished at #{Time.now}"
|
46
|
+
messages << "Backup path:\`#{@backup_path}/#{@filename}\`"
|
47
|
+
else
|
48
|
+
messages << "Backup of storage folder failed at #{Time.now}"
|
49
|
+
end
|
50
|
+
messages.join("\n")
|
51
|
+
end
|
52
|
+
|
53
|
+
def size_str(size)
|
54
|
+
case size
|
55
|
+
when 0..1024
|
56
|
+
"#{size} B"
|
57
|
+
when 1024..1024 * 1024
|
58
|
+
"#{size / 1024} KB"
|
59
|
+
when 1024 * 1024..1024 * 1024 * 1024
|
60
|
+
"#{size / 1024 / 1024} MB"
|
61
|
+
when 1024 * 1024 * 1024..1024 * 1024 * 1024 * 1024
|
62
|
+
"#{size / 1024 / 1024 / 1024} GB"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# rubocop:enable Metrics/BlockLength
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
namespace :storage do
|
5
|
+
backup_path = Rails.root.join(Rails.env.development? ? 'tmp/backups' : '../../shared/backups').to_s
|
6
|
+
backups_enabled = Rails.env.production? || ENV['BACKUPS_ENABLED'] == 'true'
|
7
|
+
external_backup = Rails.env.production? || ENV['EXTERNAL_BACKUP_ENABLED'] == 'true'
|
8
|
+
|
9
|
+
@env_local_no = ENV['NUMBER_OF_LOCAL_BACKUPS']
|
10
|
+
@env_external_no = ENV['NUMBER_OF_EXTERNAL_BACKUPS']
|
11
|
+
@total_local_backups_no = (@env_local_no || ENV['NUMBER_OF_BACKUPS'] || 7).to_i
|
12
|
+
@total_external_backups_no = (@env_external_no || ENV['NUMBER_OF_BACKUPS'] || 7).to_i
|
13
|
+
desc 'remove old storage backups'
|
14
|
+
task :remove_old_backups do
|
15
|
+
bash_regex = "'storage_.{0,}\.tar.gz'"
|
16
|
+
|
17
|
+
unless backups_enabled
|
18
|
+
puts 'remove_old_backups: Backups are disabled'
|
19
|
+
exit(0)
|
20
|
+
end
|
21
|
+
|
22
|
+
unless @total_local_backups_no.positive?
|
23
|
+
puts "remove_old_backups: No local cleanup because option '#{if @env_local_no
|
24
|
+
'NUMBER_OF_LOCAL_BACKUPS='
|
25
|
+
else
|
26
|
+
'NUMBER_OF_BACKUPS='
|
27
|
+
end}#{@total_local_backups_no}' sets unlimited backups"
|
28
|
+
end
|
29
|
+
|
30
|
+
commandlist = [
|
31
|
+
"cd #{backup_path} && ls -lt ",
|
32
|
+
"grep -E -i #{bash_regex} ",
|
33
|
+
"tail -n +#{@total_local_backups_no + 1} ",
|
34
|
+
"awk '{print $9}' ",
|
35
|
+
'xargs rm -rf'
|
36
|
+
]
|
37
|
+
|
38
|
+
system(commandlist.join(' | '))
|
39
|
+
|
40
|
+
if ENV['BACKUP_PROVIDER'].present? && external_backup
|
41
|
+
unless @total_external_backups_no.positive?
|
42
|
+
puts "remove_old_backups: No external cleanup because option '#{if @env_external_no
|
43
|
+
'NUMBER_OF_EXTERNAL_BACKUPS='
|
44
|
+
else
|
45
|
+
'NUMBER_OF_BACKUPS='
|
46
|
+
end}#{@total_external_backups_no}' sets unlimited backups"
|
47
|
+
exit(0)
|
48
|
+
end
|
49
|
+
provider = Backup::Api.new
|
50
|
+
begin
|
51
|
+
result = provider.remove_old_backups('storage_', @total_external_backups_no)
|
52
|
+
rescue StandardError => e
|
53
|
+
puts "remove_old_backups failed: #{e.message}"
|
54
|
+
end
|
55
|
+
puts 'remove_old_backups finished' if result
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|