diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..ced5a32 --- /dev/null +++ b/README.markdown @@ -0,0 +1,108 @@ +CapistranoDbTasks +================= + +Add database AND assets tasks to capistrano to a Rails project. +It only works with capistrano 3. Older versions until 0.3 works with capistrano 2. + +Currently + +* It only supports mysql and postgresql (both side remote and local) +* Synchronize assets remote to local and local to remote + +Commands mysql, mysqldump (or pg\_dump, psql), bzip2 and unbzip2 (or gzip) must be in your PATH + +Feel free to fork and to add more database support or new tasks. + +Install +======= + +Add it as a gem: + +```ruby + gem "capistrano-db-tasks", require: false +``` + +Add to config/deploy.rb: + +```ruby + require 'capistrano-db-tasks' + + # if you haven't already specified + set :rails_env, "production" + + # if you want to remove the local dump file after loading + set :db_local_clean, true + + # if you want to remove the dump file from the server after downloading + set :db_remote_clean, true + + # if you want to exclude table from dump + set :db_ignore_tables, [] + + # if you want to exclude table data (but not table schema) from dump + set :db_ignore_data_tables, [] + + # If you want to import assets, you can change default asset dir (default = system) + # This directory must be in your shared directory on the server + set :assets_dir, %w(public/assets public/att) + set :local_assets_dir, %w(public/assets public/att) + + # if you want to work on a specific local environment (default = ENV['RAILS_ENV'] || 'development') + set :locals_rails_env, "production" + + # if you are highly paranoid and want to prevent any push operation to the server + set :disallow_pushing, true + + # if you prefer bzip2/unbzip2 instead of gzip + set :compressor, :bzip2 + + # If you want to use privileged user to drop/create database on remote or local host (PostgreSQL only) + set :db_remote_superuser, 'postgres' + set :db_remote_superuser_password, 'secret' + set :db_local_superuser, 'postgres' + set :db_local_superuser_password, 'secret' + +``` + +Add to .gitignore +```yml + /db/*.sql +``` + + +[How to install bzip2 on Windows](http://stackoverflow.com/a/25625988/3324219) + +Available tasks +=============== + + app:local:sync || app:pull # Synchronize your local assets AND database using remote assets and database + app:remote:sync || app:push # Synchronize your remote assets AND database using local assets and database + + assets:local:sync || assets:pull # Synchronize your local assets using remote assets + assets:remote:sync || assets:push # Synchronize your remote assets using local assets + + db:local:sync || db:pull # Synchronize your local database using remote database data + db:remote:sync || db:push # Synchronize your remote database using local database data + +Example +======= + + cap db:pull + cap production db:pull # if you are using capistrano-ext to have multistages + + +Contributors +============ + +* tilsammans (http://github.com/tilsammansee) +* bigfive (http://github.com/bigfive) +* jakemauer (http://github.com/jakemauer) +* tjoneseng (http://github.com/tjoneseng) + +TODO +==== + +* May be change project's name as it's not only database tasks now :) +* Add tests + +Copyright (c) 2009 [Sébastien Gruhier - XILINUS], released under the MIT license diff --git a/lib/capistrano-db-tasks/database.rb b/lib/capistrano-db-tasks/database.rb index 2c54c5f..e5910c0 100644 --- a/lib/capistrano-db-tasks/database.rb +++ b/lib/capistrano-db-tasks/database.rb @@ -17,13 +17,14 @@ def postgresql? %w(postgresql pg postgis).include? @config['adapter'] end - def credentials + def credentials(options = {superuser: false}) credential_params = "" - username = @config['username'] || @config['user'] + username = (options[:superuser] && @cap.fetch("db_#{options[:location]}_superuser".to_sym)) || @config['username'] || @config['user'] + password = (options[:superuser] && @cap.fetch("db_#{options[:location]}_superuser_password".to_sym)) || @config['password'] if mysql? credential_params << " -u #{username} " if username - credential_params << " -p'#{@config['password']}' " if @config['password'] + credential_params << " -p'#{password}' " if 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'] @@ -58,8 +59,9 @@ def compressor private - def pgpass - @config['password'] ? "PGPASSWORD='#{@config['password']}'" : "" + def pgpass(options = {superuser: false}) + password = (options[:superuser] && @cap.fetch("db_#{options[:location]}_superuser_password".to_sym)) || @config['password'] + password ? "PGPASSWORD='#{password}'" : "" end def dump_cmd @@ -70,12 +72,15 @@ def dump_cmd end end - def import_cmd(file) + def import_cmd(file, location = :remote) if mysql? "mysql #{credentials} -D #{database} < #{file}" elsif postgresql? + owner = @config['username'] || @config['user'] + pg_password = pgpass(superuser: true, location: location) + pg_credentials = credentials(superuser: true, location: location) 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}" + "#{pg_password} psql -c \"#{terminate_connection_sql};\" #{pg_credentials} #{database}; #{pg_password} dropdb #{pg_credentials} #{database}; #{pg_password} createdb #{pg_credentials} --owner=#{owner} #{database}; #{pgpass} psql #{credentials} -d #{database} < #{file}" end end @@ -108,10 +113,8 @@ def initialize(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 } + config_content = @cap.capture(:cat, "config/database.yml") + @config = YAML.load(config_content)[@cap.fetch(:rails_env).to_s].each_with_object({}) { |(k, v), h| h[k.to_s] = v } end end end @@ -157,12 +160,12 @@ def initialize(cap_instance) super(cap_instance) puts "Loading local database config" dir_with_escaped_spaces = Dir.pwd.gsub ' ', '\ ' - command = "#{dir_with_escaped_spaces}/bin/rails runner \"puts '#{DBCONFIG_BEGIN_FLAG}' + ActiveRecord::Base.connection.instance_variable_get(:@config).to_yaml + '#{DBCONFIG_END_FLAG}'\"" - stdout, status = Open3.capture2(command) + command = "cat config/database.yml" + config_content, 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 } + local_env = ENV['RAILS_ENV'] || 'development' + @config = YAML.load(config_content)[local_env].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/