snatchdb 1.0.0

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.
data/bin/snatch ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'rubygems'
7
+ require 'yaml'
8
+ require 'optparse'
9
+ require 'snatch'
10
+
11
+ SAMPLE_SCHEMA = <<END
12
+ ---
13
+ host: YOUR_HOSTNAME
14
+ user: YOUR_SSH_USER
15
+ password: YOUR_SSH_PASSWORD
16
+ db_user: MYSQL_USER
17
+ db_password: MYSQL_PASSWORD
18
+ db_list:
19
+ - database1
20
+ - database2
21
+ END
22
+
23
+ optparse = OptionParser.new do |opts|
24
+ opts.banner = "Usage: snatch CONFIG.yml"
25
+ opts.on('-i', '--info', 'Display this information.') { puts opts ; exit }
26
+ opts.on('-v', '--version', 'Show version') { puts "Snatch v#{Snatch::VERSION}" ; exit }
27
+ opts.on('-n', '--new NAME', 'Create a new config file') do |name|
28
+ unless name =~ /^[a-z\d\_\-]{2,64}$/i
29
+ puts "Name should be in format: [A-Za-z0-9\-\_]{2,64}"
30
+ exit
31
+ end
32
+
33
+ file = "#{Dir.pwd}/#{name}.yml"
34
+ if File.exists?(file)
35
+ puts "File already exists!" ; exit
36
+ end
37
+
38
+ File.open(file, 'w') { |f| f.write(SAMPLE_SCHEMA) }
39
+ puts "File #{file} has been created. Edit and snatch!" ; exit
40
+ end
41
+ end
42
+
43
+ begin
44
+ optparse.parse!
45
+
46
+ unless ARGV.empty?
47
+ file = File.expand_path(ARGV.shift)
48
+ unless File.exists?(file)
49
+ puts "File #{file} was not found!"
50
+ exit
51
+ end
52
+
53
+ config = YAML.load_file(file)
54
+ config.symbolize!
55
+ Snatch.run(config)
56
+ else
57
+ puts optparse
58
+ end
59
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
60
+ puts optparse
61
+ exit
62
+ rescue Interrupt
63
+ puts "Interrupted."
64
+ rescue StandardError, Snatch::CredentialsError, Snatch::NoDatabaseError => ex
65
+ puts "ERROR: #{ex.message}"
66
+ end
@@ -0,0 +1,15 @@
1
+ module Snatch
2
+ class TransferHandler
3
+ def on_open(downloader, file)
4
+ puts "ftp: downloading: #{file.remote} -> #{file.local}"
5
+ end
6
+
7
+ def on_close(downloader, file)
8
+ puts "ftp: finished downloading #{File.basename(file.remote)}"
9
+ end
10
+
11
+ def on_finish(downloader)
12
+ puts "ftp: file saved."
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ class Hash
2
+ def symbolize!
3
+ keys.each do |key|
4
+ self[(key.to_sym rescue key) || key] = delete(key)
5
+ end
6
+ self
7
+ end
8
+ end
@@ -0,0 +1,90 @@
1
+ module Snatch
2
+ class CredentialsError < Exception ; end
3
+ class NoDatabaseError < Exception ; end
4
+
5
+ class Session
6
+ attr_reader :config, :filename
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ @ssh = nil
11
+ @filename = "#{@config[:host]}_#{Time.now.strftime("%Y%m%d%H%M%S")}.sql.gz"
12
+ end
13
+
14
+ def run!
15
+ connect
16
+ test
17
+ dump
18
+ download
19
+ cleanup
20
+ disconnect
21
+ end
22
+
23
+ private
24
+
25
+ # Connect to remote server
26
+ def connect
27
+ puts "ssh: connecting..."
28
+ begin
29
+ @ssh = Net::SSH.start(@config[:host], @config[:user],:password => @config[:password])
30
+ rescue Net::SSH::AuthenticationFailed
31
+ raise CredentialsError, 'Invalid SSH user or password'
32
+ end
33
+ puts "ssh: connected."
34
+ end
35
+
36
+ # Close ssh connection
37
+ def disconnect
38
+ @ssh.close
39
+ puts "ssh: disconnected."
40
+ end
41
+
42
+ # Check configuration options
43
+ def test
44
+ resp = @ssh.exec!("mysql --execute='SHOW DATABASES;' --user=#{@config[:db_user]} --password=#{@config[:db_password]}")
45
+ if resp =~ /ERROR 1045/
46
+ raise CredentialsError, 'Invalid MySQL user or password'
47
+ end
48
+
49
+ list = resp.split("\n").map { |s| s.strip }.select { |s| !s.empty? }.drop(1)
50
+ diff = @config[:db_list] - list
51
+
52
+ unless diff.empty?
53
+ raise NoDatabaseError, "Database(s) not found: #{diff.join(', ')}"
54
+ end
55
+ end
56
+
57
+ # Generate MySQL database dump
58
+ def dump
59
+ cmd = []
60
+ cmd << "--user=#{@config[:db_user]}"
61
+ cmd << "--password=#{@config[:db_password]}" unless @config[:db_password].strip.empty?
62
+ cmd << "--databases #{@config[:db_list].join(' ')}"
63
+ cmd << "--add-drop-database"
64
+ cmd << "--add-drop-table"
65
+ cmd << "--compact"
66
+ cmd = "mysqldump #{cmd.join(' ')} | gzip --best > /tmp/#{filename}"
67
+
68
+ puts "mysql: creating a dump..."
69
+ @ssh.exec!(cmd)
70
+ puts "mysql: done."
71
+ end
72
+
73
+ # Cleanup dump
74
+ def cleanup
75
+ @ssh.exec!("rm -f /tmp/#{filename}")
76
+ end
77
+
78
+ # Fetch remote dump to local filesystem
79
+ def download
80
+ Net::SFTP.start(@config[:host], @config[:user], :password => @config[:password]) do |sftp|
81
+ t = sftp.download!(
82
+ "/tmp/#{filename}", # Remote file
83
+ "#{Dir.pwd}/#{filename}", # Local file
84
+ :progress => TransferHandler.new # Handler
85
+ )
86
+ t.wait
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,3 @@
1
+ module Snatch
2
+ VERSION = '1.0.0'
3
+ end
data/lib/snatch.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'net/ssh'
2
+ require 'net/sftp'
3
+
4
+ require 'snatch/version'
5
+ require 'snatch/hash'
6
+ require 'snatch/handler'
7
+ require 'snatch/session'
8
+
9
+ module Snatch
10
+ def self.run(config)
11
+ s = Snatch::Session.new(config)
12
+ s.run!
13
+ end
14
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: snatchdb
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Dan Sosedoff
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-09 00:00:00 -06:00
19
+ default_executable: snatch
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: net-ssh
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - "="
28
+ - !ruby/object:Gem::Version
29
+ hash: 63
30
+ segments:
31
+ - 2
32
+ - 0
33
+ - 24
34
+ version: 2.0.24
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: net-sftp
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - "="
44
+ - !ruby/object:Gem::Version
45
+ hash: 5
46
+ segments:
47
+ - 2
48
+ - 0
49
+ - 5
50
+ version: 2.0.5
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ description: Remote database downloader
54
+ email: dan.sosedoff@gmail.com
55
+ executables:
56
+ - snatch
57
+ extensions: []
58
+
59
+ extra_rdoc_files: []
60
+
61
+ files:
62
+ - bin/snatch
63
+ - lib/snatch.rb
64
+ - lib/snatch/version.rb
65
+ - lib/snatch/hash.rb
66
+ - lib/snatch/handler.rb
67
+ - lib/snatch/session.rb
68
+ has_rdoc: true
69
+ homepage: http://github.com/sosedoff/snatch
70
+ licenses: []
71
+
72
+ post_install_message:
73
+ rdoc_options: []
74
+
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 3
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ requirements: []
96
+
97
+ rubyforge_project:
98
+ rubygems_version: 1.4.1
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: Snatch is a remote database downloader via SSH protocol
102
+ test_files: []
103
+