#!/usr/bin/env ruby if ARGV.include?("-i") puts "*******************************************************" puts system("du -sh ~/dumps") puts puts "*******************************************************" exit end require 'erb' require "yaml" DB_CONFIG_PATH = 'config/database.yml' DUMPS_DIR = "#{ENV['HOME']}/dumps" def run(*args) if !!ENV['GEORDI_TESTING'] puts "system #{args.join(', ')}" true # "Command succeeded" else system *args end end def cd_to_project_root(fail_gently) current = Dir.pwd until File.exist?(DB_CONFIG_PATH) Dir.chdir '..' if current == Dir.pwd if fail_gently puts "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *" puts "* *" puts "* *" puts "* Script is not called from inside a Rails project, *" puts "* *" puts "* THE DATABASE WILL NOT BE DUMPED. *" puts "* *" puts "* *" puts "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *" sleep 5 exit else raise "x Call me from inside a Rails project." end end current = Dir.pwd end end def dump_command(dump_file, config) host = config['host'] port = config['port'] case config['adapter'] when /mysql/ command = "mysqldump" command << " -u\"#{config['username']}\"" command << " -p\"#{config['password']}\"" command << " #{config['database']}" command << " -r #{dump_file}" # Using a transaction to allow concurrent request while creating a dump. # This works only reliable for InnoDB tables. # More details: https://dev.mysql.com/doc/refman/5.7/en/mysqldump.html#option_mysqldump_single-transaction command << ' --single-transaction' # Disable buffering in memory to avoid a memory overflow for large tables # More details https://dev.mysql.com/doc/refman/5.7/en/mysqldump.html#option_mysqldump_quick command << ' --quick' if port command << " -h#{host || '127.0.0.1'} -P#{port}" else command << " -h#{host || 'localhost'}" end command when /postgres/ command = "PGPASSWORD=\"#{config['password']}\"" command << " pg_dump #{config['database']}" command << " --clean" command << " --format=custom" command << " --file=#{dump_file}" command << " --username=\"#{config['username']}\"" command << " --host=#{host}" if host command << " --port=#{port}" if port command else raise %(x Adapter "#{config['adapter']}" is not supported.) end end def find_database_config(config_path, environment, database) environment ||= 'production' database_yml = ERB.new(File.read(config_path)).result config = if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0') YAML.safe_load(database_yml, aliases: true) else YAML.safe_load(database_yml, [], [], true) end config = config[environment] or raise "x No #{environment} database found.\nUsage: dumple ENVIRONMENT [DATABASE]" if config.values[0].is_a? Hash # Multi-db setup if database # Explicitly requested config = config[database] or raise %(x Unknown #{environment} database "#{database}".) elsif config.key? 'primary' puts '> Multiple databases detected. Defaulting to primary database.' config = config['primary'] else puts "> Multiple databases detected. Defaulting to first entry (#{config.keys[0]})." config = config.values[0] end else # Single-db setup if database raise %(x Could not select "#{database}" database in a single-db environment.) end end config end def prepare_dump_path(config) unless File.directory?(DUMPS_DIR) Dir.mkdir(DUMPS_DIR) run "chmod 700 #{DUMPS_DIR}" end if ARGV.include? '--for_download' "#{DUMPS_DIR}/dump_for_download.dump" else "#{DUMPS_DIR}/#{config['database']}_#{Time.now.strftime("%Y%m%d_%H%M%S")}.dump" end end begin fail_gently = ARGV.include?("--fail-gently") compress = ARGV.include?("--compress") environment, database = ARGV.reject { |arg| arg[0].chr == '-' } cd_to_project_root(fail_gently) config = find_database_config(DB_CONFIG_PATH, environment, database) dump_path = prepare_dump_path(config) # Dump! given_database = database ? %(#{database} ) : "" command = dump_command(dump_path, config) puts "> Dumping #{given_database}database for \"#{environment}\" environment ..." run command or raise "x Creating the dump failed." run "chmod 600 #{dump_path}" if compress puts "> Compressing the dump ..." # gzip compresses in place compress_success = run "gzip #{dump_path}" compress_success or raise "x Compressing the dump failed." dump_path << ".gz" end dump_size_kb = (File.size(dump_path) / 1024).round puts "> Dumped to #{dump_path} (#{dump_size_kb} KB)" rescue StandardError => e $stderr.puts e.message exit 1 end