pg_export 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/pg_export.svg)](https://badge.fury.io/rb/pg_export)
|
4
4
|
[![Build Status](https://travis-ci.org/maicher/pg_export.svg?branch=master)](https://travis-ci.org/maicher/pg_export)
|
5
5
|
[![Code Climate](https://codeclimate.com/github/maicher/pg_export/badges/gpa.svg)](https://codeclimate.com/github/maicher/pg_export)
|
6
|
-
[![Test Coverage](https://codeclimate.com/github/maicher/cli_spinnable/badges/coverage.svg)](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
|