clone_remote_db 0.0.1

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
+ 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: []