db_dumper 0.5

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
+ SHA256:
3
+ metadata.gz: a7bcc6d6d41f50f4034725ff07c8986e2e6e1f0016725f2cd0787348eb4f5a84
4
+ data.tar.gz: c7ac55eed36664fa46d3ecfc3e5f098be027207727d3748581450a1f57239f7d
5
+ SHA512:
6
+ metadata.gz: 1fefff8a2c3150b49b7625a1aeae6eaa2ba2a2cd786e2f166977fcf6350d09b17f4093afeac5e4a5c2e8c343b8bbd384c14e2481f99265e4249b1bd788e80ea8
7
+ data.tar.gz: 23a304879c10ffebb575300751966adbd425ae7c2405e36cf2f6eb42919972efe7b0d20d22c246c57b0663aa8e1855b956099972e50691757621ffb6ca3596be
data/README.md ADDED
@@ -0,0 +1 @@
1
+ db_dumper gem readme
data/lib/db_dumper.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ require 'net/ssh'
6
+ require 'net/scp'
7
+ require 'active_record'
8
+
9
+ require_relative 'db_dumper/configuration'
10
+ require_relative 'db_dumper/query_builder'
11
+ require_relative 'db_dumper/remote_machine'
12
+
13
+ module DbDumper
14
+ module_function def dump(config_file_path = 'config/application.yml', &block)
15
+ config = Configuration.new(config_file_path)
16
+ sql = QueryBuilder.to_sql(config, &block)
17
+ RemoteMachine.new(config, sql).dump
18
+ end
19
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'configuration/postgres'
4
+
5
+ module DbDumper
6
+ class Configuration
7
+ SshUser = Struct.new(:name, :host, :ssh_keys, :passphrase, keyword_init: true)
8
+ RemoteDB = Struct.new(:adapter, :host, :port, :database, :username, :password,
9
+ :dump_schema_options, :dump_data_options,
10
+ keyword_init: true)
11
+ RemoteMachine = Struct.new(:dest_path, keyword_init: true) do
12
+ def data_path
13
+ "#{dest_path}/csv"
14
+ end
15
+ end
16
+ LocalMachine = Struct.new(:dest_path, keyword_init: true)
17
+
18
+ DB_UTILS = {
19
+ 'postgres' => Postgres
20
+ }
21
+
22
+ attr_reader :config_file_path
23
+
24
+ def initialize(config_file_path)
25
+ @config_file_path = config_file_path
26
+ end
27
+
28
+ def log(message)
29
+ logger.info(message)
30
+ end
31
+
32
+ def db_utils
33
+ @db_utils ||= begin
34
+ utils = DB_UTILS[remote_db.adapter]
35
+ raise 'Unknown adapter for remote_db:adapter: check application.yml' unless utils
36
+ utils.new(remote_db)
37
+ end
38
+ end
39
+
40
+ def ssh_user
41
+ @ssh_user ||= SshUser.new(loaded_file['ssh_user'])
42
+ end
43
+
44
+ def remote_machine
45
+ @remote_machine ||= RemoteMachine.new(loaded_file['remote_machine'])
46
+ end
47
+
48
+ def remote_db
49
+ @remote_db ||= RemoteDB.new(loaded_file['remote_db'])
50
+ end
51
+
52
+ def local_machine
53
+ @local_machine ||= LocalMachine.new(loaded_file['local_machine'])
54
+ end
55
+
56
+ private
57
+
58
+ def logger
59
+ @logger ||= Logger.new(STDOUT)
60
+ end
61
+
62
+ def loaded_file
63
+ @loaded_file ||= YAML.load_file(config_file_path)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,31 @@
1
+ module DbDumper
2
+ class Configuration
3
+ class Postgres
4
+ attr_reader :db_config
5
+
6
+ def initialize(db_config)
7
+ @db_config = db_config
8
+ end
9
+
10
+ def dump_schema_command(dump_schema_file_path)
11
+ util_command('pg_dump', "#{db_config.dump_schema_options} -f #{dump_schema_file_path}")
12
+ end
13
+
14
+ def dump_data_command(dump_data_file_path)
15
+ util_command('psql', "#{db_config.dump_data_options} -f #{dump_data_file_path}")
16
+ end
17
+
18
+ private
19
+
20
+ def util_command(util, command)
21
+ <<-CMD.squish
22
+ PGPASSWORD=#{db_config.password} #{util}
23
+ -h #{db_config.host}
24
+ -U #{db_config.username}
25
+ -d #{db_config.database}
26
+ #{command}
27
+ CMD
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,52 @@
1
+ require_relative 'query_builder/table'
2
+ require_relative 'query_builder/query'
3
+
4
+ module DbDumper
5
+ class QueryBuilder
6
+ attr_reader :config
7
+
8
+ def self.to_sql(config, &block)
9
+ establish_connection
10
+
11
+ instance = new(config)
12
+ instance.instance_eval(&block)
13
+ instance.to_sql
14
+ end
15
+
16
+ def initialize(config)
17
+ @config = config
18
+ end
19
+
20
+ def self.establish_connection
21
+ return if ActiveRecord::Base.connected?
22
+
23
+ ActiveRecord::Migration.verbose = false
24
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
25
+ end
26
+
27
+ def dump(query)
28
+ query = q(query) if query.is_a?(String)
29
+ query.tap { |q| queries << q }
30
+ end
31
+
32
+ def q(table_name)
33
+ Query.new Table.from(table_name.to_s)
34
+ end
35
+
36
+ def to_sql
37
+ queries.map do |query|
38
+ "\\COPY (#{query.to_sql}) TO '#{copy_path}/#{query.table_name}.csv';"
39
+ end.join($RS)
40
+ end
41
+
42
+ private
43
+
44
+ def queries
45
+ @queries ||= []
46
+ end
47
+
48
+ def copy_path
49
+ config.remote_machine.dest_path
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,31 @@
1
+ module DbDumper
2
+ class QueryBuilder
3
+ class Query
4
+ attr_reader :table, :ar
5
+
6
+ delegate :table_name, to: :table
7
+
8
+ def initialize(table)
9
+ @table = table
10
+ @ar = @table.ar.all
11
+ end
12
+
13
+ def where(*args)
14
+ tap { @ar = @ar.where(*args) }
15
+ end
16
+
17
+ def joins(*args)
18
+ raise 'Only simple string for joins supported' unless args.size == 1 && args[0].is_a?(String)
19
+ tap { @ar = @ar.joins(*args) }
20
+ end
21
+
22
+ def select(*args)
23
+ tap { @ar = @ar.select(*args) }
24
+ end
25
+
26
+ def to_sql
27
+ @ar.to_sql
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ module DbDumper
2
+ class QueryBuilder
3
+ class Table
4
+ attr_reader :table_name
5
+
6
+ @tables = {}
7
+
8
+ def self.from(table_name)
9
+ @tables[table_name] ||= begin
10
+ ActiveRecord::Migration.create_table(table_name)
11
+ new(table_name)
12
+ end
13
+ end
14
+
15
+ def ar
16
+ @ar ||= Class.new(ActiveRecord::Base).tap do |klass|
17
+ klass.table_name = table_name
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def initialize(table_name)
24
+ @table_name = table_name
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,125 @@
1
+ module DbDumper
2
+ class RemoteMachine
3
+ attr_reader :config, :sql
4
+
5
+ def initialize(config, sql)
6
+ @config = config
7
+ @sql = sql
8
+ end
9
+
10
+ def dump
11
+ config.log('save sql commands to local machine')
12
+ save_commands_sql_to_tmp_file
13
+
14
+ Net::SSH.start(*ssh_credentials) do |ssh|
15
+ config.log('upload commands to remote machine')
16
+ upload_commands_sql_to_remote_machine(ssh)
17
+
18
+ config.log('dump schema on remote machine')
19
+ dump_schema(ssh)
20
+
21
+ config.log('dump data on remote machine')
22
+ dump_data(ssh)
23
+
24
+ config.log('download schema to local machine')
25
+ download_schema(ssh)
26
+
27
+ config.log('download data to local machine')
28
+ download_data(ssh)
29
+
30
+ config.log('clean remote machine')
31
+ clean_remote_machine(ssh)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # Upload
38
+ def save_commands_sql_to_tmp_file
39
+ File.open(local_commands_sql_file_path, 'w') { |f| f.write(sql) }
40
+ end
41
+
42
+ def upload_commands_sql_to_remote_machine(ssh)
43
+ ssh.scp.upload!(local_commands_sql_file_path, remote_commands_sql_file_path)
44
+ File.delete(local_commands_sql_file_path)
45
+ end
46
+
47
+ def remote_commands_sql_file_path
48
+ "#{remote_machine_dest_path}/#{commands_sql_fname}"
49
+ end
50
+
51
+ def local_commands_sql_file_path
52
+ "#{dest_path}/#{commands_sql_fname}"
53
+ end
54
+
55
+ def commands_sql_fname
56
+ @commands_sql_fname ||= "#{Digest::MD5.hexdigest(sql)}.sql"
57
+ end
58
+
59
+ # Schema
60
+
61
+ def dump_schema(ssh)
62
+ ssh.exec! db_utils.dump_schema_command(remote_machine_schema_file_path)
63
+ end
64
+
65
+ def download_schema(ssh)
66
+ ssh.scp.download!(remote_machine_schema_file_path, dest_path)
67
+ end
68
+
69
+ def remote_machine_schema_file_path
70
+ "#{remote_machine_dest_path}/#{dump_schema_fname}"
71
+ end
72
+
73
+ def dump_schema_fname
74
+ 'schema_dump.sql'
75
+ end
76
+
77
+ # Data
78
+
79
+ def dump_data(ssh)
80
+ ssh.exec!("mkdir -p #{remote_machine_data_path}")
81
+ ssh.exec! db_utils.dump_data_command(remote_commands_sql_file_path)
82
+ end
83
+
84
+ def download_data(ssh)
85
+ ssh.scp.download!(remote_machine_data_path, dest_path, recursive: true)
86
+ end
87
+
88
+ def clean_remote_machine(ssh)
89
+ ssh.exec! "rm #{remote_commands_sql_file_path}"
90
+ ssh.exec! "rm #{remote_machine_schema_file_path}"
91
+ ssh.exec! "rm -rf #{remote_machine_data_path}"
92
+ end
93
+
94
+ #
95
+
96
+ def db_utils
97
+ config.db_utils
98
+ end
99
+
100
+ def remote_machine_dest_path
101
+ config.remote_machine.dest_path
102
+ end
103
+
104
+ def remote_machine_data_path
105
+ config.remote_machine.data_path
106
+ end
107
+
108
+ def ssh_credentials
109
+ [
110
+ ssh_user.host,
111
+ ssh_user.name,
112
+ keys: ssh_user.ssh_keys,
113
+ passphrase: ssh_user.passphrase
114
+ ]
115
+ end
116
+
117
+ def ssh_user
118
+ config.ssh_user
119
+ end
120
+
121
+ def dest_path
122
+ config.local_machine.dest_path
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,3 @@
1
+ module DbDumper
2
+ VERSION = '0.5'
3
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: db_dumper
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.5'
5
+ platform: ruby
6
+ authors:
7
+ - Alexey Lukyanov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-10-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 5.2.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '5.2'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 5.2.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: sqlite3
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: pg
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: net-ssh
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: net-scp
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ description: Dump database schema and chosen data from remote machine
90
+ email: alukyanov.dev@gmail.com
91
+ executables: []
92
+ extensions: []
93
+ extra_rdoc_files: []
94
+ files:
95
+ - README.md
96
+ - lib/db_dumper.rb
97
+ - lib/db_dumper/configuration.rb
98
+ - lib/db_dumper/configuration/postgres.rb
99
+ - lib/db_dumper/query_builder.rb
100
+ - lib/db_dumper/query_builder/query.rb
101
+ - lib/db_dumper/query_builder/table.rb
102
+ - lib/db_dumper/remote_machine.rb
103
+ - lib/db_dumper/version.rb
104
+ homepage: http://rubygems.org/gems/db_dumper
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 1.9.3
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.7.3
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: Dump database schema and chosen data from remote machine
128
+ test_files: []