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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +2 -8
  4. data/bin/pg_export +39 -19
  5. data/lib/pg_export/aes/base.rb +47 -0
  6. data/lib/pg_export/aes/decryptor.rb +13 -0
  7. data/lib/pg_export/aes/encryptor.rb +13 -0
  8. data/lib/pg_export/aes.rb +3 -0
  9. data/lib/pg_export/bash/adapter.rb +31 -0
  10. data/lib/pg_export/bash/factory.rb +23 -0
  11. data/lib/pg_export/bash/repository.rb +18 -0
  12. data/lib/pg_export/boot_container.rb +69 -0
  13. data/lib/pg_export/build_logger.rb +19 -0
  14. data/lib/pg_export/configuration.rb +11 -53
  15. data/lib/pg_export/{entities/dump.rb → dump.rb} +18 -4
  16. data/lib/pg_export/errors.rb +3 -3
  17. data/lib/pg_export/ftp/adapter.rb +41 -0
  18. data/lib/pg_export/ftp/connection.rb +40 -0
  19. data/lib/pg_export/ftp/repository.rb +40 -0
  20. data/lib/pg_export/roles/colourable_string.rb +19 -0
  21. data/lib/pg_export/roles/human_readable.rb +17 -0
  22. data/lib/pg_export/roles/interactive.rb +98 -0
  23. data/lib/pg_export/roles/validatable.rb +24 -0
  24. data/lib/pg_export/services/create_and_export_dump.rb +20 -0
  25. data/lib/pg_export/version.rb +1 -1
  26. data/lib/pg_export.rb +18 -56
  27. data/pg_export.gemspec +2 -1
  28. metadata +49 -31
  29. data/lib/pg_export/includable_modules/colourable_string.rb +0 -17
  30. data/lib/pg_export/includable_modules/dump/size_human.rb +0 -15
  31. data/lib/pg_export/includable_modules/interactive.rb +0 -97
  32. data/lib/pg_export/includable_modules/logging.rb +0 -31
  33. data/lib/pg_export/includable_modules/services_container.rb +0 -41
  34. data/lib/pg_export/services/aes/base.rb +0 -26
  35. data/lib/pg_export/services/aes/decryptor.rb +0 -12
  36. data/lib/pg_export/services/aes/encryptor.rb +0 -12
  37. data/lib/pg_export/services/aes.rb +0 -28
  38. data/lib/pg_export/services/bash_utils.rb +0 -32
  39. data/lib/pg_export/services/dump_storage.rb +0 -48
  40. data/lib/pg_export/services/ftp_adapter.rb +0 -38
  41. 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: 763552e9063089d2137c2eff38b159caee96f457
4
- data.tar.gz: 445e0c662a841abf1c9ee1f3ba4fa05dc845e6e3
3
+ metadata.gz: 4e6bcca69be07806c87aa5e971292269c6e24ea3
4
+ data.tar.gz: 421af6b9f3430c9a5e3431da6f71d78b91463157
5
5
  SHA512:
6
- metadata.gz: 9f8e27408b4aa14d29783f3672c456d09521cce85195fee112fbd32b8fd3b987f039a82dc28e652553fc40f63be0d80509cf05eb1299494c9beaac57ff8d7f2f
7
- data.tar.gz: ab1c4938fbbb933735c35f806d6eca4745d240a7d749641180724ccebaba06e976b49a73762bc36eb4a7a577fdb6f2fe8e20ca41f4028cba29f8c235125c9204
6
+ metadata.gz: a2d7b152087be40ae99ae42a5e3acfb90b1aacef6780bfc6ec255883db9230c77455ae17065226e0aad91971ab74e89743badd74c4dd3c7f3571638fb8837ffe
7
+ data.tar.gz: 633e2d33af88ee049ee3ba5490ce228391ee445569533b8752b87ec5c29435bd1bdc0d518801b6c889e4a2898d08127d99a513d6239b1b54de3de1adea28b8f5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ### 0.6.0 - 2017.08.18
2
+
3
+ - Improve internal architecture
4
+
1
5
  ### 0.5.1 - 2017.03.31
2
6
 
3
7
  - Simplify Dump entity design
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
- keep_dumps: 5
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/includable_modules/colourable_string'
10
- require 'pg_export/includable_modules/interactive'
7
+ require 'pg_export/roles/colourable_string'
11
8
 
12
- config = PgExport::ServicesContainer.config
13
- pg_export = PgExport.new
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.database = database
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.keep_dumps})") do |keep|
23
- config.keep_dumps = keep
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
- PgExport::Logging.format_timestamped
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
- PgExport::Logging.mute
32
- config.database ||= 'undefined'
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.to_s
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::ServicesContainer.ftp_connection.open
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
- pg_export.call
57
- rescue OptionParser::MissingArgument, PgExport::InvalidConfigurationError => e
58
- using PgExport::ColourableString
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,13 @@
1
+ class PgExport
2
+ module Aes
3
+ class Decryptor < Base
4
+ def target_dump_name
5
+ 'Dump'.freeze
6
+ end
7
+
8
+ def cipher_type
9
+ :decrypt
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ class PgExport
2
+ module Aes
3
+ class Encryptor < Base
4
+ def target_dump_name
5
+ 'Encrypted Dump'.freeze
6
+ end
7
+
8
+ def cipher_type
9
+ :encrypt
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'aes/base'
2
+ require_relative 'aes/encryptor'
3
+ require_relative 'aes/decryptor'
@@ -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
- class PgExport
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
- def ftp_params
31
- {
32
- host: ftp_host,
33
- user: ftp_user,
34
- password: ftp_password
35
- }
36
- end
37
-
38
- def to_s
39
- DEFAULTS.keys.map(&method(:print_attr))
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 SizeHuman
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
@@ -1,5 +1,5 @@
1
1
  class PgExport
2
- class InvalidConfigurationError < StandardError; end
3
- class PgDumpError < StandardError; end
4
- class PgRestoreError < StandardError; end
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,19 @@
1
+ class PgExport
2
+ module Roles
3
+ module ColourableString
4
+ refine String do
5
+ def red
6
+ "\e[31m#{self}\e[0m"
7
+ end
8
+
9
+ def green
10
+ "\e[0;32;49m#{self}\e[0m"
11
+ end
12
+
13
+ def gray
14
+ "\e[37m#{self}\e[0m"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ 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