pg_export 0.5.1 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +2 -8
- data/bin/pg_export +39 -19
- data/lib/pg_export/aes/base.rb +47 -0
- data/lib/pg_export/aes/decryptor.rb +13 -0
- data/lib/pg_export/aes/encryptor.rb +13 -0
- data/lib/pg_export/aes.rb +3 -0
- data/lib/pg_export/bash/adapter.rb +31 -0
- data/lib/pg_export/bash/factory.rb +23 -0
- data/lib/pg_export/bash/repository.rb +18 -0
- data/lib/pg_export/boot_container.rb +69 -0
- data/lib/pg_export/build_logger.rb +19 -0
- data/lib/pg_export/configuration.rb +11 -53
- data/lib/pg_export/{entities/dump.rb → dump.rb} +18 -4
- data/lib/pg_export/errors.rb +3 -3
- data/lib/pg_export/ftp/adapter.rb +41 -0
- data/lib/pg_export/ftp/connection.rb +40 -0
- data/lib/pg_export/ftp/repository.rb +40 -0
- data/lib/pg_export/roles/colourable_string.rb +19 -0
- data/lib/pg_export/roles/human_readable.rb +17 -0
- data/lib/pg_export/roles/interactive.rb +98 -0
- data/lib/pg_export/roles/validatable.rb +24 -0
- data/lib/pg_export/services/create_and_export_dump.rb +20 -0
- data/lib/pg_export/version.rb +1 -1
- data/lib/pg_export.rb +18 -56
- data/pg_export.gemspec +2 -1
- metadata +49 -31
- data/lib/pg_export/includable_modules/colourable_string.rb +0 -17
- data/lib/pg_export/includable_modules/dump/size_human.rb +0 -15
- data/lib/pg_export/includable_modules/interactive.rb +0 -97
- data/lib/pg_export/includable_modules/logging.rb +0 -31
- data/lib/pg_export/includable_modules/services_container.rb +0 -41
- data/lib/pg_export/services/aes/base.rb +0 -26
- data/lib/pg_export/services/aes/decryptor.rb +0 -12
- data/lib/pg_export/services/aes/encryptor.rb +0 -12
- data/lib/pg_export/services/aes.rb +0 -28
- data/lib/pg_export/services/bash_utils.rb +0 -32
- data/lib/pg_export/services/dump_storage.rb +0 -48
- data/lib/pg_export/services/ftp_adapter.rb +0 -38
- data/lib/pg_export/services/ftp_connection.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e6bcca69be07806c87aa5e971292269c6e24ea3
|
4
|
+
data.tar.gz: 421af6b9f3430c9a5e3431da6f71d78b91463157
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2d7b152087be40ae99ae42a5e3acfb90b1aacef6780bfc6ec255883db9230c77455ae17065226e0aad91971ab74e89743badd74c4dd3c7f3571638fb8837ffe
|
7
|
+
data.tar.gz: 633e2d33af88ee049ee3ba5490ce228391ee445569533b8752b87ec5c29435bd1bdc0d518801b6c889e4a2898d08127d99a513d6239b1b54de3de1adea28b8f5
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
[](https://badge.fury.io/rb/pg_export)
|
4
4
|
[](https://travis-ci.org/maicher/pg_export)
|
5
5
|
[](https://codeclimate.com/github/maicher/pg_export)
|
6
|
-
[](https://codeclimate.com/github/maicher/pg_export/coverage)
|
7
6
|
|
8
7
|
CLI for creating and exporting PostgreSQL dumps to FTP.
|
9
8
|
|
@@ -86,18 +85,13 @@ __Step 3.__ Configure how many dumps should be kept in FTP (optional).
|
|
86
85
|
__Step 4.__ Print the configuration to verify whether env variables has been loaded.
|
87
86
|
|
88
87
|
$ pg_export --configuration
|
89
|
-
=> database:
|
90
|
-
|
91
|
-
dump_password: k40***
|
92
|
-
ftp_host: yourftp.example.com
|
93
|
-
ftp_user: user
|
94
|
-
ftp_password: pass***
|
88
|
+
=> {:database=>"undefined", :keep_dumps=>10, :dump_encryption_key=>"k4***", :ftp_host=>"yourftp.example.com",
|
89
|
+
:ftp_user=>"user", :ftp_password=>"pass***", :logger_format=>:plain}
|
95
90
|
|
96
91
|
__Step 5.__ Try connecting to FTP to verify the connection.
|
97
92
|
|
98
93
|
$ pg_export --ftp
|
99
94
|
=> Connect to yourftp.example.com
|
100
|
-
Close FTP
|
101
95
|
|
102
96
|
__Step 6.__ Perform database export.
|
103
97
|
|
data/bin/pg_export
CHANGED
@@ -3,34 +3,38 @@
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'ostruct'
|
5
5
|
|
6
|
-
require 'cli_spinnable'
|
7
|
-
|
8
6
|
require 'pg_export'
|
9
|
-
require 'pg_export/
|
10
|
-
require 'pg_export/includable_modules/interactive'
|
7
|
+
require 'pg_export/roles/colourable_string'
|
11
8
|
|
12
|
-
config =
|
13
|
-
|
9
|
+
config = {
|
10
|
+
database: 'undefined',
|
11
|
+
keep_dumps: ENV['KEEP_DUMPS'],
|
12
|
+
dump_encryption_key: ENV['DUMP_ENCRYPTION_KEY'],
|
13
|
+
ftp_host: ENV['BACKUP_FTP_HOST'],
|
14
|
+
ftp_user: ENV['BACKUP_FTP_USER'],
|
15
|
+
ftp_password: ENV['BACKUP_FTP_PASSWORD'],
|
16
|
+
logger_format: :plain,
|
17
|
+
interactive: false
|
18
|
+
}
|
14
19
|
|
15
20
|
option_parser = OptionParser.new do |opts|
|
16
21
|
opts.banner = 'Usage: pg_export [options]'
|
17
22
|
|
18
|
-
opts.on('-d', '--database DATABASE', '[Required] Name of the database to export') do |database|
|
19
|
-
config
|
23
|
+
opts.on('-d', '--database DATABASE', String, '[Required] Name of the database to export') do |database|
|
24
|
+
config[:database] = database
|
20
25
|
end
|
21
26
|
|
22
|
-
opts.on('-k', '--keep [KEEP]', Integer, "[Optional] Number of dump files to keep on FTP (default: #{config
|
23
|
-
config
|
27
|
+
opts.on('-k', '--keep [KEEP]', Integer, "[Optional] Number of dump files to keep on FTP (default: #{config[:keep_dumps]})") do |keep|
|
28
|
+
config[:keep_dumps] = keep
|
24
29
|
end
|
25
30
|
|
26
31
|
opts.on('-t', '--timestamped', '[Optional] Enables log messages with timestamps') do
|
27
|
-
|
32
|
+
config[:logger_format] = :timestamped
|
28
33
|
end
|
29
34
|
|
30
35
|
opts.on('-i', '--interactive', 'Interactive, command line mode, for restoring dumps into databases') do
|
31
|
-
|
32
|
-
config
|
33
|
-
pg_export.extend(PgExport::Interactive)
|
36
|
+
config[:logger_format] = :muted
|
37
|
+
config[:interactive] = true
|
34
38
|
end
|
35
39
|
|
36
40
|
opts.on('-h', '--help', 'Show this message') do
|
@@ -41,21 +45,37 @@ option_parser = OptionParser.new do |opts|
|
|
41
45
|
opts.separator "\nSetting can be verified by running following commands:"
|
42
46
|
|
43
47
|
opts.on('-c', '--configuration', 'Prints the configuration') do
|
44
|
-
puts config
|
48
|
+
puts config
|
45
49
|
exit
|
46
50
|
end
|
47
51
|
|
48
52
|
opts.on('-f', '--ftp', 'Tries connecting to FTP to verify the connection') do
|
49
|
-
PgExport::
|
53
|
+
PgExport::Ftp::Connection.new(
|
54
|
+
host: config[:ftp_host],
|
55
|
+
user: config[:ftp_user],
|
56
|
+
password: config[:ftp_password],
|
57
|
+
logger: PgExport::BuildLogger.call(stream: $stdout, format: :plain)
|
58
|
+
).open
|
50
59
|
exit
|
51
60
|
end
|
52
61
|
end
|
53
62
|
|
54
63
|
begin
|
55
64
|
option_parser.parse!
|
56
|
-
|
57
|
-
|
58
|
-
|
65
|
+
|
66
|
+
PgExport.new(
|
67
|
+
dump_encryption_key: config[:dump_encryption_key],
|
68
|
+
ftp_host: config[:ftp_host],
|
69
|
+
ftp_user: config[:ftp_user],
|
70
|
+
ftp_password: config[:ftp_password],
|
71
|
+
logger_format: config[:logger_format],
|
72
|
+
interactive: config[:interactive]
|
73
|
+
).call(
|
74
|
+
config[:database],
|
75
|
+
config[:keep_dumps]
|
76
|
+
)
|
77
|
+
rescue OptionParser::MissingArgument, PgExport::PgExportError, ArgumentError => e
|
78
|
+
using PgExport::Roles::ColourableString
|
59
79
|
puts "Error: #{e}".red
|
60
80
|
puts option_parser
|
61
81
|
rescue PgExport::PgDumpError => e
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'pg_export/dump'
|
3
|
+
|
4
|
+
class PgExport
|
5
|
+
module Aes
|
6
|
+
class Base
|
7
|
+
ALGORITHM = 'AES-128-CBC'.freeze
|
8
|
+
|
9
|
+
def initialize(key:, logger:)
|
10
|
+
@key, @logger = key, logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(source_dump)
|
14
|
+
target_dump = Dump.new(name: target_dump_name, db_name: source_dump.db_name)
|
15
|
+
copy(from: source_dump, to: target_dump)
|
16
|
+
logger.info "Create #{target_dump}"
|
17
|
+
target_dump
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :key, :logger
|
23
|
+
|
24
|
+
def copy(from:, to:)
|
25
|
+
cipher.reset
|
26
|
+
to.open(:write) do |f|
|
27
|
+
from.each_chunk do |chunk|
|
28
|
+
f << cipher.update(chunk)
|
29
|
+
end
|
30
|
+
f << cipher.final
|
31
|
+
end
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def cipher
|
36
|
+
@cipher ||= build_cipher
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_cipher
|
40
|
+
OpenSSL::Cipher.new(ALGORITHM).tap do |cipher|
|
41
|
+
cipher.public_send(cipher_type)
|
42
|
+
cipher.key = key
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'pg_export/errors'
|
3
|
+
|
4
|
+
class PgExport
|
5
|
+
module Bash
|
6
|
+
class Adapter
|
7
|
+
def get(path, db_name)
|
8
|
+
popen("pg_dump -Fc --file #{path} #{db_name}") do |errors|
|
9
|
+
raise PgDumpError, errors unless errors.empty?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def persist(path, db_name)
|
14
|
+
popen("pg_restore -c -d #{db_name} #{path}") do |errors|
|
15
|
+
raise PgRestoreError, errors if /FATAL/ =~ errors
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def popen(command)
|
22
|
+
Open3.popen3(command) do |_, _, err|
|
23
|
+
errors = err.read
|
24
|
+
yield errors
|
25
|
+
end
|
26
|
+
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'pg_export/dump'
|
3
|
+
|
4
|
+
class PgExport
|
5
|
+
module Bash
|
6
|
+
class Factory
|
7
|
+
def initialize(adapter:, logger:)
|
8
|
+
@adapter, @logger = adapter, logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def build_dump(db_name)
|
12
|
+
dump = Dump.new(name: 'Dump', db_name: db_name)
|
13
|
+
adapter.get(dump.path, dump.db_name)
|
14
|
+
logger.info "Create #{dump}"
|
15
|
+
dump
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :adapter, :logger
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class PgExport
|
2
|
+
module Bash
|
3
|
+
class Repository
|
4
|
+
def initialize(adapter:, logger:)
|
5
|
+
@adapter, @logger = adapter, logger
|
6
|
+
end
|
7
|
+
|
8
|
+
def persist(dump, db_name)
|
9
|
+
adapter.persist(dump.path, db_name)
|
10
|
+
logger.info "Persist #{dump} #{db_name} to #{adapter}"
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :adapter, :logger
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require_relative 'build_logger'
|
2
|
+
require_relative 'ftp/adapter'
|
3
|
+
require_relative 'ftp/connection'
|
4
|
+
require_relative 'ftp/repository'
|
5
|
+
require_relative 'bash/adapter'
|
6
|
+
require_relative 'bash/repository'
|
7
|
+
require_relative 'bash/factory'
|
8
|
+
require_relative 'aes'
|
9
|
+
require_relative 'services/create_and_export_dump'
|
10
|
+
|
11
|
+
class PgExport
|
12
|
+
class BootContainer
|
13
|
+
class << self
|
14
|
+
def call(config)
|
15
|
+
container = {}
|
16
|
+
|
17
|
+
boot_logger(container, config)
|
18
|
+
boot_aes(container, config)
|
19
|
+
boot_ftp(container, config)
|
20
|
+
boot_bash(container)
|
21
|
+
boot_services(container)
|
22
|
+
|
23
|
+
container
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def boot_logger(container, config)
|
29
|
+
container[:logger] = BuildLogger.call(stream: $stdout, format: config[:logger_format])
|
30
|
+
end
|
31
|
+
|
32
|
+
def boot_aes(container, config)
|
33
|
+
container[:encryptor] = Aes::Encryptor.new(key: config[:dump_encryption_key], logger: container[:logger])
|
34
|
+
container[:decryptor] = Aes::Decryptor.new(key: config[:dump_encryption_key], logger: container[:logger])
|
35
|
+
end
|
36
|
+
|
37
|
+
def boot_ftp(container, config)
|
38
|
+
container[:ftp_connection] = Ftp::Connection.new(
|
39
|
+
host: config[:ftp_host],
|
40
|
+
user: config[:ftp_user],
|
41
|
+
password: config[:ftp_password],
|
42
|
+
logger: container[:logger]
|
43
|
+
)
|
44
|
+
container[:ftp_adapter] = Ftp::Adapter.new(connection: container[:ftp_connection])
|
45
|
+
container[:ftp_repository] = Ftp::Repository.new(adapter: container[:ftp_adapter], logger: container[:logger])
|
46
|
+
end
|
47
|
+
|
48
|
+
def boot_bash(container)
|
49
|
+
container[:bash_adapter] = Bash::Adapter.new
|
50
|
+
container[:bash_repository] = Bash::Repository.new(
|
51
|
+
adapter: container[:bash_adapter],
|
52
|
+
logger: container[:logger]
|
53
|
+
)
|
54
|
+
container[:bash_factory] = Bash::Factory.new(
|
55
|
+
adapter: container[:bash_adapter],
|
56
|
+
logger: container[:logger]
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def boot_services(container)
|
61
|
+
container[:create_and_export_dump] = Services::CreateAndExportDump.new(
|
62
|
+
bash_factory: container[:bash_factory],
|
63
|
+
encryptor: container[:encryptor],
|
64
|
+
ftp_repository: container[:ftp_repository]
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class PgExport
|
4
|
+
class BuildLogger
|
5
|
+
FORMATS = {
|
6
|
+
plain: ->(_, _, _, message) { "#{message}\n" },
|
7
|
+
muted: ->(_, _, _, _) {},
|
8
|
+
timestamped: lambda do |severity, datetime, progname, message|
|
9
|
+
"#{datetime} #{Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{progname} #{severity}: #{message}\n"
|
10
|
+
end
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
def self.call(stream:, format:)
|
14
|
+
Logger.new(stream).tap do |logger|
|
15
|
+
logger.formatter = FORMATS.fetch(format)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,56 +1,14 @@
|
|
1
|
-
|
2
|
-
class Configuration
|
3
|
-
FIELD_REQUIRED = 'Field %s is required'.freeze
|
4
|
-
INVALID_ENCRYPTION_KEY_LENGTH = 'Dump encryption key should have exact 16 characters. Edit your DUMP_ENCRYPTION_KEY env variable.'.freeze
|
5
|
-
|
6
|
-
DEFAULTS = {
|
7
|
-
database: nil,
|
8
|
-
keep_dumps: ENV['KEEP_DUMPS'] || 10,
|
9
|
-
dump_encryption_key: ENV['DUMP_ENCRYPTION_KEY'],
|
10
|
-
ftp_host: ENV['BACKUP_FTP_HOST'],
|
11
|
-
ftp_user: ENV['BACKUP_FTP_USER'],
|
12
|
-
ftp_password: ENV['BACKUP_FTP_PASSWORD']
|
13
|
-
}.freeze
|
14
|
-
|
15
|
-
attr_accessor *DEFAULTS.keys
|
16
|
-
|
17
|
-
def initialize
|
18
|
-
DEFAULTS.each_pair do |key, value|
|
19
|
-
send("#{key}=", value)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def validate
|
24
|
-
DEFAULTS.keys.each do |field|
|
25
|
-
raise InvalidConfigurationError, FIELD_REQUIRED % field if public_send(field).nil?
|
26
|
-
end
|
27
|
-
raise InvalidConfigurationError, INVALID_ENCRYPTION_KEY_LENGTH unless dump_encryption_key.length == 16
|
28
|
-
end
|
1
|
+
require 'dry-struct'
|
29
2
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
def print_attr(key)
|
45
|
-
if %i(ftp_password dump_encryption_key).include?(key)
|
46
|
-
if public_send(key)
|
47
|
-
"#{key}: #{public_send(key)[0..2]}***\n"
|
48
|
-
else
|
49
|
-
"#{key}:\n"
|
50
|
-
end
|
51
|
-
else
|
52
|
-
"#{key}: #{public_send(key)}\n"
|
53
|
-
end
|
54
|
-
end
|
3
|
+
class PgExport
|
4
|
+
class Configuration < Dry::Struct
|
5
|
+
include Dry::Types.module
|
6
|
+
|
7
|
+
attribute :dump_encryption_key, Strict::String.constrained(size: 16)
|
8
|
+
attribute :ftp_host, Strict::String
|
9
|
+
attribute :ftp_user, Strict::String
|
10
|
+
attribute :ftp_password, Strict::String
|
11
|
+
attribute :logger_format, Strict::Symbol.enum(:plain, :timestamped, :muted)
|
12
|
+
attribute :interactive, Strict::Bool
|
55
13
|
end
|
56
14
|
end
|
@@ -1,16 +1,24 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
require_relative 'roles/human_readable'
|
5
|
+
|
1
6
|
class PgExport
|
2
7
|
class Dump
|
8
|
+
TIMESTAMP = '_%Y%m%d_%H%M%S'.freeze
|
9
|
+
|
3
10
|
extend Forwardable
|
4
|
-
include
|
11
|
+
include Roles::HumanReadable
|
5
12
|
|
6
13
|
CHUNK_SIZE = (2**16).freeze
|
7
14
|
|
8
15
|
def_delegators :file, :path, :read, :write, :<<, :rewind, :close, :size, :eof?
|
9
16
|
|
10
|
-
attr_reader :name
|
17
|
+
attr_reader :name, :db_name
|
11
18
|
|
12
|
-
def initialize(name)
|
13
|
-
@name = name
|
19
|
+
def initialize(name:, db_name:)
|
20
|
+
@name, @db_name = name, db_name
|
21
|
+
@timestamp = Time.now.strftime(TIMESTAMP)
|
14
22
|
end
|
15
23
|
|
16
24
|
def ext
|
@@ -31,12 +39,18 @@ class PgExport
|
|
31
39
|
end
|
32
40
|
end
|
33
41
|
|
42
|
+
def timestamped_name
|
43
|
+
db_name + timestamp + ext
|
44
|
+
end
|
45
|
+
|
34
46
|
def to_s
|
35
47
|
"#{name} (#{size_human})"
|
36
48
|
end
|
37
49
|
|
38
50
|
private
|
39
51
|
|
52
|
+
attr_reader :timestamp
|
53
|
+
|
40
54
|
def file
|
41
55
|
@file ||= Tempfile.new(file_name)
|
42
56
|
end
|
data/lib/pg_export/errors.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
class PgExport
|
2
|
-
class
|
3
|
-
class
|
4
|
-
class
|
2
|
+
class PgExportError < StandardError; end
|
3
|
+
class PgRestoreError < PgExportError; end
|
4
|
+
class PgDumpError < PgExportError; end
|
5
5
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class PgExport
|
2
|
+
module Ftp
|
3
|
+
class Adapter
|
4
|
+
CHUNK_SIZE = (2**16).freeze
|
5
|
+
|
6
|
+
def initialize(connection:)
|
7
|
+
@connection = connection
|
8
|
+
@host = connection.host
|
9
|
+
ObjectSpace.define_finalizer(self, proc { connection.close })
|
10
|
+
end
|
11
|
+
|
12
|
+
def list(regex_string)
|
13
|
+
ftp.list(regex_string).map { |item| item.split(' ').last }.sort.reverse
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(filename)
|
17
|
+
ftp.delete(filename)
|
18
|
+
end
|
19
|
+
|
20
|
+
def persist(path, timestamped_name)
|
21
|
+
ftp.putbinaryfile(path, timestamped_name, CHUNK_SIZE)
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(path, timestamped_name)
|
25
|
+
ftp.getbinaryfile(timestamped_name, path, CHUNK_SIZE)
|
26
|
+
end
|
27
|
+
|
28
|
+
def ftp
|
29
|
+
connection.ftp
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
host
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :connection, :host
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'net/ftp'
|
2
|
+
|
3
|
+
class PgExport
|
4
|
+
module Ftp
|
5
|
+
class Connection
|
6
|
+
attr_reader :host
|
7
|
+
|
8
|
+
def initialize(host:, user:, password:, logger:)
|
9
|
+
@host, @user, @password, @logger = host, user, password, logger
|
10
|
+
open_ftp_thread
|
11
|
+
end
|
12
|
+
|
13
|
+
def ftp
|
14
|
+
open_ftp_thread.join
|
15
|
+
@ftp
|
16
|
+
end
|
17
|
+
|
18
|
+
def close
|
19
|
+
ftp.close
|
20
|
+
logger.info 'Close FTP'
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :user, :password, :logger
|
27
|
+
|
28
|
+
def open
|
29
|
+
@ftp = Net::FTP.new(host, user, password)
|
30
|
+
@ftp.passive = true
|
31
|
+
logger.info "Connect to #{host}"
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def open_ftp_thread
|
36
|
+
@open_ftp_thread ||= Thread.new { open }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class PgExport
|
2
|
+
module Ftp
|
3
|
+
class Repository
|
4
|
+
def initialize(adapter:, logger:)
|
5
|
+
@adapter, @logger = adapter, logger
|
6
|
+
end
|
7
|
+
|
8
|
+
def persist(dump)
|
9
|
+
adapter.persist(dump.path, dump.timestamped_name)
|
10
|
+
logger.info "Persist #{dump} #{dump.timestamped_name} to #{adapter}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(db_name)
|
14
|
+
dump = Dump.new(name: 'Encrypted Dump', db_name: db_name)
|
15
|
+
adapter.get(dump.path, dump.db_name)
|
16
|
+
logger.info "Get #{dump} #{db_name} from #{adapter}"
|
17
|
+
dump
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove_old(name, keep)
|
21
|
+
find_by_name(name).drop(keep).each do |filename|
|
22
|
+
adapter.delete(filename)
|
23
|
+
logger.info "Remove #{filename} from #{adapter}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_by_name(name)
|
28
|
+
adapter.list(name + '_*')
|
29
|
+
end
|
30
|
+
|
31
|
+
def all
|
32
|
+
adapter.list('*')
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :adapter, :logger
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class PgExport
|
2
|
+
module Roles
|
3
|
+
module HumanReadable
|
4
|
+
MAPPING = {
|
5
|
+
'B' => 1024,
|
6
|
+
'kB' => 1024 * 1024,
|
7
|
+
'MB' => 1024 * 1024 * 1024,
|
8
|
+
'GB' => 1024 * 1024 * 1024 * 1024,
|
9
|
+
'TB' => 1024 * 1024 * 1024 * 1024 * 1024
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
def size_human
|
13
|
+
MAPPING.each_pair { |e, s| return "#{(size.to_f / (s / 1024)).round(2)}#{e}" if size < s }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|