capistrano-ops 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 772b42fc59038f214889f2e20c4d3978d932d2212a5d8c231622333570b37daa
4
+ data.tar.gz: bf996f05934e11484667fe0a8e4c0b4027acd4cfefa7f4d3e54541a71a60ce5b
5
+ SHA512:
6
+ metadata.gz: 3fddcba0cc8ce3b3a6bb363dadd9e76261478820dd21247f1f96ed03dfd037f3a93b9c14e572d7f406b0721892417ac44fc36a0e7f6922d56eaa3f6ac0b0160b
7
+ data.tar.gz: '09bbf508cae370818e13a8753d07991cb954f0749db120150200ef2a738e5fee347235de19d2a1cf7b67008e86422aa89bf0b84dd6a6499ccf517bbb834ff069'
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.gem
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+
3
+ before_install: gem install bundler
4
+
5
+ rvm:
6
+ - 2.4.6
7
+ - 2.5.5
8
+ - 2.6.3
9
+ - jruby-9.2.6.0
10
+
11
+ env:
12
+ global:
13
+ - JRUBY_OPTS=--debug
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in capistrano-ops.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 zauberware
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # capistrano-ops
2
+
3
+ Library of useful scripts for DevOps using capistrano with rails.
4
+
5
+ **Only supports Capistrano 3 and above**.
6
+
7
+ ## Requirements
8
+
9
+ ```ruby
10
+ 'capistrano', '~> 3.0'
11
+ 'whenever'
12
+ ```
13
+
14
+ ## Installation
15
+
16
+ Add the gem to your `Gemfile` after setting up Capistrano
17
+ group:
18
+
19
+ ```ruby
20
+ group :development do
21
+ gem 'capistrano', require: false
22
+ end
23
+
24
+ gem 'capistrano-ops'
25
+ ```
26
+
27
+ Then `bundle` and add it to your `Capfile`
28
+
29
+ ```ruby
30
+ # Capfile
31
+
32
+ require 'capistrano/ops'
33
+ ```
34
+
35
+ and `initializers`
36
+
37
+ ```ruby
38
+ # initializers/capistrano-ops.rb
39
+ require 'capistrano/ops'
40
+ ```
41
+
42
+ ## Script overview
43
+
44
+ | Script | Description |
45
+ | ------------------------------------------------ | ----------------------------------------------------------------- |
46
+ | `cap <environment> backup:create` | creates backup of postgres database on the server |
47
+ | `cap <environment> backup:pull` | download latest postgres backup from server |
48
+ | `cap <environment> figaro_yml:compare` | compare local application.yml with server application.yml |
49
+ | `cap <environment> figaro_yml:get` | shows env vars from server application.yml configured thru figaro |
50
+ | `cap <environment> logs:rails` | display server log live |
51
+ | `cap <environment> whenever:show_crontab` | display server app crontab generated with whenever |
52
+ | `cap <environment> invoke:rake TASK=<your:task>` | invoke rake task on server |
53
+ | `rake pg:dump` | creates postgres database backup |
54
+ | `rake pg:remove_old_dumps` | remove old postgres backups |
55
+
56
+ ## Usage
57
+
58
+ for all backup task you have to setup your database.yml properly:
59
+
60
+ ```
61
+ production:
62
+
63
+ database: database_name
64
+ username: database_username
65
+ password: database_password
66
+ host: database_host
67
+ port: database_port
68
+ ```
69
+
70
+ ### Optional Settings for backup task
71
+
72
+ | env | description | type/options |
73
+ | ----------------- | ---------------------------------------------------------------------- | :----------------------------------------------------------------: |
74
+ | NUMBER_OF_BACKUPS | number of backups to keep (default: 1) | `number` |
75
+ | BACKUPS_ENABLED | enable/disable backup task (default: Rails.env == 'production') | `boolean` |
76
+ | DEFAULT_URL | notification message title (default: "#{database} Backup") | `string` |
77
+ | NOTIFICATION_TYPE | for notification (default: nil) | `string` (`webhook`/`slack`) |
78
+ | SLACK_SECRET | for slack integration | `string` (e.g. `xoxb-1234567890-1234567890-1234567890-1234567890`) |
79
+ | SLACK_CHANNEL | for slack integration | `string` (e.g. `C234567890`) |
80
+ | WEBHOOK_URL | Webhook server to send message | e.g `http://example.com` |
81
+ | WEBHOOK_SECRET | Secret to send with uses md5-hmac hexdigest in header`x-hub-signature` | --- |
82
+
83
+ ### use with whenever/capistrano
84
+
85
+ install whenever gem and add this to your schedule.rb
86
+
87
+ ```ruby
88
+ # config/schedule.rb
89
+ # Use this file to easily define all of your cron jobs.
90
+ env :PATH, ENV['PATH']
91
+ set :output, -> { '2>&1 | logger -t whenever_cron' }
92
+
93
+ every :day, at: '2:00 am' do
94
+ rake 'pg:dump'
95
+ end
96
+
97
+ every :day, at: '3:00 am' do
98
+ rake 'pg:remove_old_dumps'
99
+ end
100
+ ```
101
+
102
+ add this to your capfile
103
+
104
+ ```ruby
105
+ # Capfile
106
+ require 'whenever/capistrano'
107
+ ```
108
+
109
+ ## Configuration
110
+
111
+ You can optionally specify the capistrano roles for the rake task (Defaults to `:app`):
112
+
113
+ ```ruby
114
+ # Defaults to [:app]
115
+ set :rake_roles, %i[db app]
116
+ ```
117
+
118
+ ## Slack integration
119
+
120
+ if you want to use slack integration you have to add this to your `application.yml`
121
+
122
+ ```ruby
123
+ NOTIFICATION_TYPE: 'slack'
124
+ SLACK_SECRET: '<your-slack-secret>'
125
+ SLACK_CHANNEL: '<your-slack-channel>'
126
+ ```
127
+
128
+ ## Webhook integration
129
+
130
+ if you want to use webhook integration you have to add this to your `application.yml`
131
+
132
+ ```ruby
133
+ NOTIFICATION_TYPE: 'webhook'
134
+ WEBHOOK_URL: '<your-webhook-url>'
135
+ WEBHOOK_SECRET: '<your-webhook-secret>'
136
+ ```
137
+
138
+ ## Contributing
139
+
140
+ 1. Fork it ( https://github.com/zauberware/capistrano-ops/fork )
141
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
142
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
143
+ 4. Push to the branch (`git push origin my-new-feature`)
144
+ 5. Create a new Pull Request
145
+
146
+ ## License
147
+
148
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
149
+
150
+ ```
151
+
152
+ ```
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'capistrano/rake'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+
5
+ require 'capistrano/ops/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'capistrano-ops'
9
+ s.version = Capistrano::Ops::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ['Florian Crusius']
12
+ s.email = ['florian@zauberware.com']
13
+ s.license = 'MIT'
14
+ s.homepage = 'https://github.com/zauberware/capistrano-ops'
15
+ s.summary = 'devops tasks for rails applications'
16
+ s.description = 'A collection of devops tasks for rails applications'
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- test/{functional,unit}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
20
+ s.require_paths = ['lib']
21
+
22
+ s.add_development_dependency 'bundler', '~> 2.3.9'
23
+ s.add_development_dependency 'rake', '~> 10.0'
24
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :backup do
4
+ # Default to :app role
5
+ rake_roles = fetch(:rake_roles, :app)
6
+
7
+ desc 'create a backup of the server database'
8
+ task :create do
9
+ on roles(rake_roles) do
10
+ env = "RAILS_ENV=#{fetch(:stage)}"
11
+ # rubocop:disable Layout/LineLength
12
+ path_cmd = "PATH=$HOME/.rbenv/versions/#{RUBY_VERSION}/bin:$PATH"
13
+ # rubocop:enable Layout/LineLength
14
+ execute "cd #{release_path} && #{path_cmd} && #{env} BACKUPS_ENABLED=true bundle exec rake pg:dump"
15
+ end
16
+ end
17
+ desc 'pull latest database backups from server to local'
18
+ task :pull do
19
+ on roles(rake_roles) do
20
+ # rubocop:disable Layout/LineLength
21
+ execute "cd #{shared_path}/backups && tar -czf #{shared_path}/backups.tar.gz $(ls -lt | grep -E -i '.{0,}\.dump' | head -n 1 | awk '{print $9}')"
22
+ # rubocop:enable Layout/LineLength
23
+ download! "#{shared_path}/backups.tar.gz", 'backups.tar.gz'
24
+ execute "rm #{shared_path}/backups.tar.gz"
25
+ system 'tar -xzf backups.tar.gz'
26
+ system 'rm backups.tar.gz'
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
4
+ namespace :figaro_yml do
5
+ # Defaults to :app role
6
+ rake_roles = fetch(:rake_roles, :app)
7
+
8
+ desc 'get the figaro_yml file from the server'
9
+ task :get do
10
+ on roles(rake_roles) do
11
+
12
+ puts capture "cat #{shared_path}/config/application.yml"
13
+ end
14
+ end
15
+
16
+ desc 'compare and set the figaro_yml file on the server'
17
+ task :compare do
18
+ env = fetch(:stage)
19
+ # read local application.yml
20
+ local = File.read('config/application.yml')
21
+
22
+ # convert to hash
23
+ local_global_env = YAML.safe_load(local)
24
+
25
+ # split into stage and global
26
+ local_stage_env = local_global_env[env.to_s]
27
+ local_global_env.delete('staging')
28
+ local_global_env.delete('production')
29
+
30
+ on roles(rake_roles) do
31
+ # read remote application.yml
32
+ remote = capture("cat #{shared_path}/config/application.yml")
33
+
34
+ remote_global_env = YAML.safe_load(remote)
35
+ remote_stage_env = remote_global_env[env.to_s]
36
+ remote_global_env.delete(env.to_s)
37
+
38
+ puts "with command 'cap #{env} figaro_yml:setup', following variables will be overwritten:"
39
+ puts '--------------------------------------------------------------------------------'
40
+ result1 = compare_hashes(local_global_env, remote_global_env)
41
+ result2 = compare_hashes(local_stage_env, remote_stage_env)
42
+ if !result1.empty? || !result2.empty?
43
+ loop do
44
+ print 'Update remote application.yml? (y/N): '
45
+ input = $stdin.gets.strip.downcase
46
+ answer = (input.empty? ? 'N' : input).downcase.to_s
47
+
48
+ next unless %w(y n).include?(answer)
49
+
50
+ if answer == 'y'
51
+ puts 'Updating remote application.yml'
52
+ invoke 'figaro_yml:setup'
53
+ exit
54
+ end
55
+ break
56
+ end
57
+ puts 'remote application.yml not updated'
58
+ exit
59
+ end
60
+ puts 'remote application.yml is up to date'
61
+ end
62
+ end
63
+ def compare_hashes(hash1, hash2)
64
+ changes = false
65
+ local_server = hash1.to_a - hash2.to_a
66
+ server_local = hash2.to_a - hash1.to_a
67
+
68
+ [local_server + server_local].flatten(1).to_h.keys.each do |k|
69
+ new_value = hash1[k].to_s
70
+ new_value = new_value.empty? ? "nil" : new_value
71
+ old_value = hash2[k].to_s
72
+ old_value = old_value.empty? ? "nil" : old_value
73
+ if old_value != new_value
74
+ puts "#{k}: #{old_value} => #{new_value} \r\n"
75
+ changes = true
76
+ end
77
+ end
78
+ end
79
+ end
80
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,24 @@
1
+ namespace :invoke do
2
+
3
+ # Defalut to :app roles
4
+ rake_roles = fetch(:rake_roles, :app)
5
+
6
+ desc "Execute a rake task on a remote server (cap invoke:rake TASK=db:migrate)"
7
+ task :rake do
8
+ if ENV['TASK']
9
+ on roles(rake_roles) do
10
+ within current_path do
11
+ with rails_env: fetch(:rails_env) do
12
+ execute :rake, ENV['TASK']
13
+ end
14
+ end
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
+ end
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :logs do
4
+ # Default to :app role
5
+ rake_roles = fetch(:rake_roles, :app)
6
+ desc 'tail rails logs'
7
+ task :rails do
8
+ on roles(rake_roles) do
9
+ trap('SIGINT') do
10
+ puts "\nDisconnecting..."
11
+ exit
12
+ end
13
+ execute "tail -f #{shared_path}/log/#{fetch(:rails_env)}.log"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :whenever do
4
+ # Default to :app role
5
+ rake_roles = fetch(:rake_roles, :app)
6
+ desc 'show crontab'
7
+ task :show_crontab do
8
+ on roles(rake_roles) do
9
+ puts capture 'crontab -l'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capistrano/version'
4
+
5
+ if defined?(Capistrano::VERSION) && Gem::Version.new(Capistrano::VERSION).release >= Gem::Version.new('3.0.0')
6
+ load File.expand_path('capistrano/v3/tasks/whenever.rake', __dir__)
7
+ load File.expand_path('capistrano/v3/tasks/backup.rake', __dir__)
8
+ load File.expand_path('capistrano/v3/tasks/figaro_yml.rake', __dir__)
9
+ load File.expand_path('capistrano/v3/tasks/logs.rake', __dir__)
10
+ load File.expand_path('capistrano/v3/tasks/invoke.rake', __dir__)
11
+ else
12
+ puts 'Capistrano 3 is required to use this gem'
13
+ end
@@ -0,0 +1,29 @@
1
+ module Notification
2
+ class Api
3
+ attr_accessor :notification_type
4
+
5
+ def initialize(notification_type: ENV['NOTIFICATION_TYPE'])
6
+ self.notification_type = notification_type
7
+ end
8
+
9
+ def send_backup_notification(result, date, database, backup_path)
10
+ return if notification_type.nil?
11
+ case notification_type
12
+ when 'slack'
13
+ Slack.new.backup_notification(result, date, database, backup_path)
14
+ when 'webhook'
15
+ Webhook.new.backup_notification(result, date, database, backup_path)
16
+ end
17
+ end
18
+
19
+ def send_notification(message)
20
+ return if notification_type.nil?
21
+ case notification_type
22
+ when 'slack'
23
+ Slack.new.notify(message)
24
+ when 'webhook'
25
+ p 'webhook'
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,66 @@
1
+ module Notification
2
+ class Slack
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'net/https'
6
+ def initialize
7
+ @slack_secret = ENV['SLACK_SECRET']
8
+ @slack_channel = ENV['SLACK_CHANNEL']
9
+ @slack_base_url ='https://slack.com/api/'
10
+ end
11
+
12
+ def notify(message)
13
+ return if @slack_secret.nil? || @slack_channel.nil?
14
+ uri = URI.parse("#{@slack_base_url}chat.postMessage")
15
+ http = Net::HTTP.new(uri.host, uri.port)
16
+ http.use_ssl = true
17
+ request = Net::HTTP::Post.new(uri.request_uri, initHeader = {'Content-Type' =>'application/json', 'Authorization' => 'Bearer ' + @slack_secret})
18
+ request.body = {
19
+ channel: @slack_channel,
20
+ text: message
21
+ }.to_json
22
+ response = http.request(request)
23
+ puts response.body
24
+ end
25
+
26
+ def backup_notification(result, date, database, backup_path)
27
+ return if @slack_secret.nil? || @slack_channel.nil?
28
+ uri = URI.parse("#{@slack_base_url}chat.postMessage")
29
+ http = Net::HTTP.new(uri.host, uri.port)
30
+ http.use_ssl = true
31
+ request = Net::HTTP::Post.new(uri.request_uri, initHeader = {'Content-Type' =>'application/json', 'Authorization' => 'Bearer ' + @slack_secret})
32
+ message_one = "Backup of #{database} successfully finished at #{Time.now}"
33
+ message_two = "Backup path:\`#{backup_path}/#{database}_#{date}.dump\`"
34
+ data = {
35
+ channel: @slack_channel,
36
+ blocks: [
37
+ {
38
+ type: 'header',
39
+ text: {
40
+ type: 'plain_text',
41
+ text: ENV['DEFAULT_URL'] || "#{database} Backup",
42
+ emoji: true
43
+ }
44
+ },
45
+ {
46
+ type: 'section',
47
+ text: {
48
+ type: 'mrkdwn',
49
+ text: result ? "#{message_one}\n#{message_two}" : "Backup of #{database} failed at #{Time.now}"
50
+ }
51
+ }
52
+ ]
53
+ }
54
+ request.body = data.to_json
55
+ begin
56
+ response = JSON.parse(http.request(request).body)
57
+ if response['ok'] == false
58
+ raise Notification::Error, response['error']
59
+ end
60
+ response
61
+ rescue => e
62
+ puts "Slack error: \n\t#{e.message}"
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,40 @@
1
+ module Notification
2
+ class Webhook
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'net/https'
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
17
+
18
+ def backup_notification(result, date, database, backup_path)
19
+ return if @webhook_url.nil? || @secret.nil?
20
+
21
+ data = {
22
+ domain: ENV['DEFAULT_URL'] || "#{database} Backup",
23
+ backupPath: result ? backup_path : nil,
24
+ backupDate: date,
25
+ }.to_json
26
+
27
+ uri = URI.parse(@webhook_url)
28
+ https = Net::HTTP.new(uri.host, uri.port)
29
+ https.use_ssl = uri.scheme == "https"
30
+ request = Net::HTTP::Post.new(uri.path.empty? ? "/" : uri.path, initHeader = {'Content-Type' =>'application/json', 'x-hub-signature' => generate_signature("#{data}")})
31
+ request.body = "#{data}"
32
+ begin
33
+ response = https.request(request)
34
+ response.to_hash
35
+ rescue => e
36
+ puts "Webhook error: \n\t#{e.message}"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,7 @@
1
+ require 'capistrano/ops/notification/api'
2
+ require 'capistrano/ops/notification/slack'
3
+ require 'capistrano/ops/notification/webhook'
4
+
5
+ module Notification
6
+ class Error < StandardError; end
7
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capistrano/ops'
4
+ require 'rails'
5
+ module Capistrano
6
+ module Ops
7
+ class Railtie < Rails::Railtie
8
+ railtie_name :ops
9
+ rake_tasks do
10
+ path = File.expand_path(__dir__)
11
+ Dir.glob("#{path}/tasks/**/*.rake").each { |f| load f }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
4
+ namespace :pg do
5
+ default_backup_path = Rails.env.development? ? 'tmp/backups' : '../../shared/backups'
6
+ database = Rails.configuration.database_configuration[Rails.env]['database']
7
+ username = Rails.configuration.database_configuration[Rails.env]['username']
8
+ password = Rails.configuration.database_configuration[Rails.env]['password']
9
+ hostname = Rails.configuration.database_configuration[Rails.env]['host']
10
+ portnumber = Rails.configuration.database_configuration[Rails.env]['port']
11
+ backup_path = Rails.root.join(default_backup_path).to_s
12
+ backups_enabled = Rails.env.production? || ENV['BACKUPS_ENABLED'] == 'true'
13
+
14
+ task :dump do
15
+ api = Notification::Api.new
16
+ date = Time.now.to_i
17
+ user = username.present? ? " -U #{username}" : ''
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 -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)
28
+ end
29
+ if backups_enabled
30
+ # rubocop:disable Layout/LineLength
31
+ p result ? "Backup created: #{backup_path}/#{database}_#{date}.dump" : "Backup failed, created empty file at #{backup_path}/#{database}_#{date}.dump"
32
+ system "rm #{backup_path}/#{database}_#{date}.dump" unless result
33
+ # rubocop:enable Layout/LineLength
34
+ else
35
+ p 'dump: Backups are disabled'
36
+ end
37
+ end
38
+ end
39
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :pg do
4
+ default_backup_path = Rails.env.development? ? 'tmp/backups' : '../../shared/backups'
5
+ database = Rails.configuration.database_configuration[Rails.env]['database']
6
+
7
+ backup_path = Rails.root.join(default_backup_path).to_s
8
+ backups_enabled = Rails.env.production? || ENV['BACKUPS_ENABLED'] == 'true'
9
+
10
+ task :remove_old_dumps do
11
+ bash_regex = "'#{database}.{0,}\.dump'"
12
+ total_backups_no = ENV['NUMBER_OF_BACKUPS'] || 1
13
+ # rubocop:disable Layout/LineLength
14
+ cmd = "cd #{backup_path} && ls -lt | grep -E -i #{bash_regex} | tail -n +#{total_backups_no.to_i + 1} | awk '{print $9}'|xargs rm -rf"
15
+ # rubocop:enable Layout/LineLength
16
+ system(cmd) if backups_enabled
17
+ p backups_enabled ? 'Old backups removed' : 'remove_old_dumps: Backups are disabled'
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ module Capistrano
3
+ module Ops
4
+ VERSION = '0.1.0'
5
+ end
6
+ end
@@ -0,0 +1,24 @@
1
+
2
+
3
+ module Capistrano
4
+ module Ops
5
+ require 'capistrano/ops/notification'
6
+ require 'capistrano/ops/railtie' if defined?(Rails)
7
+ require 'capistrano/ops/capistrano' if defined?(Capistrano::VERSION)
8
+ def self.path
9
+ Dir.pwd
10
+ end
11
+
12
+ def self.bin_rails?
13
+ File.exist?(File.join(path, 'bin', 'rails'))
14
+ end
15
+
16
+ def self.script_rails?
17
+ File.exist?(File.join(path, 'script', 'rails'))
18
+ end
19
+
20
+ def self.bundler?
21
+ File.exist?(File.join(path, 'Gemfile'))
22
+ end
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: capistrano-ops
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Florian Crusius
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-03-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.3.9
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 2.3.9
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description: A collection of devops tasks for rails applications
42
+ email:
43
+ - florian@zauberware.com
44
+ executables:
45
+ - console
46
+ - setup
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".gitignore"
51
+ - ".travis.yml"
52
+ - Gemfile
53
+ - LICENSE.txt
54
+ - README.md
55
+ - Rakefile
56
+ - bin/console
57
+ - bin/setup
58
+ - capistrano-ops.gemspec
59
+ - lib/capistrano/ops.rb
60
+ - lib/capistrano/ops/capistrano.rb
61
+ - lib/capistrano/ops/capistrano/v3/tasks/backup.rake
62
+ - lib/capistrano/ops/capistrano/v3/tasks/figaro_yml.rake
63
+ - lib/capistrano/ops/capistrano/v3/tasks/invoke.rake
64
+ - lib/capistrano/ops/capistrano/v3/tasks/logs.rake
65
+ - lib/capistrano/ops/capistrano/v3/tasks/whenever.rake
66
+ - lib/capistrano/ops/notification.rb
67
+ - lib/capistrano/ops/notification/api.rb
68
+ - lib/capistrano/ops/notification/slack.rb
69
+ - lib/capistrano/ops/notification/webhook.rb
70
+ - lib/capistrano/ops/railtie.rb
71
+ - lib/capistrano/ops/tasks/pg/dump.rake
72
+ - lib/capistrano/ops/tasks/pg/remove_old_dumps.rake
73
+ - lib/capistrano/ops/version.rb
74
+ homepage: https://github.com/zauberware/capistrano-ops
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubygems_version: 3.3.26
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: devops tasks for rails applications
97
+ test_files: []