clone_remote_db 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7e4ef1f0948c75e7b40317ada0a5561c677ce93a
4
+ data.tar.gz: c1f4de4836d591b99952a9587ac908b072715f13
5
+ SHA512:
6
+ metadata.gz: b3b63f475666459d1a108fca67f0129814195a0e8379ee00584193066e2df20724247c8f24f541a1ee0e06e6cd23e70b48c42b1c7dfdb7cbfcc39cb61df3e369
7
+ data.tar.gz: 7551aee85ba3d6e42b9c9f86c20edce8c68251daa569593f12cb50b1979d28919d18c9d36e60e20e017e01a6a68c7a7052c26526954073bb25be0431460cc0c6
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Josh McDade
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # clone_remote_db
2
+
3
+ Simple script to backup a PostgreSQL database from a remote server, and restore
4
+ it into a local database (such as development or staging).
5
+
6
+ ## Installation
7
+
8
+ Install the gem.
9
+
10
+ gem install clone_remote_db
11
+
12
+ And then execute:
13
+
14
+ $ clone_remote_db --help
15
+
16
+ ## Usage
17
+
18
+ The script can run with just arguments, but to make it easier, environments can
19
+ be defined in a a configuration file (by default: `$HOME/.clone_remote_db.yml`).
20
+
21
+ Below is an example YAML configuration file:
22
+
23
+ ---
24
+ defaults: &defaults
25
+ local_dest: '~/pg_dumps/{remote_db}/%Y-%m/%Y-%m-%d_%H.%M.sql.gz'
26
+ remote_user: 'postgres'
27
+ gzip_opts:
28
+ - '-9'
29
+ - '--stdout'
30
+ pg_dump_opts:
31
+ - '-c'
32
+ - '-O'
33
+ pg_exclude_data:
34
+ - 'versions'
35
+ environments:
36
+ foo:
37
+ <<: *defaults
38
+ host: 'foo-ssh-alias'
39
+ local_db: 'foo_development'
40
+ remote_db: 'foo_development'
41
+ pg_exclude_data:
42
+ - versions
43
+ - session_logs
44
+ - send_mails
45
+ - job_logs
46
+ bar:
47
+ <<: *defaults
48
+ host: 'bar-ssh-alias'
49
+ local_db: 'bar_development'
50
+ remote_db: 'bar_production'
51
+ pg_exclude_data: []
52
+
53
+ You can avoid command-line arguments all together by using a configuration
54
+ file, so the below command would copy the remote database "foo_production" into
55
+ your local database "foo_development".
56
+
57
+ clone_remote_db foo
58
+
59
+ If a database has already been downloaded, you can skip the download step and
60
+ import the saved backup right away:
61
+
62
+ clone_remote_db foo --import-only \
63
+ ~/pg_dumps/foo_production/2014-01/2014-01-01_12-00.sql.gz
64
+
65
+ Placeholders can be used for the --local-dest argument:
66
+
67
+ clone_remote_db foo --local-dest \
68
+ '~/pg_dumps/{remote_db}/%Y-%m/%Y-%m-%d_%H.%M.sql.gz'
69
+
70
+ Configuration values from the YAML file can be overwritten so you do not need
71
+ to modify the file:
72
+
73
+ clone_remote_db foo --local-db foo_staging
74
+ # instead of copying foo_production into foo_development, it will copy
75
+ # foo_production into foo_staging
76
+
77
+ ## Contributing
78
+
79
+ 1. Fork it ( http://github.com/<my-github-username>/clone_remote_db/fork )
80
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
81
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
82
+ 4. Push to the branch (`git push origin my-new-feature`)
83
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require 'yaml'
5
+ require 'fileutils'
6
+ require 'shellwords'
7
+
8
+ require File.expand_path('../../lib/clone_remote_db', __FILE__)
9
+
10
+ loader = CloneRemoteDb::Loader.new(File.basename(__FILE__))
11
+ cli_opts = loader.parse_opts!
12
+
13
+ def escape_opts(opts)
14
+ if opts.is_a?(Enumerable)
15
+ opts.map { |o| Shellwords.escape(o) }.join(' ')
16
+ elsif opts.nil? || opts == ''
17
+ ''
18
+ else
19
+ Shellwords.escape(opts.to_s)
20
+ end
21
+ end
22
+
23
+ def run_cmd(cmd, msg, opts = {})
24
+ start_time = Time.now
25
+ puts ">> [#{start_time}] #{msg}: #{cmd}"
26
+
27
+ unless opts[:dry_run]
28
+ pipe = IO.popen(cmd)
29
+ while (line = pipe.gets)
30
+ print line
31
+ end
32
+ end
33
+
34
+ end_time = Time.now
35
+ puts "<< [#{end_time} %.1fs] #{msg}" % [end_time - start_time]
36
+ end
37
+
38
+ config_file = File.join(ENV['HOME'], '.clone_remote_db.yml')
39
+ if File.exist?(config_file)
40
+ CONFIG = YAML.load(File.read(config_file))
41
+ else
42
+ CONFIG = { 'environments' => {} }
43
+ end
44
+
45
+ # merge the CLI options with the configuration from the YAML file
46
+ cmd = ARGV.shift
47
+ if cmd
48
+ cmd_config = CONFIG['environments'][cmd]
49
+ if cmd_config
50
+ cmd_config = cmd_config.symbolize_keys
51
+ loader.options.merge!(cmd_config.select { |k,v| cli_opts[k].nil? })
52
+ else
53
+ loader.die("unknown subcommand #{cmd}")
54
+ end
55
+ end
56
+
57
+ opts = loader.options
58
+
59
+ [:local_db, :remote_db, :host].each do |opt|
60
+ loader.die(opt, "is required") unless opts[opt]
61
+ end
62
+
63
+ loader.die(:import_only, "file not found") if opts[:import_only] && !File.exist?(opts[:import_only])
64
+
65
+ # gzip compression options
66
+ gzip_opts = opts[:gzip_opts]
67
+
68
+ # pg_dump exclude-table-data tables
69
+ pg_exclude_data = opts[:pg_exclude_data]
70
+
71
+ db_name = opts[:remote_db]
72
+ local_dbname = opts[:local_db]
73
+ host = opts[:host]
74
+
75
+ # pg_dump setup
76
+ pg_user = opts[:remote_user]
77
+ pg_options = opts[:pg_dump_opts] << "-x #{escape_opts(db_name)}"
78
+ pg_options_joined = pg_exclude_data.map { |t| '--exclude-table-data=' + escape_opts(t) }.
79
+ join(' ') + ' ' + pg_options.join(' ')
80
+ pg_dump_part = "pg_dump #{pg_options_joined}"
81
+
82
+ local_dest = loader.replace_variables(opts[:local_dest])
83
+ now = Time.now
84
+ output_file = File.expand_path(now.strftime(local_dest))
85
+ dir_name = File.dirname(output_file)
86
+
87
+ FileUtils.mkdir_p(dir_name) unless opts[:dry_run]
88
+
89
+ gzip_part = "gzip #{escape_opts(opts[:gzip_opts])}"
90
+ sudo_part = %~sudo -u #{pg_user} #{pg_dump_part} | #{gzip_part}~
91
+
92
+ if opts[:import_only]
93
+ output_file = loader.replace_variables(opts[:import_only])
94
+ else
95
+ ssh_cmd = %~ssh #{host} "#{sudo_part}" > #{output_file}~
96
+ run_cmd(ssh_cmd, "downloading #{db_name}", opts)
97
+ end
98
+
99
+ import_cmd = "gunzip --stdout #{output_file} | psql #{local_dbname}"
100
+ run_cmd(import_cmd, "importing #{db_name} into #{local_dbname}", opts)
101
+
102
+ # vim: set ft=ruby :
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'clone_remote_db/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "clone_remote_db"
8
+ spec.version = CloneRemoteDb::VERSION
9
+ spec.authors = ["Josh McDade"]
10
+ spec.email = ["josh.ncsu@gmail.com"]
11
+ spec.summary = %q{Helper script to dump, download, and remote a remote PostgreSQL database}
12
+ spec.homepage = "https://github.com/joshm1/clone_remote_db"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.5"
21
+ spec.add_development_dependency "rake"
22
+ end
@@ -0,0 +1,13 @@
1
+ # monkey patch functionally similar to the one provided by ActiveSupport
2
+ class Hash
3
+ # recursively symolizes keys in a Hash that are strings
4
+ # does not modify the original hash; returns a new hash
5
+ def symbolize_keys
6
+ self.to_a.reduce({}) do |acc, (key, val)|
7
+ val = symbolize_keys(val) if val.is_a?(Hash)
8
+ key = key.to_sym if key.is_a?(String)
9
+ acc[key] = val
10
+ acc
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,83 @@
1
+ require 'optparse'
2
+
3
+ class CloneRemoteDb::Loader
4
+ attr_reader :script
5
+
6
+ def initialize(script)
7
+ @script = script
8
+ end
9
+
10
+ def parse_opts!
11
+ OptionParser.new do |opts|
12
+ banner(opts)
13
+ version(opts)
14
+ opt(opts, :local_db, "Local postgresql database name", arg: 'DBNAME', short: '-l')
15
+ opt(opts, :remote_db, "Remote postgresql database name", arg: 'DBNAME', short: '-r')
16
+ opt(opts, :host, "Host of the postgresql database", arg: 'HOST', short: '-h')
17
+ opt(opts, :import_only, "Skip download and provide path to dump.sql.gz file", arg: 'PATH')
18
+ opt(opts, :local_dest, "The path to save the dump.sql.gz file to", arg: 'PATH')
19
+ opt(opts, :dry_run, "Don't actually do anything", short: '-n')
20
+ end.parse!
21
+ options
22
+ end
23
+
24
+ def die(key_or_msg, msg = nil, exit_code = -1)
25
+ if key_or_msg.is_a?(String)
26
+ $stderr.puts "Error: #{key_or_msg}"
27
+ else
28
+ arg = '--' + key_or_msg.to_s.gsub('_', '-')
29
+ $stderr.puts "Error: argument #{arg} #{msg}."
30
+ end
31
+ exit(exit_code)
32
+ end
33
+
34
+ def banner(opts)
35
+ opts.banner = <<-EOS
36
+ Download and import a remote (PostgreSQL) database into a local database.
37
+
38
+ Usage:
39
+ #{script} [options]
40
+
41
+ Options:
42
+ EOS
43
+ end
44
+
45
+ def version(opts)
46
+ opts.on('--version', 'Show the version') do
47
+ io.puts "clone_remote_db #{CloneRemoteDb::VERSION} (c) Josh McDade"
48
+ exit(0)
49
+ end
50
+ end
51
+
52
+ def opt(opts, key, msg, arg_opts = {})
53
+ arg = "--#{key.to_s.gsub('_', '-')}"
54
+ arg << " #{arg_opts[:arg]}" if arg_opts[:arg]
55
+ on_args = [arg, msg]
56
+ on_args.unshift(arg_opts[:short]) if arg_opts[:short]
57
+ opts.on(*on_args, msg) do |v|
58
+ options[key] = v
59
+ end
60
+ end
61
+
62
+ def options
63
+ @options ||= {}.merge(defaults)
64
+ end
65
+
66
+ def defaults
67
+ {
68
+ local_dest: '~/pg_dumps/{remote_db}/%Y-%m/%Y-%m-%d_%H.%M.sql.gz',
69
+ gzip_opts: %w(-9 --stdout),
70
+ pg_dump_opts: %w(-c -O),
71
+ pg_exclude_data: %w(versions),
72
+ remote_user: 'postgres'
73
+ }
74
+ end
75
+
76
+ def replace_variables(str)
77
+ new_str = str
78
+ %w(local_db remote_db remote_user).each do |arg|
79
+ new_str = new_str.gsub('{' + arg + '}', options[arg.to_sym])
80
+ end
81
+ new_str
82
+ end
83
+ end
@@ -0,0 +1,3 @@
1
+ module CloneRemoteDb
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path("../clone_remote_db/hash", __FILE__)
2
+
3
+ module CloneRemoteDb
4
+ lib = File.expand_path('../clone_remote_db', __FILE__)
5
+ autoload :Loader, "#{lib}/loader.rb"
6
+ autoload :Version, "#{lib}/version.rb"
7
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clone_remote_db
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Josh McDade
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-03 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: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email:
43
+ - josh.ncsu@gmail.com
44
+ executables:
45
+ - clone_remote_db
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - .gitignore
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - bin/clone_remote_db
55
+ - clone_remote_db.gemspec
56
+ - lib/clone_remote_db.rb
57
+ - lib/clone_remote_db/hash.rb
58
+ - lib/clone_remote_db/loader.rb
59
+ - lib/clone_remote_db/version.rb
60
+ homepage: https://github.com/joshm1/clone_remote_db
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.0.3
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Helper script to dump, download, and remote a remote PostgreSQL database
84
+ test_files: []