From 30dbec3354997ff92e94913d76543bf3232c1c20 Mon Sep 17 00:00:00 2001 From: Andrey Subbota Date: Fri, 6 Jan 2017 00:30:45 +0400 Subject: [PATCH 1/3] Change gem structure --- capistrano-db-tasks.gemspec | 2 +- lib/capistrano-db-tasks.rb | 4 +- lib/capistrano-db-tasks/dbtasks.rb | 124 ------------------ lib/capistrano/db_tasks.rb | 9 ++ .../db_tasks}/asset.rb | 0 .../db_tasks}/compressors/base.rb | 0 .../db_tasks}/compressors/bzip2.rb | 0 .../db_tasks}/compressors/gzip.rb | 0 .../db_tasks}/database.rb | 0 lib/capistrano/db_tasks/tasks.rb | 4 + .../db_tasks}/util.rb | 0 .../db_tasks}/version.rb | 0 lib/capistrano/tasks/app.cap | 48 +++++++ lib/capistrano/tasks/assets.cap | 38 ++++++ lib/capistrano/tasks/database.cap | 36 +++++ lib/capistrano/tasks/db_tasks.cap | 5 + 16 files changed, 143 insertions(+), 127 deletions(-) delete mode 100644 lib/capistrano-db-tasks/dbtasks.rb create mode 100644 lib/capistrano/db_tasks.rb rename lib/{capistrano-db-tasks => capistrano/db_tasks}/asset.rb (100%) rename lib/{capistrano-db-tasks => capistrano/db_tasks}/compressors/base.rb (100%) rename lib/{capistrano-db-tasks => capistrano/db_tasks}/compressors/bzip2.rb (100%) rename lib/{capistrano-db-tasks => capistrano/db_tasks}/compressors/gzip.rb (100%) rename lib/{capistrano-db-tasks => capistrano/db_tasks}/database.rb (100%) create mode 100644 lib/capistrano/db_tasks/tasks.rb rename lib/{capistrano-db-tasks => capistrano/db_tasks}/util.rb (100%) rename lib/{capistrano-db-tasks => capistrano/db_tasks}/version.rb (100%) create mode 100644 lib/capistrano/tasks/app.cap create mode 100644 lib/capistrano/tasks/assets.cap create mode 100644 lib/capistrano/tasks/database.cap create mode 100644 lib/capistrano/tasks/db_tasks.cap diff --git a/capistrano-db-tasks.gemspec b/capistrano-db-tasks.gemspec index 802b846..9a63916 100644 --- a/capistrano-db-tasks.gemspec +++ b/capistrano-db-tasks.gemspec @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require "capistrano-db-tasks/version" +require "capistrano/db_tasks/version" Gem::Specification.new do |gem| gem.name = "capistrano-db-tasks" diff --git a/lib/capistrano-db-tasks.rb b/lib/capistrano-db-tasks.rb index ba57349..20adc09 100644 --- a/lib/capistrano-db-tasks.rb +++ b/lib/capistrano-db-tasks.rb @@ -1,2 +1,2 @@ -require "capistrano" -require File.expand_path("#{File.dirname(__FILE__)}/capistrano-db-tasks/dbtasks") +$stderr.puts %Q(Please, change \"require 'capistrano-db-tasks'\" to \"require 'capistrano/db_tasks'" in your Capfile.) +require 'capistrano/db_tasks' diff --git a/lib/capistrano-db-tasks/dbtasks.rb b/lib/capistrano-db-tasks/dbtasks.rb deleted file mode 100644 index ab7cf21..0000000 --- a/lib/capistrano-db-tasks/dbtasks.rb +++ /dev/null @@ -1,124 +0,0 @@ -require File.expand_path("#{File.dirname(__FILE__)}/util") -require File.expand_path("#{File.dirname(__FILE__)}/database") -require File.expand_path("#{File.dirname(__FILE__)}/asset") -require File.expand_path("#{File.dirname(__FILE__)}/compressors/base") -require File.expand_path("#{File.dirname(__FILE__)}/compressors/bzip2") -require File.expand_path("#{File.dirname(__FILE__)}/compressors/gzip") - -set :local_rails_env, ENV['RAILS_ENV'] || 'development' unless fetch(:local_rails_env) -set :rails_env, fetch(:stage) || 'production' unless fetch(:rails_env) -set :db_local_clean, false unless fetch(:db_local_clean) -set :assets_dir, 'system' unless fetch(:assets_dir) -set :local_assets_dir, 'public' unless fetch(:local_assets_dir) -set :skip_data_sync_confirm, ENV['SKIP_DATA_SYNC_CONFIRM'].to_s.casecmp('true').zero? -set :disallow_pushing, false unless fetch(:disallow_pushing) -set :compressor, :gzip unless fetch(:compressor) - -namespace :capistrano_db_tasks do - task :check_can_push do - raise "pushing is disabled, set disallow_pushing to false to carry out this operation" if fetch(:disallow_pushing) - end -end - -namespace :db do - namespace :remote do - desc 'Synchronize your remote database using local database data' - task :sync => 'capistrano_db_tasks:check_can_push' do - on roles(:db) do - if fetch(:skip_data_sync_confirm) || Util.prompt('Are you sure you want to REPLACE THE REMOTE DATABASE with local database') - Database.local_to_remote(self) - end - end - end - end - - namespace :local do - desc 'Synchronize your local database using remote database data' - task :sync do - on roles(:db) do - puts "Local database: #{Database::Local.new(self).database}" - if fetch(:skip_data_sync_confirm) || Util.prompt('Are you sure you want to erase your local database with server database') - Database.remote_to_local(self) - end - end - end - end - - desc 'Synchronize your local database using remote database data' - task :pull => "db:local:sync" - - desc 'Synchronize your remote database using local database data' - task :push => "db:remote:sync" -end - -namespace :assets do - namespace :remote do - desc 'Synchronize your remote assets using local assets' - task :sync => 'capistrano_db_tasks:check_can_push' do - on roles(:app) do - puts "Assets directories: #{fetch(:assets_dir)}" - if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to erase your server assets with local assets") - Asset.local_to_remote(self) - end - end - end - end - - namespace :local do - desc 'Synchronize your local assets using remote assets' - task :sync do - on roles(:app) do - puts "Assets directories: #{fetch(:local_assets_dir)}" - if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to erase your local assets with server assets") - Asset.remote_to_local(self) - end - end - end - end - - desc 'Synchronize your local assets using remote assets' - task :pull => "assets:local:sync" - - desc 'Synchronize your remote assets using local assets' - task :push => "assets:remote:sync" -end - -namespace :app do - namespace :remote do - desc 'Synchronize your remote assets AND database using local assets and database' - task :sync => 'capistrano_db_tasks:check_can_push' do - if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to REPLACE THE REMOTE DATABASE AND your remote assets with local database and assets(#{fetch(:assets_dir)})") - on roles(:db) do - Database.local_to_remote(self) - end - - on roles(:app) do - Asset.local_to_remote(self) - end - end - end - end - - namespace :local do - desc 'Synchronize your local assets AND database using remote assets and database' - task :sync do - puts "Local database : #{Database::Local.new(self).database}" - puts "Assets directories : #{fetch(:local_assets_dir)}" - if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to erase your local database AND your local assets with server database and assets(#{fetch(:assets_dir)})") - on roles(:db) do - Database.remote_to_local(self) - end - - on roles(:app) do - Asset.remote_to_local(self) - end - end - end - end - - desc 'Synchronize your local assets AND database using remote assets and database' - task :pull => "app:local:sync" - - desc 'Synchronize your remote assets AND database using local assets and database' - task :push => "app:remote:sync" -end diff --git a/lib/capistrano/db_tasks.rb b/lib/capistrano/db_tasks.rb new file mode 100644 index 0000000..c604945 --- /dev/null +++ b/lib/capistrano/db_tasks.rb @@ -0,0 +1,9 @@ +require_relative 'db_tasks/util' +require_relative 'db_tasks/database' +require_relative 'db_tasks/asset' + +require_relative 'db_tasks/compressors/base' +require_relative 'db_tasks/compressors/bzip2' +require_relative 'db_tasks/compressors/gzip' + +require_relative 'db_tasks/tasks' diff --git a/lib/capistrano-db-tasks/asset.rb b/lib/capistrano/db_tasks/asset.rb similarity index 100% rename from lib/capistrano-db-tasks/asset.rb rename to lib/capistrano/db_tasks/asset.rb diff --git a/lib/capistrano-db-tasks/compressors/base.rb b/lib/capistrano/db_tasks/compressors/base.rb similarity index 100% rename from lib/capistrano-db-tasks/compressors/base.rb rename to lib/capistrano/db_tasks/compressors/base.rb diff --git a/lib/capistrano-db-tasks/compressors/bzip2.rb b/lib/capistrano/db_tasks/compressors/bzip2.rb similarity index 100% rename from lib/capistrano-db-tasks/compressors/bzip2.rb rename to lib/capistrano/db_tasks/compressors/bzip2.rb diff --git a/lib/capistrano-db-tasks/compressors/gzip.rb b/lib/capistrano/db_tasks/compressors/gzip.rb similarity index 100% rename from lib/capistrano-db-tasks/compressors/gzip.rb rename to lib/capistrano/db_tasks/compressors/gzip.rb diff --git a/lib/capistrano-db-tasks/database.rb b/lib/capistrano/db_tasks/database.rb similarity index 100% rename from lib/capistrano-db-tasks/database.rb rename to lib/capistrano/db_tasks/database.rb diff --git a/lib/capistrano/db_tasks/tasks.rb b/lib/capistrano/db_tasks/tasks.rb new file mode 100644 index 0000000..333d250 --- /dev/null +++ b/lib/capistrano/db_tasks/tasks.rb @@ -0,0 +1,4 @@ +load File.expand_path("../../tasks/db_tasks.cap", __FILE__) +load File.expand_path("../../tasks/app.cap", __FILE__) +load File.expand_path("../../tasks/database.cap", __FILE__) +load File.expand_path("../../tasks/assets.cap", __FILE__) diff --git a/lib/capistrano-db-tasks/util.rb b/lib/capistrano/db_tasks/util.rb similarity index 100% rename from lib/capistrano-db-tasks/util.rb rename to lib/capistrano/db_tasks/util.rb diff --git a/lib/capistrano-db-tasks/version.rb b/lib/capistrano/db_tasks/version.rb similarity index 100% rename from lib/capistrano-db-tasks/version.rb rename to lib/capistrano/db_tasks/version.rb diff --git a/lib/capistrano/tasks/app.cap b/lib/capistrano/tasks/app.cap new file mode 100644 index 0000000..8053d43 --- /dev/null +++ b/lib/capistrano/tasks/app.cap @@ -0,0 +1,48 @@ +namespace :app do + namespace :remote do + desc "Synchronize your remote assets AND database using local assets and database" + task sync: "capistrano_db_tasks:check_can_push" do + if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to REPLACE THE REMOTE DATABASE AND your remote assets with local database and assets(#{fetch(:assets_dir)})") + on roles(:db) do + Database.local_to_remote(self) + end + + on roles(:app) do + Asset.local_to_remote(self) + end + end + end + end + + namespace :local do + desc "Synchronize your local assets AND database using remote assets and database" + task :sync do + puts "Local database : #{Database::Local.new(self).database}" + puts "Assets directories : #{fetch(:local_assets_dir)}" + if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to erase your local database AND your local assets with server database and assets(#{fetch(:assets_dir)})") + on roles(:db) do + Database.remote_to_local(self) + end + + on roles(:app) do + Asset.remote_to_local(self) + end + end + end + end + + desc "Synchronize your local assets AND database using remote assets and database" + task pull: "app:local:sync" + + desc "Synchronize your remote assets AND database using local assets and database" + task push: "app:remote:sync" +end + +namespace :load do + task :defaults do + set :local_rails_env, fetch(:local_rails_env, ENV["RAILS_ENV"] || "development") + set :skip_data_sync_confirm, fetch(:skip_data_sync_confirm, ENV["SKIP_DATA_SYNC_CONFIRM"].to_s.casecmp("true").zero?) + set :disallow_pushing, fetch(:disallow_pushing, false) + set :compressor, fetch(:compressor, :gzip) + end +end diff --git a/lib/capistrano/tasks/assets.cap b/lib/capistrano/tasks/assets.cap new file mode 100644 index 0000000..176f328 --- /dev/null +++ b/lib/capistrano/tasks/assets.cap @@ -0,0 +1,38 @@ +namespace :assets do + namespace :remote do + desc "Synchronize your remote assets using local assets" + task sync: "capistrano_db_tasks:check_can_push" do + on roles(:app) do + puts "Assets directories: #{fetch(:assets_dir)}" + if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to erase your server assets with local assets") + Asset.local_to_remote(self) + end + end + end + end + + namespace :local do + desc "Synchronize your local assets using remote assets" + task :sync do + on roles(:app) do + puts "Assets directories: #{fetch(:local_assets_dir)}" + if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to erase your local assets with server assets") + Asset.remote_to_local(self) + end + end + end + end + + desc "Synchronize your local assets using remote assets" + task pull: "assets:local:sync" + + desc "Synchronize your remote assets using local assets" + task push: "assets:remote:sync" +end + +namespace :load do + task :defaults do + set :assets_dir, fetch(:assets_dir, "system") + set :local_assets_dir, fetch(:local_assets_dir, "public") + end +end diff --git a/lib/capistrano/tasks/database.cap b/lib/capistrano/tasks/database.cap new file mode 100644 index 0000000..6d88653 --- /dev/null +++ b/lib/capistrano/tasks/database.cap @@ -0,0 +1,36 @@ +namespace :db do + namespace :remote do + desc "Synchronize your remote database using local database data" + task sync: "capistrano_db_tasks:check_can_push" do + on roles(:db) do + if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to REPLACE THE REMOTE DATABASE with local database") + Database.local_to_remote(self) + end + end + end + end + + namespace :local do + desc "Synchronize your local database using remote database data" + task :sync do + on roles(:db) do + puts "Local database: #{Database::Local.new(self).database}" + if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to erase your local database with server database") + Database.remote_to_local(self) + end + end + end + end + + desc "Synchronize your local database using remote database data" + task pull: "db:local:sync" + + desc "Synchronize your remote database using local database data" + task push: "db:remote:sync" +end + +namespace :load do + task :defaults do + set :db_local_clean, fetch(:db_local_clean, false) + end +end diff --git a/lib/capistrano/tasks/db_tasks.cap b/lib/capistrano/tasks/db_tasks.cap new file mode 100644 index 0000000..574bfac --- /dev/null +++ b/lib/capistrano/tasks/db_tasks.cap @@ -0,0 +1,5 @@ +namespace :capistrano_db_tasks do + task :check_can_push do + raise "pushing is disabled, set disallow_pushing to false to carry out this operation" if fetch(:disallow_pushing) + end +end From 2fe3f1a0632c25e208e84e457a40c8dc93708a3f Mon Sep 17 00:00:00 2001 From: Andrey Subbota Date: Fri, 6 Jan 2017 00:31:58 +0400 Subject: [PATCH 2/3] Update gem module nesting --- capistrano-db-tasks.gemspec | 2 +- lib/capistrano/db_tasks.rb | 2 +- lib/capistrano/db_tasks/asset.rb | 33 -- lib/capistrano/db_tasks/assets.rb | 37 ++ lib/capistrano/db_tasks/compressors/base.rb | 8 +- lib/capistrano/db_tasks/compressors/bzip2.rb | 60 +-- lib/capistrano/db_tasks/compressors/gzip.rb | 62 +-- lib/capistrano/db_tasks/database.rb | 378 ++++++++++--------- lib/capistrano/db_tasks/util.rb | 12 +- lib/capistrano/db_tasks/version.rb | 6 +- lib/capistrano/tasks/app.cap | 12 +- lib/capistrano/tasks/database.cap | 9 +- 12 files changed, 323 insertions(+), 298 deletions(-) delete mode 100644 lib/capistrano/db_tasks/asset.rb create mode 100644 lib/capistrano/db_tasks/assets.rb diff --git a/capistrano-db-tasks.gemspec b/capistrano-db-tasks.gemspec index 9a63916..92b0a68 100644 --- a/capistrano-db-tasks.gemspec +++ b/capistrano-db-tasks.gemspec @@ -5,7 +5,7 @@ require "capistrano/db_tasks/version" Gem::Specification.new do |gem| gem.name = "capistrano-db-tasks" - gem.version = CapistranoDbTasks::VERSION + gem.version = Capistrano::DbTasks::VERSION gem.authors = ["Sebastien Gruhier"] gem.email = ["sebastien.gruhier@xilinus.com"] gem.homepage = "https://github.com/sgruhier/capistrano-db-tasks" diff --git a/lib/capistrano/db_tasks.rb b/lib/capistrano/db_tasks.rb index c604945..fd488ac 100644 --- a/lib/capistrano/db_tasks.rb +++ b/lib/capistrano/db_tasks.rb @@ -1,6 +1,6 @@ require_relative 'db_tasks/util' require_relative 'db_tasks/database' -require_relative 'db_tasks/asset' +require_relative 'db_tasks/assets' require_relative 'db_tasks/compressors/base' require_relative 'db_tasks/compressors/bzip2' diff --git a/lib/capistrano/db_tasks/asset.rb b/lib/capistrano/db_tasks/asset.rb deleted file mode 100644 index 5b7e93c..0000000 --- a/lib/capistrano/db_tasks/asset.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Asset - extend self - - def remote_to_local(cap) - servers = Capistrano::Configuration.env.send(:servers) - server = servers.detect { |s| s.roles.include?(:app) } - port = server.netssh_options[:port] || 22 - user = server.netssh_options[:user] || server.properties.fetch(:user) - dirs = [cap.fetch(:assets_dir)].flatten - local_dirs = [cap.fetch(:local_assets_dir)].flatten - - dirs.each_index do |idx| - system("rsync -a --del -L -K -vv --progress --rsh='ssh -p #{port}' #{user}@#{server}:#{cap.current_path}/#{dirs[idx]} #{local_dirs[idx]}") - end - end - - def local_to_remote(cap) - servers = Capistrano::Configuration.env.send(:servers) - server = servers.detect { |s| s.roles.include?(:app) } - port = server.netssh_options[:port] || 22 - user = server.netssh_options[:user] || server.properties.fetch(:user) - dirs = [cap.fetch(:assets_dir)].flatten - local_dirs = [cap.fetch(:local_assets_dir)].flatten - - dirs.each_index do |idx| - system("rsync -a --del -L -K -vv --progress --rsh='ssh -p #{port}' ./#{dirs[idx]} #{user}@#{server}:#{cap.current_path}/#{local_dirs[idx]}") - end - end - - def to_string(cap) - [cap.fetch(:assets_dir)].flatten.join(" ") - end -end diff --git a/lib/capistrano/db_tasks/assets.rb b/lib/capistrano/db_tasks/assets.rb new file mode 100644 index 0000000..bd530f7 --- /dev/null +++ b/lib/capistrano/db_tasks/assets.rb @@ -0,0 +1,37 @@ +module Capistrano + module DbTasks + module Assets + extend self + + def remote_to_local(cap) + servers = Capistrano::Configuration.env.send(:servers) + server = servers.detect { |s| s.roles.include?(:app) } + port = server.netssh_options[:port] || 22 + user = server.netssh_options[:user] || server.properties.fetch(:user) + dirs = [cap.fetch(:assets_dir)].flatten + local_dirs = [cap.fetch(:local_assets_dir)].flatten + + dirs.each_index do |idx| + system("rsync -a --del -L -K -vv --progress --rsh='ssh -p #{port}' #{user}@#{server}:#{cap.current_path}/#{dirs[idx]} #{local_dirs[idx]}") + end + end + + def local_to_remote(cap) + servers = Capistrano::Configuration.env.send(:servers) + server = servers.detect { |s| s.roles.include?(:app) } + port = server.netssh_options[:port] || 22 + user = server.netssh_options[:user] || server.properties.fetch(:user) + dirs = [cap.fetch(:assets_dir)].flatten + local_dirs = [cap.fetch(:local_assets_dir)].flatten + + dirs.each_index do |idx| + system("rsync -a --del -L -K -vv --progress --rsh='ssh -p #{port}' ./#{dirs[idx]} #{user}@#{server}:#{cap.current_path}/#{local_dirs[idx]}") + end + end + + def to_string(cap) + [cap.fetch(:assets_dir)].flatten.join(" ") + end + end + end +end diff --git a/lib/capistrano/db_tasks/compressors/base.rb b/lib/capistrano/db_tasks/compressors/base.rb index 90b6bcb..94a6446 100644 --- a/lib/capistrano/db_tasks/compressors/base.rb +++ b/lib/capistrano/db_tasks/compressors/base.rb @@ -1,3 +1,7 @@ -module Compressors - class Base; end +module Capistrano + module DbTasks + module Compressors + class Base; end + end + end end diff --git a/lib/capistrano/db_tasks/compressors/bzip2.rb b/lib/capistrano/db_tasks/compressors/bzip2.rb index 0bce61e..6323559 100644 --- a/lib/capistrano/db_tasks/compressors/bzip2.rb +++ b/lib/capistrano/db_tasks/compressors/bzip2.rb @@ -1,36 +1,40 @@ -module Compressors - class Bzip2 < Base - class << self - def file_extension - "bz2" - end +module Capistrano + module DbTasks + module Compressors + class Bzip2 < Base + class << self + def file_extension + "bz2" + end - def compress(from, to = nil) - to = case to - when "-" - "-c --stdout" - when nil - "" - else - "-c --stdout > #{to}" - end + def compress(from, to = nil) + to = case to + when "-" + "-c --stdout" + when nil + "" + else + "-c --stdout > #{to}" + end - "bzip2 #{from} #{to}" - end + "bzip2 #{from} #{to}" + end - def decompress(from, to = nil) - from = "-f #{from}" unless from == "-" + def decompress(from, to = nil) + from = "-f #{from}" unless from == "-" - to = case to - when "-" - "-c --stdout" - when nil - "" - else - "-c --stdout > #{to}" - end + to = case to + when "-" + "-c --stdout" + when nil + "" + else + "-c --stdout > #{to}" + end - "bunzip2 -f #{from} #{to}" + "bunzip2 -f #{from} #{to}" + end + end end end end diff --git a/lib/capistrano/db_tasks/compressors/gzip.rb b/lib/capistrano/db_tasks/compressors/gzip.rb index 94861aa..a389df2 100644 --- a/lib/capistrano/db_tasks/compressors/gzip.rb +++ b/lib/capistrano/db_tasks/compressors/gzip.rb @@ -1,36 +1,40 @@ -module Compressors - class Gzip < Base - class << self - def file_extension - "gz" - end +module Capistrano + module DbTasks + module Compressors + class Gzip < Base + class << self + def file_extension + "gz" + end - def compress(from, to = nil) - from = from == :stdin ? "-" : from - to = case to - when '-' - "-c --stdout" - when nil - "" - else - "-c --stdout > #{to}" - end + def compress(from, to = nil) + from = from == :stdin ? "-" : from + to = case to + when '-' + "-c --stdout" + when nil + "" + else + "-c --stdout > #{to}" + end - "gzip #{from} #{to}" - end + "gzip #{from} #{to}" + end - def decompress(from, to = nil) - from = from == :stdin ? "-" : from - to = case to - when :stdout - "-c --stdout" - when nil - "" - else - "-c --stdout > #{to}" - end + def decompress(from, to = nil) + from = from == :stdin ? "-" : from + to = case to + when :stdout + "-c --stdout" + when nil + "" + else + "-c --stdout > #{to}" + end - "gzip -d #{from} #{to}" + "gzip -d #{from} #{to}" + end + end end end end diff --git a/lib/capistrano/db_tasks/database.rb b/lib/capistrano/db_tasks/database.rb index 10ac9c0..bcbac94 100644 --- a/lib/capistrano/db_tasks/database.rb +++ b/lib/capistrano/db_tasks/database.rb @@ -1,230 +1,234 @@ -module Database - class Base - DBCONFIG_BEGIN_FLAG = "__CAPISTRANODB_CONFIG_BEGIN_FLAG__".freeze - DBCONFIG_END_FLAG = "__CAPISTRANODB_CONFIG_END_FLAG__".freeze +module Capistrano + module DbTasks + module Database + class Base + DBCONFIG_BEGIN_FLAG = "__CAPISTRANODB_CONFIG_BEGIN_FLAG__".freeze + DBCONFIG_END_FLAG = "__CAPISTRANODB_CONFIG_END_FLAG__".freeze - attr_accessor :config, :output_file + attr_accessor :config, :output_file - def initialize(cap_instance) - @cap = cap_instance - end + def initialize(cap_instance) + @cap = cap_instance + end - def mysql? - @config['adapter'] =~ /^mysql/ - end + def mysql? + @config['adapter'] =~ /^mysql/ + end - def postgresql? - %w(postgresql pg postgis).include? @config['adapter'] - end + def postgresql? + %w(postgresql pg postgis).include? @config['adapter'] + end - def credentials - credential_params = "" - username = @config['username'] || @config['user'] - - if mysql? - credential_params << " -u #{username} " if username - credential_params << " -p'#{@config['password']}' " if @config['password'] - credential_params << " -h #{@config['host']} " if @config['host'] - credential_params << " -S #{@config['socket']} " if @config['socket'] - credential_params << " -P #{@config['port']} " if @config['port'] - elsif postgresql? - credential_params << " -U #{username} " if username - credential_params << " -h #{@config['host']} " if @config['host'] - credential_params << " -p #{@config['port']} " if @config['port'] - end + def credentials + credential_params = "" + username = @config['username'] || @config['user'] + + if mysql? + credential_params << " -u #{username} " if username + credential_params << " -p'#{@config['password']}' " if @config['password'] + credential_params << " -h #{@config['host']} " if @config['host'] + credential_params << " -S #{@config['socket']} " if @config['socket'] + credential_params << " -P #{@config['port']} " if @config['port'] + elsif postgresql? + credential_params << " -U #{username} " if username + credential_params << " -h #{@config['host']} " if @config['host'] + credential_params << " -p #{@config['port']} " if @config['port'] + end + + credential_params + end - credential_params - end + def database + @config['database'] + end - def database - @config['database'] - end + def current_time + Time.now.strftime("%Y-%m-%d-%H%M%S") + end - def current_time - Time.now.strftime("%Y-%m-%d-%H%M%S") - end + def output_file + @output_file ||= "#{database}_#{current_time}.sql.#{compressor.file_extension}" + end - def output_file - @output_file ||= "#{database}_#{current_time}.sql.#{compressor.file_extension}" - end + def compressor + @compressor ||= begin + compressor_klass = @cap.fetch(:compressor).to_s.split('_').collect(&:capitalize).join + klass = Object.module_eval("::Capistrano::DbTasks::Compressors::#{compressor_klass}", __FILE__, __LINE__) + klass + end + end - def compressor - @compressor ||= begin - compressor_klass = @cap.fetch(:compressor).to_s.split('_').collect(&:capitalize).join - klass = Object.module_eval("::Compressors::#{compressor_klass}", __FILE__, __LINE__) - klass - end - end + private - private + def pgpass + @config['password'] ? "PGPASSWORD='#{@config['password']}'" : "" + end - def pgpass - @config['password'] ? "PGPASSWORD='#{@config['password']}'" : "" - end + def dump_cmd + if mysql? + "mysqldump #{credentials} #{database} #{dump_cmd_opts}" + elsif postgresql? + "#{pgpass} pg_dump #{credentials} #{database} #{dump_cmd_opts}" + end + end - def dump_cmd - if mysql? - "mysqldump #{credentials} #{database} #{dump_cmd_opts}" - elsif postgresql? - "#{pgpass} pg_dump #{credentials} #{database} #{dump_cmd_opts}" - end - end + def import_cmd(file) + if mysql? + "mysql #{credentials} -D #{database} < #{file}" + elsif postgresql? + terminate_connection_sql = "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '#{database}' AND pid <> pg_backend_pid();" + "#{pgpass} psql -c \"#{terminate_connection_sql};\" #{credentials} #{database}; #{pgpass} dropdb #{credentials} #{database}; #{pgpass} createdb #{credentials} #{database}; #{pgpass} psql #{credentials} -d #{database} < #{file}" + end + end - def import_cmd(file) - if mysql? - "mysql #{credentials} -D #{database} < #{file}" - elsif postgresql? - terminate_connection_sql = "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '#{database}' AND pid <> pg_backend_pid();" - "#{pgpass} psql -c \"#{terminate_connection_sql};\" #{credentials} #{database}; #{pgpass} dropdb #{credentials} #{database}; #{pgpass} createdb #{credentials} #{database}; #{pgpass} psql #{credentials} -d #{database} < #{file}" - end - end + def dump_cmd_opts + if mysql? + "--lock-tables=false #{dump_cmd_ignore_tables_opts} #{dump_cmd_ignore_data_tables_opts}" + elsif postgresql? + "--no-acl --no-owner #{dump_cmd_ignore_tables_opts} #{dump_cmd_ignore_data_tables_opts}" + end + end - def dump_cmd_opts - if mysql? - "--lock-tables=false #{dump_cmd_ignore_tables_opts} #{dump_cmd_ignore_data_tables_opts}" - elsif postgresql? - "--no-acl --no-owner #{dump_cmd_ignore_tables_opts} #{dump_cmd_ignore_data_tables_opts}" - end - end + def dump_cmd_ignore_tables_opts + ignore_tables = @cap.fetch(:db_ignore_tables, []) + if mysql? + ignore_tables.map { |t| "--ignore-table=#{database}.#{t}" }.join(" ") + elsif postgresql? + ignore_tables.map { |t| "--exclude-table=#{t}" }.join(" ") + end + end - def dump_cmd_ignore_tables_opts - ignore_tables = @cap.fetch(:db_ignore_tables, []) - if mysql? - ignore_tables.map { |t| "--ignore-table=#{database}.#{t}" }.join(" ") - elsif postgresql? - ignore_tables.map { |t| "--exclude-table=#{t}" }.join(" ") + def dump_cmd_ignore_data_tables_opts + ignore_tables = @cap.fetch(:db_ignore_data_tables, []) + ignore_tables.map { |t| "--exclude-table-data=#{t}" }.join(" ") if postgresql? + end end - end - def dump_cmd_ignore_data_tables_opts - ignore_tables = @cap.fetch(:db_ignore_data_tables, []) - ignore_tables.map { |t| "--exclude-table-data=#{t}" }.join(" ") if postgresql? - end - end - - class Remote < Base - def initialize(cap_instance) - super(cap_instance) - puts "Loading remote database config" - @cap.within @cap.current_path do - @cap.with rails_env: @cap.fetch(:rails_env) do - dirty_config_content = @cap.capture(:rails, "runner \"puts '#{DBCONFIG_BEGIN_FLAG}' + ActiveRecord::Base.connection.instance_variable_get(:@config).to_yaml + '#{DBCONFIG_END_FLAG}'\"", '2>/dev/null') - # Remove all warnings, errors and artefacts produced by bunlder, rails and other useful tools - config_content = dirty_config_content.match(/#{DBCONFIG_BEGIN_FLAG}(.*?)#{DBCONFIG_END_FLAG}/m)[1] - @config = YAML.load(config_content).each_with_object({}) { |(k, v), h| h[k.to_s] = v } + class Remote < Base + def initialize(cap_instance) + super(cap_instance) + puts "Loading remote database config" + @cap.within @cap.current_path do + @cap.with rails_env: @cap.fetch(:rails_env) do + dirty_config_content = @cap.capture(:rails, "runner \"puts '#{DBCONFIG_BEGIN_FLAG}' + ActiveRecord::Base.connection.instance_variable_get(:@config).to_yaml + '#{DBCONFIG_END_FLAG}'\"", '2>/dev/null') + # Remove all warnings, errors and artefacts produced by bunlder, rails and other useful tools + config_content = dirty_config_content.match(/#{DBCONFIG_BEGIN_FLAG}(.*?)#{DBCONFIG_END_FLAG}/m)[1] + @config = YAML.load(config_content).each_with_object({}) { |(k, v), h| h[k.to_s] = v } + end + end end - end - end - def dump - @cap.execute "cd #{@cap.current_path} && #{dump_cmd} | #{compressor.compress('-', db_dump_file_path)}" - self - end + def dump + @cap.execute "cd #{@cap.current_path} && #{dump_cmd} | #{compressor.compress('-', db_dump_file_path)}" + self + end - def download(local_file = "#{output_file}") - @cap.download! db_dump_file_path, local_file - end + def download(local_file = "#{output_file}") + @cap.download! db_dump_file_path, local_file + end - def clean_dump_if_needed - if @cap.fetch(:db_remote_clean) - @cap.execute "rm -f #{db_dump_file_path}" - else - puts "leaving #{db_dump_file_path} on the server (add \"set :db_remote_clean, true\" to deploy.rb to remove)" - end - end + def clean_dump_if_needed + if @cap.fetch(:db_remote_clean) + @cap.execute "rm -f #{db_dump_file_path}" + else + puts "leaving #{db_dump_file_path} on the server (add \"set :db_remote_clean, true\" to deploy.rb to remove)" + end + end - # cleanup = true removes the mysqldump file after loading, false leaves it in db/ - def load(file, cleanup) - unzip_file = File.join(File.dirname(file), File.basename(file, ".#{compressor.file_extension}")) - # @cap.run "cd #{@cap.current_path} && bunzip2 -f #{file} && RAILS_ENV=#{@cap.rails_env} bundle exec rake db:drop db:create && #{import_cmd(unzip_file)}" - @cap.execute "cd #{@cap.current_path} && #{compressor.decompress(file)} && RAILS_ENV=#{@cap.fetch(:rails_env)} && #{import_cmd(unzip_file)}" - @cap.execute("cd #{@cap.current_path} && rm #{unzip_file}") if cleanup - end + # cleanup = true removes the mysqldump file after loading, false leaves it in db/ + def load(file, cleanup) + unzip_file = File.join(File.dirname(file), File.basename(file, ".#{compressor.file_extension}")) + # @cap.run "cd #{@cap.current_path} && bunzip2 -f #{file} && RAILS_ENV=#{@cap.rails_env} bundle exec rake db:drop db:create && #{import_cmd(unzip_file)}" + @cap.execute "cd #{@cap.current_path} && #{compressor.decompress(file)} && RAILS_ENV=#{@cap.fetch(:rails_env)} && #{import_cmd(unzip_file)}" + @cap.execute("cd #{@cap.current_path} && rm #{unzip_file}") if cleanup + end - private + private - def db_dump_file_path - "#{db_dump_dir}/#{output_file}" - end + def db_dump_file_path + "#{db_dump_dir}/#{output_file}" + end - def db_dump_dir - @cap.fetch(:db_dump_dir) || "#{@cap.current_path}/db" - end - end + def db_dump_dir + @cap.fetch(:db_dump_dir) || "#{@cap.current_path}/db" + end + end - class Local < Base - def initialize(cap_instance) - super(cap_instance) - puts "Loading local database config" - command = "#{Dir.pwd}/bin/rails runner \"puts '#{DBCONFIG_BEGIN_FLAG}' + ActiveRecord::Base.connection.instance_variable_get(:@config).to_yaml + '#{DBCONFIG_END_FLAG}'\"" - stdout, status = Open3.capture2(command) - raise "Error running command (status=#{status}): #{command}" if status != 0 + class Local < Base + def initialize(cap_instance) + super(cap_instance) + puts "Loading local database config" + command = "#{Dir.pwd}/bin/rails runner \"puts '#{DBCONFIG_BEGIN_FLAG}' + ActiveRecord::Base.connection.instance_variable_get(:@config).to_yaml + '#{DBCONFIG_END_FLAG}'\"" + stdout, status = Open3.capture2(command) + raise "Error running command (status=#{status}): #{command}" if status != 0 - config_content = stdout.match(/#{DBCONFIG_BEGIN_FLAG}(.*?)#{DBCONFIG_END_FLAG}/m)[1] - @config = YAML.load(config_content).each_with_object({}) { |(k, v), h| h[k.to_s] = v } - end + config_content = stdout.match(/#{DBCONFIG_BEGIN_FLAG}(.*?)#{DBCONFIG_END_FLAG}/m)[1] + @config = YAML.load(config_content).each_with_object({}) { |(k, v), h| h[k.to_s] = v } + end - # cleanup = true removes the mysqldump file after loading, false leaves it in db/ - def load(file, cleanup) - unzip_file = File.join(File.dirname(file), File.basename(file, ".#{compressor.file_extension}")) - puts "executing local: #{compressor.decompress(file)} && #{import_cmd(unzip_file)}" - execute("#{compressor.decompress(file)} && #{import_cmd(unzip_file)}") - if cleanup - puts "removing #{unzip_file}" - File.unlink(unzip_file) - else - puts "leaving #{unzip_file} (specify :db_local_clean in deploy.rb to remove)" - end - puts "Completed database import" - end + # cleanup = true removes the mysqldump file after loading, false leaves it in db/ + def load(file, cleanup) + unzip_file = File.join(File.dirname(file), File.basename(file, ".#{compressor.file_extension}")) + puts "executing local: #{compressor.decompress(file)} && #{import_cmd(unzip_file)}" + execute("#{compressor.decompress(file)} && #{import_cmd(unzip_file)}") + if cleanup + puts "removing #{unzip_file}" + File.unlink(unzip_file) + else + puts "leaving #{unzip_file} (specify :db_local_clean in deploy.rb to remove)" + end + puts "Completed database import" + end - def dump - execute "#{dump_cmd} | #{compressor.compress('-', output_file)}" - self - end + def dump + execute "#{dump_cmd} | #{compressor.compress('-', output_file)}" + self + end - def upload - remote_file = "#{@cap.current_path}/#{output_file}" - @cap.upload! output_file, remote_file - end + def upload + remote_file = "#{@cap.current_path}/#{output_file}" + @cap.upload! output_file, remote_file + end - private + private - def execute(cmd) - result = system cmd - @cap.error "Failed to execute the local command: #{cmd}" unless result - result - end - end + def execute(cmd) + result = system cmd + @cap.error "Failed to execute the local command: #{cmd}" unless result + result + end + end - class << self - def check(local_db, remote_db) - raise 'Only mysql or postgresql on remote and local server is supported' unless (local_db.mysql? && remote_db.mysql?) || (local_db.postgresql? && remote_db.postgresql?) - end + class << self + def check(local_db, remote_db) + raise 'Only mysql or postgresql on remote and local server is supported' unless (local_db.mysql? && remote_db.mysql?) || (local_db.postgresql? && remote_db.postgresql?) + end - def remote_to_local(instance) - local_db = Database::Local.new(instance) - remote_db = Database::Remote.new(instance) + def remote_to_local(instance) + local_db = Database::Local.new(instance) + remote_db = Database::Remote.new(instance) - check(local_db, remote_db) + check(local_db, remote_db) - begin - remote_db.dump.download - ensure - remote_db.clean_dump_if_needed - end - local_db.load(remote_db.output_file, instance.fetch(:db_local_clean)) - end + begin + remote_db.dump.download + ensure + remote_db.clean_dump_if_needed + end + local_db.load(remote_db.output_file, instance.fetch(:db_local_clean)) + end - def local_to_remote(instance) - local_db = Database::Local.new(instance) - remote_db = Database::Remote.new(instance) + def local_to_remote(instance) + local_db = Database::Local.new(instance) + remote_db = Database::Remote.new(instance) - check(local_db, remote_db) + check(local_db, remote_db) - local_db.dump.upload - remote_db.load(local_db.output_file, instance.fetch(:db_local_clean)) - File.unlink(local_db.output_file) if instance.fetch(:db_local_clean) + local_db.dump.upload + remote_db.load(local_db.output_file, instance.fetch(:db_local_clean)) + File.unlink(local_db.output_file) if instance.fetch(:db_local_clean) + end + end end end end diff --git a/lib/capistrano/db_tasks/util.rb b/lib/capistrano/db_tasks/util.rb index 2e22efb..83921af 100644 --- a/lib/capistrano/db_tasks/util.rb +++ b/lib/capistrano/db_tasks/util.rb @@ -1,6 +1,10 @@ -module Util - def self.prompt(msg, prompt = "(y)es, (n)o ") - ask(:answer, "#{msg} #{prompt} ? ") - (fetch(:answer) =~ /^y$|^yes$/i).to_i.zero? +module Capistrano + module DbTasks + module Util + def self.prompt(msg, prompt = "(y)es, (n)o ") + ask(:answer, "#{msg} #{prompt} ? ") + (fetch(:answer) =~ /^y$|^yes$/i).to_i.zero? + end + end end end diff --git a/lib/capistrano/db_tasks/version.rb b/lib/capistrano/db_tasks/version.rb index bbebcfc..dd74aaf 100644 --- a/lib/capistrano/db_tasks/version.rb +++ b/lib/capistrano/db_tasks/version.rb @@ -1,3 +1,5 @@ -module CapistranoDbTasks - VERSION = "0.6".freeze +module Capistrano + module DbTasks + VERSION = "0.6".freeze + end end diff --git a/lib/capistrano/tasks/app.cap b/lib/capistrano/tasks/app.cap index 8053d43..f069e92 100644 --- a/lib/capistrano/tasks/app.cap +++ b/lib/capistrano/tasks/app.cap @@ -4,11 +4,11 @@ namespace :app do task sync: "capistrano_db_tasks:check_can_push" do if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to REPLACE THE REMOTE DATABASE AND your remote assets with local database and assets(#{fetch(:assets_dir)})") on roles(:db) do - Database.local_to_remote(self) + Capistrano::DbTasks::Database.local_to_remote(self) end on roles(:app) do - Asset.local_to_remote(self) + Capistrano::DbTasks::Asset.local_to_remote(self) end end end @@ -17,15 +17,15 @@ namespace :app do namespace :local do desc "Synchronize your local assets AND database using remote assets and database" task :sync do - puts "Local database : #{Database::Local.new(self).database}" + puts "Local database : #{Capistrano::DbTasks::Database::Local.new(self).database}" puts "Assets directories : #{fetch(:local_assets_dir)}" - if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to erase your local database AND your local assets with server database and assets(#{fetch(:assets_dir)})") + if fetch(:skip_data_sync_confirm) || Capistrano::DbTasks::Util.prompt("Are you sure you want to erase your local database AND your local assets with server database and assets(#{fetch(:assets_dir)})") on roles(:db) do - Database.remote_to_local(self) + Capistrano::DbTasks::Database.remote_to_local(self) end on roles(:app) do - Asset.remote_to_local(self) + Capistrano::DbTasks::Asset.remote_to_local(self) end end end diff --git a/lib/capistrano/tasks/database.cap b/lib/capistrano/tasks/database.cap index 6d88653..481ada1 100644 --- a/lib/capistrano/tasks/database.cap +++ b/lib/capistrano/tasks/database.cap @@ -3,8 +3,8 @@ namespace :db do desc "Synchronize your remote database using local database data" task sync: "capistrano_db_tasks:check_can_push" do on roles(:db) do - if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to REPLACE THE REMOTE DATABASE with local database") - Database.local_to_remote(self) + if fetch(:skip_data_sync_confirm) || Capistrano::DbTasks::Util.prompt("Are you sure you want to REPLACE THE REMOTE DATABASE with local database") + Capistrano::DbTasks::Database.local_to_remote(self) end end end @@ -14,9 +14,8 @@ namespace :db do desc "Synchronize your local database using remote database data" task :sync do on roles(:db) do - puts "Local database: #{Database::Local.new(self).database}" - if fetch(:skip_data_sync_confirm) || Util.prompt("Are you sure you want to erase your local database with server database") - Database.remote_to_local(self) + if fetch(:skip_data_sync_confirm) || Capistrano::DbTasks::Util.prompt("Are you sure you want to erase your local database with server database") + Capistrano::DbTasks::Database.remote_to_local(self) end end end From df87b0cfbd2ed6e764e24691cec14cfa59eabe0c Mon Sep 17 00:00:00 2001 From: Andrey Subbota Date: Fri, 6 Jan 2017 23:45:30 +0400 Subject: [PATCH 3/3] Withdraws and separates database adapters. Split local and remote database tasks. --- lib/capistrano-db-tasks.rb | 2 +- lib/capistrano/db_tasks.rb | 8 + lib/capistrano/db_tasks/adapters/base.rb | 23 ++ lib/capistrano/db_tasks/adapters/mysql.rb | 43 ++++ lib/capistrano/db_tasks/adapters/postgres.rb | 56 +++++ lib/capistrano/db_tasks/database.rb | 208 +------------------ lib/capistrano/db_tasks/databases/base.rb | 68 ++++++ lib/capistrano/db_tasks/databases/local.rb | 51 +++++ lib/capistrano/db_tasks/databases/remote.rb | 56 +++++ 9 files changed, 311 insertions(+), 204 deletions(-) create mode 100644 lib/capistrano/db_tasks/adapters/base.rb create mode 100644 lib/capistrano/db_tasks/adapters/mysql.rb create mode 100644 lib/capistrano/db_tasks/adapters/postgres.rb create mode 100644 lib/capistrano/db_tasks/databases/base.rb create mode 100644 lib/capistrano/db_tasks/databases/local.rb create mode 100644 lib/capistrano/db_tasks/databases/remote.rb diff --git a/lib/capistrano-db-tasks.rb b/lib/capistrano-db-tasks.rb index 20adc09..619a501 100644 --- a/lib/capistrano-db-tasks.rb +++ b/lib/capistrano-db-tasks.rb @@ -1,2 +1,2 @@ -$stderr.puts %Q(Please, change \"require 'capistrano-db-tasks'\" to \"require 'capistrano/db_tasks'" in your Capfile.) +$stderr.puts %(Please, change \"require 'capistrano-db-tasks'\" to \"require 'capistrano/db_tasks'" in your Capfile.) require 'capistrano/db_tasks' diff --git a/lib/capistrano/db_tasks.rb b/lib/capistrano/db_tasks.rb index fd488ac..c8372be 100644 --- a/lib/capistrano/db_tasks.rb +++ b/lib/capistrano/db_tasks.rb @@ -6,4 +6,12 @@ require_relative 'db_tasks/compressors/bzip2' require_relative 'db_tasks/compressors/gzip' +require_relative 'db_tasks/databases/base' +require_relative 'db_tasks/databases/local' +require_relative 'db_tasks/databases/remote' + +require_relative 'db_tasks/adapters/base' +require_relative 'db_tasks/adapters/mysql' +require_relative 'db_tasks/adapters/postgres' + require_relative 'db_tasks/tasks' diff --git a/lib/capistrano/db_tasks/adapters/base.rb b/lib/capistrano/db_tasks/adapters/base.rb new file mode 100644 index 0000000..4cba9e5 --- /dev/null +++ b/lib/capistrano/db_tasks/adapters/base.rb @@ -0,0 +1,23 @@ +module Capistrano + module DbTasks + module Adapters + class Base + attr_accessor :cap, :config + + def self.suitable?(adapter); end + + def database + @config['database'] + end + + def dump_cmd + raise "Dump database is not implemented" + end + + def import_cmd + raise "Import database is not implemented" + end + end + end + end +end diff --git a/lib/capistrano/db_tasks/adapters/mysql.rb b/lib/capistrano/db_tasks/adapters/mysql.rb new file mode 100644 index 0000000..f22295d --- /dev/null +++ b/lib/capistrano/db_tasks/adapters/mysql.rb @@ -0,0 +1,43 @@ +module Capistrano + module DbTasks + module Adapters + class Mysql < Base + def self.suitable?(adapter) + adapter.index('mysql') == 0 + end + + def credentials + credential_params = "" + username = config['username'] || config['user'] + + credential_params << " -u #{username} " if username + credential_params << " -p'#{config['password']}' " if config['password'] + credential_params << " -h #{config['host']} " if config['host'] + credential_params << " -S #{config['socket']} " if config['socket'] + credential_params << " -P #{config['port']} " if config['port'] + + credential_params + end + + def dump_cmd + "mysqldump #{credentials} #{database} #{dump_cmd_opts}" + end + + def import_cmd(file) + "mysql #{credentials} -D #{database} < #{file}" + end + + private + + def dump_cmd_opts + "--lock-tables=false #{dump_cmd_ignore_tables_opts} #{dump_cmd_ignore_data_tables_opts}" + end + + def dump_cmd_ignore_tables_opts + ignore_tables = @cap.fetch(:db_ignore_tables, []) + ignore_tables.map { |t| "--ignore-table=#{database}.#{t}" }.join(" ") + end + end + end + end +end diff --git a/lib/capistrano/db_tasks/adapters/postgres.rb b/lib/capistrano/db_tasks/adapters/postgres.rb new file mode 100644 index 0000000..b82107a --- /dev/null +++ b/lib/capistrano/db_tasks/adapters/postgres.rb @@ -0,0 +1,56 @@ +module Capistrano + module DbTasks + module Adapters + class Postgres < Base + def self.suitable?(adapter) + %w(postgresql pg postgis).include? adapter + end + + def credentials + credential_params = "" + username = config['username'] || config['user'] + + credential_params << " -U #{username} " if username + credential_params << " -h #{config['host']} " if config['host'] + credential_params << " -p #{config['port']} " if config['port'] + + credential_params + end + + def dump_cmd + "#{pgpass} pg_dump #{credentials} #{database} #{dump_cmd_opts}" + end + + def import_cmd(file) + terminate_connection_sql = "SELECT pg_terminate_backend(pg_stat_activity.pid) " \ + "FROM pg_stat_activity WHERE pg_stat_activity.datname = '#{database}' AND pid <> pg_backend_pid();" + + "#{pgpass} psql -c \"#{terminate_connection_sql};\" #{credentials} #{database};" \ + "#{pgpass} dropdb #{credentials} #{database};" \ + "#{pgpass} createdb #{credentials} #{database};" \ + "#{pgpass} psql #{credentials} -d #{database} < #{file}" + end + + private + + def pgpass + config['password'] ? "PGPASSWORD='#{config['password']}'" : "" + end + + def dump_cmd_opts + "--no-acl --no-owner #{dump_cmd_ignore_tables_opts} #{dump_cmd_ignore_data_tables_opts}" + end + + def dump_cmd_ignore_tables_opts + ignore_tables = @cap.fetch(:db_ignore_tables, []) + ignore_tables.map { |t| "--exclude-table=#{t}" }.join(" ") + end + + def dump_cmd_ignore_data_tables_opts + ignore_tables = @cap.fetch(:db_ignore_data_tables, []) + ignore_tables.map { |t| "--exclude-table-data=#{t}" }.join(" ") + end + end + end + end +end diff --git a/lib/capistrano/db_tasks/database.rb b/lib/capistrano/db_tasks/database.rb index bcbac94..f910066 100644 --- a/lib/capistrano/db_tasks/database.rb +++ b/lib/capistrano/db_tasks/database.rb @@ -1,212 +1,14 @@ module Capistrano module DbTasks module Database - class Base - DBCONFIG_BEGIN_FLAG = "__CAPISTRANODB_CONFIG_BEGIN_FLAG__".freeze - DBCONFIG_END_FLAG = "__CAPISTRANODB_CONFIG_END_FLAG__".freeze - - attr_accessor :config, :output_file - - def initialize(cap_instance) - @cap = cap_instance - end - - def mysql? - @config['adapter'] =~ /^mysql/ - end - - def postgresql? - %w(postgresql pg postgis).include? @config['adapter'] - end - - def credentials - credential_params = "" - username = @config['username'] || @config['user'] - - if mysql? - credential_params << " -u #{username} " if username - credential_params << " -p'#{@config['password']}' " if @config['password'] - credential_params << " -h #{@config['host']} " if @config['host'] - credential_params << " -S #{@config['socket']} " if @config['socket'] - credential_params << " -P #{@config['port']} " if @config['port'] - elsif postgresql? - credential_params << " -U #{username} " if username - credential_params << " -h #{@config['host']} " if @config['host'] - credential_params << " -p #{@config['port']} " if @config['port'] - end - - credential_params - end - - def database - @config['database'] - end - - def current_time - Time.now.strftime("%Y-%m-%d-%H%M%S") - end - - def output_file - @output_file ||= "#{database}_#{current_time}.sql.#{compressor.file_extension}" - end - - def compressor - @compressor ||= begin - compressor_klass = @cap.fetch(:compressor).to_s.split('_').collect(&:capitalize).join - klass = Object.module_eval("::Capistrano::DbTasks::Compressors::#{compressor_klass}", __FILE__, __LINE__) - klass - end - end - - private - - def pgpass - @config['password'] ? "PGPASSWORD='#{@config['password']}'" : "" - end - - def dump_cmd - if mysql? - "mysqldump #{credentials} #{database} #{dump_cmd_opts}" - elsif postgresql? - "#{pgpass} pg_dump #{credentials} #{database} #{dump_cmd_opts}" - end - end - - def import_cmd(file) - if mysql? - "mysql #{credentials} -D #{database} < #{file}" - elsif postgresql? - terminate_connection_sql = "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '#{database}' AND pid <> pg_backend_pid();" - "#{pgpass} psql -c \"#{terminate_connection_sql};\" #{credentials} #{database}; #{pgpass} dropdb #{credentials} #{database}; #{pgpass} createdb #{credentials} #{database}; #{pgpass} psql #{credentials} -d #{database} < #{file}" - end - end - - def dump_cmd_opts - if mysql? - "--lock-tables=false #{dump_cmd_ignore_tables_opts} #{dump_cmd_ignore_data_tables_opts}" - elsif postgresql? - "--no-acl --no-owner #{dump_cmd_ignore_tables_opts} #{dump_cmd_ignore_data_tables_opts}" - end - end - - def dump_cmd_ignore_tables_opts - ignore_tables = @cap.fetch(:db_ignore_tables, []) - if mysql? - ignore_tables.map { |t| "--ignore-table=#{database}.#{t}" }.join(" ") - elsif postgresql? - ignore_tables.map { |t| "--exclude-table=#{t}" }.join(" ") - end - end - - def dump_cmd_ignore_data_tables_opts - ignore_tables = @cap.fetch(:db_ignore_data_tables, []) - ignore_tables.map { |t| "--exclude-table-data=#{t}" }.join(" ") if postgresql? - end - end - - class Remote < Base - def initialize(cap_instance) - super(cap_instance) - puts "Loading remote database config" - @cap.within @cap.current_path do - @cap.with rails_env: @cap.fetch(:rails_env) do - dirty_config_content = @cap.capture(:rails, "runner \"puts '#{DBCONFIG_BEGIN_FLAG}' + ActiveRecord::Base.connection.instance_variable_get(:@config).to_yaml + '#{DBCONFIG_END_FLAG}'\"", '2>/dev/null') - # Remove all warnings, errors and artefacts produced by bunlder, rails and other useful tools - config_content = dirty_config_content.match(/#{DBCONFIG_BEGIN_FLAG}(.*?)#{DBCONFIG_END_FLAG}/m)[1] - @config = YAML.load(config_content).each_with_object({}) { |(k, v), h| h[k.to_s] = v } - end - end - end - - def dump - @cap.execute "cd #{@cap.current_path} && #{dump_cmd} | #{compressor.compress('-', db_dump_file_path)}" - self - end - - def download(local_file = "#{output_file}") - @cap.download! db_dump_file_path, local_file - end - - def clean_dump_if_needed - if @cap.fetch(:db_remote_clean) - @cap.execute "rm -f #{db_dump_file_path}" - else - puts "leaving #{db_dump_file_path} on the server (add \"set :db_remote_clean, true\" to deploy.rb to remove)" - end - end - - # cleanup = true removes the mysqldump file after loading, false leaves it in db/ - def load(file, cleanup) - unzip_file = File.join(File.dirname(file), File.basename(file, ".#{compressor.file_extension}")) - # @cap.run "cd #{@cap.current_path} && bunzip2 -f #{file} && RAILS_ENV=#{@cap.rails_env} bundle exec rake db:drop db:create && #{import_cmd(unzip_file)}" - @cap.execute "cd #{@cap.current_path} && #{compressor.decompress(file)} && RAILS_ENV=#{@cap.fetch(:rails_env)} && #{import_cmd(unzip_file)}" - @cap.execute("cd #{@cap.current_path} && rm #{unzip_file}") if cleanup - end - - private - - def db_dump_file_path - "#{db_dump_dir}/#{output_file}" - end - - def db_dump_dir - @cap.fetch(:db_dump_dir) || "#{@cap.current_path}/db" - end - end - - class Local < Base - def initialize(cap_instance) - super(cap_instance) - puts "Loading local database config" - command = "#{Dir.pwd}/bin/rails runner \"puts '#{DBCONFIG_BEGIN_FLAG}' + ActiveRecord::Base.connection.instance_variable_get(:@config).to_yaml + '#{DBCONFIG_END_FLAG}'\"" - stdout, status = Open3.capture2(command) - raise "Error running command (status=#{status}): #{command}" if status != 0 - - config_content = stdout.match(/#{DBCONFIG_BEGIN_FLAG}(.*?)#{DBCONFIG_END_FLAG}/m)[1] - @config = YAML.load(config_content).each_with_object({}) { |(k, v), h| h[k.to_s] = v } - end - - # cleanup = true removes the mysqldump file after loading, false leaves it in db/ - def load(file, cleanup) - unzip_file = File.join(File.dirname(file), File.basename(file, ".#{compressor.file_extension}")) - puts "executing local: #{compressor.decompress(file)} && #{import_cmd(unzip_file)}" - execute("#{compressor.decompress(file)} && #{import_cmd(unzip_file)}") - if cleanup - puts "removing #{unzip_file}" - File.unlink(unzip_file) - else - puts "leaving #{unzip_file} (specify :db_local_clean in deploy.rb to remove)" - end - puts "Completed database import" - end - - def dump - execute "#{dump_cmd} | #{compressor.compress('-', output_file)}" - self - end - - def upload - remote_file = "#{@cap.current_path}/#{output_file}" - @cap.upload! output_file, remote_file - end - - private - - def execute(cmd) - result = system cmd - @cap.error "Failed to execute the local command: #{cmd}" unless result - result - end - end - class << self def check(local_db, remote_db) - raise 'Only mysql or postgresql on remote and local server is supported' unless (local_db.mysql? && remote_db.mysql?) || (local_db.postgresql? && remote_db.postgresql?) + raise 'Remote and local server must have some adapter' unless local_db.adapter.class == remote_db.adapter.class end def remote_to_local(instance) - local_db = Database::Local.new(instance) - remote_db = Database::Remote.new(instance) + local_db = Capistrano::DbTasks::Databases::Local.new(instance) + remote_db = Capistrano::DbTasks::Databases::Remote.new(instance) check(local_db, remote_db) @@ -219,8 +21,8 @@ def remote_to_local(instance) end def local_to_remote(instance) - local_db = Database::Local.new(instance) - remote_db = Database::Remote.new(instance) + local_db = Capistrano::DbTasks::Databases::Local.new(instance) + remote_db = Capistrano::DbTasks::Databases::Remote.new(instance) check(local_db, remote_db) diff --git a/lib/capistrano/db_tasks/databases/base.rb b/lib/capistrano/db_tasks/databases/base.rb new file mode 100644 index 0000000..e280a98 --- /dev/null +++ b/lib/capistrano/db_tasks/databases/base.rb @@ -0,0 +1,68 @@ +require 'pry' +module Capistrano + module DbTasks + module Databases + class Base + DBCONFIG_BEGIN_FLAG = "__CAPISTRANODB_CONFIG_BEGIN_FLAG__".freeze + DBCONFIG_END_FLAG = "__CAPISTRANODB_CONFIG_END_FLAG__".freeze + + DBCONFIG_RAILS_CMD = %(puts '#{DBCONFIG_BEGIN_FLAG}'+ActiveRecord::Base.connection.instance_variable_get(:@config).to_yaml+'#{DBCONFIG_END_FLAG}').freeze + + attr_accessor :adapter, :cap, :compressor, :config + + def initialize(cap_instance) + @cap = cap_instance + load_config! + load_adapter! + load_compressor! + end + + def load_compressor! + @compressor ||= begin + compressor_klass = @cap.fetch(:compressor).to_s.split('_').collect(&:capitalize).join + klass = Object.module_eval("::Capistrano::DbTasks::Compressors::#{compressor_klass}", __FILE__, __LINE__) + klass + end + end + + def output_file + @output_file ||= "#{config['database']}_#{current_time}.sql.#{compressor.file_extension}" + end + + def load_config! + raise "Only in Local or Remote" + end + + def available_adapters + @available_adapters ||= Capistrano::DbTasks::Adapters.constants.map { |c| Capistrano::DbTasks::Adapters.const_get(c) }.select { |c| c.is_a? Class } + end + + def load_adapter! + adapter_klass = available_adapters.find { |a| a.suitable? config["adapter"] } + + raise "Only #{avaliable_adapters.map(&:to_s).join(', ')} on remote and local server is supported" unless adapter_klass + + @adapter = adapter_klass.new + @adapter.cap = @cap + @adapter.config = @config + + @adapter + end + + private + + def run_locally(command) + stdout, status = Open3.capture2(command) + + raise "Error running command locally (status=#{status}): #{command}" if status != 0 + + stdout + end + + def current_time + Time.now.strftime("%Y-%m-%d-%H%M%S") + end + end + end + end +end diff --git a/lib/capistrano/db_tasks/databases/local.rb b/lib/capistrano/db_tasks/databases/local.rb new file mode 100644 index 0000000..fbf44a7 --- /dev/null +++ b/lib/capistrano/db_tasks/databases/local.rb @@ -0,0 +1,51 @@ +module Capistrano + module DbTasks + module Databases + class Local < Base + def initialize(cap_instance) + super(cap_instance) + end + + def dump + run_locally "#{adapter.dump_cmd} | #{compressor.compress('-', output_file)}" + self + end + + # cleanup = true removes the mysqldump file after loading, false leaves it in db/ + def load(file, cleanup) + unzip_file = File.join(File.dirname(file), File.basename(file, ".#{compressor.file_extension}")) + puts "executing local: #{compressor.decompress(file)} && #{adapter.import_cmd(unzip_file)}" + + run_locally("#{compressor.decompress(file)} && #{adapter.import_cmd(unzip_file)}") + + if cleanup + puts "removing #{unzip_file}" + File.unlink(unzip_file) + else + puts "leaving #{unzip_file} (specify :db_local_clean in deploy.rb to remove)" + end + puts "Completed database import" + end + + def upload + remote_file = "#{@cap.current_path}/#{output_file}" + @cap.upload! output_file, remote_file + end + + private + + def load_config! + puts "Loading local database config" + + command = %(#{Dir.pwd}/bin/rails runner "#{DBCONFIG_RAILS_CMD}") + stdout = run_locally(command) + + config_content = stdout.match(/#{DBCONFIG_BEGIN_FLAG}(.*?)#{DBCONFIG_END_FLAG}/m)[1] + config = YAML.load(config_content).each_with_object({}) { |(k, v), h| h[k.to_s] = v } + + @config = config + end + end + end + end +end diff --git a/lib/capistrano/db_tasks/databases/remote.rb b/lib/capistrano/db_tasks/databases/remote.rb new file mode 100644 index 0000000..ecf2953 --- /dev/null +++ b/lib/capistrano/db_tasks/databases/remote.rb @@ -0,0 +1,56 @@ +module Capistrano + module DbTasks + module Databases + class Remote < Base + def initialize(cap_instance) + super(cap_instance) + end + + def dump + @cap.execute "cd #{@cap.current_path} && #{adapter.dump_cmd} | #{compressor.compress('-', db_dump_file_path)}" + self + end + + def download(local_file = "#{output_file}") + @cap.download! db_dump_file_path, local_file + end + + def clean_dump_if_needed + if @cap.fetch(:db_remote_clean) + @cap.execute "rm -f #{db_dump_file_path}" + else + puts "leaving #{db_dump_file_path} on the server (add \"set :db_remote_clean, true\" to deploy.rb to remove)" + end + end + + def load(file, cleanup) + unzip_file = File.join(File.dirname(file), File.basename(file, ".#{compressor.file_extension}")) + @cap.execute "cd #{@cap.current_path} && #{compressor.decompress(file)} && RAILS_ENV=#{@cap.fetch(:rails_env)} && #{import_cmd(unzip_file)}" + @cap.execute("cd #{@cap.current_path} && rm #{unzip_file}") if cleanup + end + + private + + def db_dump_file_path + "#{db_dump_dir}/#{output_file}" + end + + def db_dump_dir + @cap.fetch(:db_dump_dir) || "#{@cap.current_path}/db" + end + + def load_config! + puts "Loading remote database config" + @cap.within @cap.current_path do + @cap.with rails_env: @cap.fetch(:rails_env) do + dirty_config_content = @cap.capture(:rails, %(runner "#{DBCONFIG_RAILS_CMD}"), '2>/dev/null') + # Remove all warnings, errors and artefacts produced by bunlder, rails and other useful tools + config_content = dirty_config_content.match(/#{DBCONFIG_BEGIN_FLAG}(.*?)#{DBCONFIG_END_FLAG}/m)[1] + @config = YAML.load(config_content).each_with_object({}) { |(k, v), h| h[k.to_s] = v } + end + end + end + end + end + end +end