pg_export 0.6.1 → 0.7.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 (79) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +9 -13
  3. data/.travis.yml +2 -2
  4. data/CHANGELOG.md +9 -0
  5. data/Gemfile +2 -0
  6. data/README.md +28 -29
  7. data/Rakefile +2 -0
  8. data/bin/console +2 -8
  9. data/bin/pg_export +44 -45
  10. data/lib/pg_export/build_logger.rb +4 -2
  11. data/lib/pg_export/configuration.rb +22 -2
  12. data/lib/pg_export/container.rb +48 -0
  13. data/lib/pg_export/import.rb +7 -0
  14. data/lib/pg_export/lib/pg_export/adapters/bash_adapter.rb +37 -0
  15. data/lib/pg_export/lib/pg_export/adapters/ftp_adapter.rb +67 -0
  16. data/lib/pg_export/lib/pg_export/entities/dump.rb +45 -0
  17. data/lib/pg_export/lib/pg_export/factories/cipher_factory.rb +32 -0
  18. data/lib/pg_export/lib/pg_export/factories/dump_factory.rb +31 -0
  19. data/lib/pg_export/lib/pg_export/factories/ftp_adapter_factory.rb +22 -0
  20. data/lib/pg_export/lib/pg_export/listeners/interactive/build_dump.rb +19 -0
  21. data/lib/pg_export/lib/pg_export/listeners/interactive/close_ftp_connection.rb +19 -0
  22. data/lib/pg_export/lib/pg_export/listeners/interactive/decrypt_dump.rb +19 -0
  23. data/lib/pg_export/lib/pg_export/listeners/interactive/download_dump_from_ftp.rb +19 -0
  24. data/lib/pg_export/lib/pg_export/listeners/interactive/encrypt_dump.rb +19 -0
  25. data/lib/pg_export/lib/pg_export/listeners/interactive/fetch_dumps_from_ftp.rb +19 -0
  26. data/lib/pg_export/lib/pg_export/listeners/interactive/open_ftp_connection.rb +19 -0
  27. data/lib/pg_export/lib/pg_export/listeners/interactive/remove_old_dumps_from_ftp.rb +23 -0
  28. data/lib/pg_export/lib/pg_export/listeners/interactive/restore.rb +19 -0
  29. data/lib/pg_export/lib/pg_export/listeners/interactive/upload_dump_to_ftp.rb +19 -0
  30. data/lib/pg_export/lib/pg_export/listeners/interactive_listener.rb +49 -0
  31. data/lib/pg_export/lib/pg_export/listeners/plain/build_dump.rb +15 -0
  32. data/lib/pg_export/lib/pg_export/listeners/plain/close_ftp_connection.rb +15 -0
  33. data/lib/pg_export/lib/pg_export/listeners/plain/decrypt_dump.rb +15 -0
  34. data/lib/pg_export/lib/pg_export/listeners/plain/download_dump_from_ftp.rb +15 -0
  35. data/lib/pg_export/lib/pg_export/listeners/plain/encrypt_dump.rb +15 -0
  36. data/lib/pg_export/lib/pg_export/listeners/plain/fetch_dumps_from_ftp.rb +15 -0
  37. data/lib/pg_export/lib/pg_export/listeners/plain/open_ftp_connection.rb +15 -0
  38. data/lib/pg_export/lib/pg_export/listeners/plain/remove_old_dumps_from_ftp.rb +17 -0
  39. data/lib/pg_export/lib/pg_export/listeners/plain/restore.rb +15 -0
  40. data/lib/pg_export/lib/pg_export/listeners/plain/upload_dump_to_ftp.rb +15 -0
  41. data/lib/pg_export/lib/pg_export/listeners/plain_listener.rb +17 -0
  42. data/lib/pg_export/lib/pg_export/operations/decrypt_dump.rb +20 -0
  43. data/lib/pg_export/lib/pg_export/operations/encrypt_dump.rb +18 -0
  44. data/lib/pg_export/lib/pg_export/operations/open_ftp_connection.rb +19 -0
  45. data/lib/pg_export/lib/pg_export/operations/remove_old_dumps_from_ftp.rb +22 -0
  46. data/lib/pg_export/lib/pg_export/repositories/ftp_dump_file_repository.rb +19 -0
  47. data/lib/pg_export/lib/pg_export/repositories/ftp_dump_repository.rb +36 -0
  48. data/lib/pg_export/lib/pg_export/transactions/export_dump.rb +56 -0
  49. data/lib/pg_export/lib/pg_export/transactions/import_dump_interactively.rb +67 -0
  50. data/lib/pg_export/lib/pg_export/types.rb +14 -0
  51. data/lib/pg_export/lib/pg_export/ui/interactive/input.rb +36 -0
  52. data/lib/pg_export/lib/pg_export/ui/plain/input.rb +17 -0
  53. data/lib/pg_export/lib/pg_export/value_objects/dump_file.rb +70 -0
  54. data/lib/pg_export/system/boot/config.rb +11 -0
  55. data/lib/pg_export/system/boot/interactive.rb +32 -0
  56. data/lib/pg_export/system/boot/logger.rb +15 -0
  57. data/lib/pg_export/system/boot/plain.rb +33 -0
  58. data/lib/pg_export/version.rb +3 -1
  59. data/lib/pg_export.rb +23 -20
  60. data/pg_export.gemspec +13 -10
  61. metadata +108 -52
  62. data/lib/pg_export/aes/base.rb +0 -47
  63. data/lib/pg_export/aes/decryptor.rb +0 -13
  64. data/lib/pg_export/aes/encryptor.rb +0 -13
  65. data/lib/pg_export/aes.rb +0 -3
  66. data/lib/pg_export/bash/adapter.rb +0 -31
  67. data/lib/pg_export/bash/factory.rb +0 -23
  68. data/lib/pg_export/bash/repository.rb +0 -18
  69. data/lib/pg_export/boot_container.rb +0 -69
  70. data/lib/pg_export/dump.rb +0 -62
  71. data/lib/pg_export/errors.rb +0 -5
  72. data/lib/pg_export/ftp/adapter.rb +0 -41
  73. data/lib/pg_export/ftp/connection.rb +0 -40
  74. data/lib/pg_export/ftp/repository.rb +0 -40
  75. data/lib/pg_export/roles/colourable_string.rb +0 -19
  76. data/lib/pg_export/roles/human_readable.rb +0 -17
  77. data/lib/pg_export/roles/interactive.rb +0 -98
  78. data/lib/pg_export/roles/validatable.rb +0 -24
  79. data/lib/pg_export/services/create_and_export_dump.rb +0 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c9d176bb30786ee0c9e6995d2d1875607463188a
4
- data.tar.gz: 0866aa29b2ebf8945568aab3c3aa102fd4a6e1e2
2
+ SHA256:
3
+ metadata.gz: e6e920a09c0e04dd2b2e2eb612db083a390cbb74fae09bc1b5cfa510bc74751a
4
+ data.tar.gz: d23c28f594f37ac01bd586668bb62b8641ce56b9b1c5f4ff05a528dc1e2a69b2
5
5
  SHA512:
6
- metadata.gz: 742ff5d0198a1fcca3a9b4f351dbcc9c975f0e07307bebdff30ea657825668dc04fee09e16e9e516d1cbd4f5a6505a4d3078ce1eca0d4ef00556ceb6d22f6c31
7
- data.tar.gz: 53c17e1c8ca81d967ab1d3b4b09f13463afd4657151554dc517db2e9ff501bb580b3e34fad497886fd3e17dbaf35a45c8d6ec0044f14450fa314e36c01bf142a
6
+ metadata.gz: 92a31d0fa7d86a759060cf0f404fcc35e6d14a6f8043b66c5782e3b69646c2a1a5708276a88697d7f8e40fc85ae5a464ad98129e89e5b9f6bc61f6dfb72039c4
7
+ data.tar.gz: 4b6d1c3a9889a6b7b5a494605e300684680fa21d40ed1f79f9ae64356555f4fea6e5b4245b835e3e705d1c9f53459117ba672ebe70db4306e01f61e80cae9605
data/.rubocop.yml CHANGED
@@ -1,26 +1,22 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.1
3
- Exclude:
4
- - 'spec/spec_helper.rb'
2
+ TargetRubyVersion: 2.3
5
3
 
6
- Style/BlockLength:
4
+ Metrics/BlockLength:
7
5
  Exclude:
8
6
  - 'bin/pg_export'
9
- - 'spec/**/*.rb'
7
+ ExcludedMethods: ['describe', 'context']
10
8
 
11
9
  Metrics/LineLength:
12
10
  Max: 200
13
11
 
14
- Lint/AmbiguousOperator:
15
- Exclude:
16
- - 'lib/pg_export/configuration.rb'
17
-
18
- Style/AlignArray:
19
- Exclude:
20
- - 'spec/configuration_spec.rb'
21
-
22
12
  Style/ParallelAssignment:
23
13
  Enabled: false
24
14
 
15
+ Naming/UncommunicativeMethodParamName:
16
+ Enabled: false
17
+
25
18
  Documentation:
26
19
  Enabled: false
20
+
21
+ Lint/UnusedMethodArgument:
22
+ Enabled: false
data/.travis.yml CHANGED
@@ -1,9 +1,9 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.2.0
4
+ - 2.3.0
5
5
  addons:
6
6
  code_climate:
7
7
  repo_token: db03e5968c5bcd68b12ca50f5d41ae07dd74fe80d4e1421d754e31c316e7477a
8
- before_install: gem install bundler -v 1.13.3
8
+ before_install: gem install bundler -v 1.16.6
9
9
  after_success: codeclimate-test-reporter
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ### 0.7.0 - 2018.10.18
2
+
3
+ - Change required ruby version from 2.2.0 to 2.3.0.
4
+ - Refactor architecture to be more functional using dry-system
5
+ - Use tty for interactive CLI (instead of cli_spinnable)
6
+ - Improve configuration parsing
7
+ - Add -m option for muting log messages
8
+ - Make log messages more verbose
9
+
1
10
  ### 0.6.1 - 2017.08.20
2
11
 
3
12
  - Change required ruby version from 2.1.0 to 2.2.0.
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in pg_export.gemspec
data/README.md CHANGED
@@ -26,7 +26,7 @@ Features:
26
26
 
27
27
  ## Dependencies
28
28
 
29
- * Ruby >= 2.2.0
29
+ * Ruby >= 2.3.0
30
30
  * $ pg_dump
31
31
  * $ pg_restore
32
32
 
@@ -54,7 +54,8 @@ Or install it yourself as:
54
54
  -d, --database DATABASE [Required] Name of the database to export
55
55
  -k, --keep [KEEP] [Optional] Number of dump files to keep on FTP (default: 10)
56
56
  -t, --timestamped [Optional] Enables log messages with timestamps
57
- -i, --interactive Interactive, command line mode, for restoring dumps into databases
57
+ -m, --muted [Optional] Mutes log messages (overrides -t option)
58
+ -i, --interactive [Optional] Interactive command line mode - for restoring dumps into databases
58
59
  -h, --help Show this message
59
60
 
60
61
  Setting can be verified by running following commands:
@@ -63,50 +64,48 @@ Or install it yourself as:
63
64
 
64
65
  ## How to start
65
66
 
66
- __Step 1.__ Prepare FTP account and put configuration into env variables. Dumps will be exported into that location.
67
+ __Step 1.__ Prepare ENV variables.
67
68
 
68
- # /etc/environment
69
- BACKUP_FTP_HOST="yourftp.example.com"
70
- BACKUP_FTP_USER="user"
71
- BACKUP_FTP_PASSWORD="password"
69
+ /* FTP storage for database dumps. */
70
+ BACKUP_FTP_HOST=yourftp.example.com
71
+ BACKUP_FTP_USER=user
72
+ BACKUP_FTP_PASSWORD=password
72
73
 
73
- __Step 2.__ Put dump encryption key into env variable (exactly 16 characters). Dumps will be SSL(AES-128-CBC) encrypted using that key.
74
-
75
- # /etc/environment
76
- DUMP_ENCRYPTION_KEY="1234567890abcdef"
74
+ /* Encryption key shoul have exactly 16 characters. */
75
+ /* Dumps will be SSL(AES-128-CBC) encrypted using this key. */
76
+ DUMP_ENCRYPTION_KEY=1234567890abcdef
77
77
 
78
- Variables cannot include `#` sign, [more info](http://serverfault.com/questions/539730/environment-variable-in-etc-environment-with-pound-hash-sign-in-the-value).
79
-
80
- __Step 3.__ Configure how many dumps should be kept in FTP (optional).
81
-
82
- # /etc/environment
83
- KEEP_DUMPS=5 # default: 10
78
+ /* Dumps to be kept on FTP */
79
+ /* Optional, defaults to 10 */
80
+ KEEP_DUMPS=5
81
+
82
+ Note, that variables cannot include `#` sign, [more info](http://serverfault.com/questions/539730/environment-variable-in-etc-environment-with-pound-hash-sign-in-the-value).
84
83
 
85
- __Step 4.__ Print the configuration to verify whether env variables has been loaded.
84
+ __Step 2.__ Print the configuration to verify if env variables has been loaded properly.
86
85
 
87
86
  $ pg_export --configuration
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}
87
+ => {:dump_encryption_key=>"k4***", :ftp_host=>"yourftp.example.com", :ftp_user=>"your_ftp_user",
88
+ :ftp_password=>"pass***", :logger_format=>"plain", :keep_dumps=>2}
90
89
 
91
- __Step 5.__ Try connecting to FTP to verify the connection.
90
+ __Step 3.__ Try connecting to FTP to verify the connection.
92
91
 
93
92
  $ pg_export --ftp
94
- => Connect to yourftp.example.com
93
+ => 230 User your_ftp_user logged in
95
94
 
96
- __Step 6.__ Perform database export.
95
+ __Step 4.__ Perform database export.
97
96
 
98
- $ pg_export -d your_database
99
- => Create Dump Tempfile (1.36MB)
100
- Create Encrypted Dump Tempfile (1.34MB)
97
+ $ pg_export -d your_database [-k 5]
98
+ => Dump database your_database to your_database_20181016_121314 (1.36MB)
99
+ Encrypt your_database_20181016_121314 (1.34MB)
101
100
  Connect to yourftp.example.com
102
- Export Encrypted Dump Tempfile (1.34MB) your_database_20161020_125747 to yourftp.example.com
101
+ Upload your_database_20181016_121314 (1.34MB) to yourftp.example.com
103
102
  Close FTP
104
103
 
105
104
  ## How to restore a dump?
106
105
 
107
- Go to interactive mode and follow the instructions:
106
+ Run interactive mode and follow the instructions:
108
107
 
109
- pg_export -i
108
+ pg_export [-d your_database] -i
110
109
 
111
110
  ## Development
112
111
 
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rspec/core/rake_task'
3
5
 
data/bin/console CHANGED
@@ -1,14 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'pg_export'
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
6
  require 'irb'
7
+
14
8
  IRB.start
data/bin/pg_export CHANGED
@@ -1,40 +1,36 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'optparse'
4
- require 'ostruct'
5
5
 
6
6
  require 'pg_export'
7
- require 'pg_export/roles/colourable_string'
8
-
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
- }
7
+ require 'pg_export/container'
8
+
9
+ ENV['KEEP_DUMPS'] = ENV['KEEP_DUMPS'] || '10'
10
+ interactive = false
11
+ database = nil
19
12
 
20
13
  option_parser = OptionParser.new do |opts|
21
14
  opts.banner = 'Usage: pg_export [options]'
22
15
 
23
- opts.on('-d', '--database DATABASE', String, '[Required] Name of the database to export') do |database|
24
- config[:database] = database
16
+ opts.on('-d', '--database DATABASE', String, '[Required] Name of the database to export') do |d|
17
+ database = d
25
18
  end
26
19
 
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
20
+ opts.on('-k', '--keep [KEEP]', String, "[Optional] Number of dump files to keep on FTP (default: #{ENV['KEEP_DUMPS']})") do |keep|
21
+ ENV['KEEP_DUMPS'] = keep
29
22
  end
30
23
 
31
24
  opts.on('-t', '--timestamped', '[Optional] Enables log messages with timestamps') do
32
- config[:logger_format] = :timestamped
25
+ ENV['LOGGER_FORMAT'] = 'timestamped'
26
+ end
27
+
28
+ opts.on('-m', '--muted', '[Optional] Mutes log messages (overrides -t option)') do
29
+ ENV['LOGGER_FORMAT'] = 'muted'
33
30
  end
34
31
 
35
- opts.on('-i', '--interactive', 'Interactive, command line mode, for restoring dumps into databases') do
36
- config[:logger_format] = :muted
37
- config[:interactive] = true
32
+ opts.on('-i', '--interactive', '[Optional] Interactive command line mode - for restoring dumps into databases') do
33
+ interactive = true
38
34
  end
39
35
 
40
36
  opts.on('-h', '--help', 'Show this message') do
@@ -45,39 +41,42 @@ option_parser = OptionParser.new do |opts|
45
41
  opts.separator "\nSetting can be verified by running following commands:"
46
42
 
47
43
  opts.on('-c', '--configuration', 'Prints the configuration') do
48
- puts config
44
+ PgExport::Container.start(:config)
45
+ puts PgExport::Container['config'].to_h
49
46
  exit
50
47
  end
51
48
 
52
49
  opts.on('-f', '--ftp', 'Tries connecting to FTP to verify the connection') do
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
+ PgExport::Container.start(:ftp)
51
+ ftp_adapter = PgExport::Container['factories.ftp_adapter_factory'].ftp_adapter
52
+ ftp = ftp_adapter.open_ftp
53
+ puts ftp.welcome
59
54
  exit
60
55
  end
61
56
  end
62
57
 
63
58
  begin
64
59
  option_parser.parse!
60
+ rescue OptionParser::ParseError => e
61
+ warn e.message.capitalize
62
+ warn 'Type "pg_export -h" for available options'
63
+ exit
64
+ end
65
+
66
+ begin
67
+ pg_export =
68
+ if interactive
69
+ PgExport.interactive
70
+ else
71
+ PgExport.plain
72
+ end
73
+ rescue PgExport::InitializationError => e
74
+ warn 'Unable to initialize PgExport due to invalid configuration. Check you ENVs.'
75
+ warn "Detailed message: #{e.message}"
76
+ exit
77
+ end
65
78
 
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
79
- puts "Error: #{e}".red
80
- puts option_parser
81
- rescue PgExport::PgDumpError => e
82
- puts e
79
+ pg_export.call(database) do |result|
80
+ result.success { puts 'Success' }
81
+ result.failure { |message:| warn message }
83
82
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
2
4
 
3
5
  class PgExport
4
6
  class BuildLogger
5
7
  FORMATS = {
6
8
  plain: ->(_, _, _, message) { "#{message}\n" },
7
- muted: ->(_, _, _, _) {},
9
+ muted: ->(*) { raise 'Do not initialize logger when it is muted' },
8
10
  timestamped: lambda do |severity, datetime, progname, message|
9
11
  "#{datetime} #{Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{progname} #{severity}: #{message}\n"
10
12
  end
@@ -12,7 +14,7 @@ class PgExport
12
14
 
13
15
  def self.call(stream:, format:)
14
16
  Logger.new(stream).tap do |logger|
15
- logger.formatter = FORMATS.fetch(format)
17
+ logger.formatter = FORMATS.fetch(format.to_sym)
16
18
  end
17
19
  end
18
20
  end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-types'
1
4
  require 'dry-struct'
2
5
 
3
6
  class PgExport
@@ -8,7 +11,24 @@ class PgExport
8
11
  attribute :ftp_host, Strict::String
9
12
  attribute :ftp_user, Strict::String
10
13
  attribute :ftp_password, Strict::String
11
- attribute :logger_format, Strict::Symbol.enum(:plain, :timestamped, :muted)
12
- attribute :interactive, Strict::Bool
14
+ attribute :logger_format, Coercible::String.enum('plain', 'timestamped', 'muted')
15
+ attribute :keep_dumps, Coercible::Integer.constrained(gteq: 0)
16
+
17
+ def self.build(env)
18
+ new(
19
+ dump_encryption_key: env['DUMP_ENCRYPTION_KEY'],
20
+ ftp_host: env['BACKUP_FTP_HOST'],
21
+ ftp_user: env['BACKUP_FTP_USER'],
22
+ ftp_password: env['BACKUP_FTP_PASSWORD'],
23
+ logger_format: env['LOGGER_FORMAT'] || 'plain',
24
+ keep_dumps: env['KEEP_DUMPS'] || 10
25
+ )
26
+ rescue Dry::Struct::Error => e
27
+ raise PgExport::InitializationError, e.message.gsub('[PgExport::Configuration.new] ', '')
28
+ end
29
+
30
+ def logger_muted?
31
+ logger_format == 'muted'
32
+ end
13
33
  end
14
34
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/system/container'
4
+ require 'pg_export/lib/pg_export/types'
5
+
6
+ class PgExport
7
+ class Container < Dry::System::Container
8
+ configure do
9
+ config.root = Pathname(__FILE__).realpath.dirname
10
+ config.name = :pg_export
11
+ config.default_namespace = 'pg_export'
12
+ config.auto_register = %w[lib]
13
+ end
14
+
15
+ load_paths!('lib')
16
+
17
+ boot(:ftp) do
18
+ init do
19
+ require 'pg_export/lib/pg_export/factories/ftp_adapter_factory'
20
+ end
21
+
22
+ start do
23
+ use :config
24
+
25
+ register('factories.ftp_adapter_factory') do
26
+ ::PgExport::Factories::FtpAdapterFactory.new
27
+ end
28
+ end
29
+ end
30
+
31
+ boot(:main) do
32
+ init do
33
+ require 'pg_export/lib/pg_export/operations/encrypt_dump'
34
+ require 'pg_export/lib/pg_export/operations/decrypt_dump'
35
+ require 'pg_export/lib/pg_export/operations/remove_old_dumps_from_ftp'
36
+ require 'pg_export/lib/pg_export/operations/open_ftp_connection'
37
+ end
38
+
39
+ start do
40
+ use :ftp
41
+ register('operations.encrypt_dump') { ::PgExport::Operations::EncryptDump.new }
42
+ register('operations.decrypt_dump') { ::PgExport::Operations::DecryptDump.new }
43
+ register('operations.remove_old_dumps_from_ftp') { ::PgExport::Operations::RemoveOldDumpsFromFtp.new }
44
+ register('operations.open_ftp_connection') { ::PgExport::Operations::OpenFtpConnection.new }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'container'
4
+
5
+ class PgExport
6
+ Import = PgExport::Container.injector.hash
7
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ class PgExport
6
+ module Adapters
7
+ class BashAdapter
8
+ class PgRestoreError < StandardError; end
9
+ class PgDumpError < StandardError; end
10
+
11
+ def pg_dump(file, db_name)
12
+ popen("pg_dump -Fc --file #{file.path} #{db_name}") do |errors|
13
+ raise PgDumpError, errors.chomp unless errors.empty?
14
+ end
15
+
16
+ file
17
+ end
18
+
19
+ def pg_restore(file, db_name)
20
+ popen("pg_restore -c -d #{db_name} #{file.path}") do |errors|
21
+ raise PgRestoreError, errors.chomp if /FATAL/ =~ errors
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def popen(command)
28
+ Open3.popen3(command) do |_, _, err|
29
+ errors = err.read
30
+ yield errors
31
+ end
32
+
33
+ self
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # auto_register: false
4
+
5
+ require 'net/ftp'
6
+
7
+ class PgExport
8
+ module Adapters
9
+ class FtpAdapter
10
+ CHUNK_SIZE = (2**16).freeze
11
+
12
+ def initialize(host:, user:, password:)
13
+ @host, @user, @password, @logger = host, user, password
14
+ ObjectSpace.define_finalizer(self, proc { ftp.close if @ftp })
15
+ end
16
+
17
+ def open_ftp
18
+ @ftp = Net::FTP.new(host, user, password)
19
+ @ftp.passive = true
20
+ @ftp
21
+ end
22
+
23
+ def close_ftp
24
+ @ftp&.close
25
+ end
26
+
27
+ def list(regex_string)
28
+ ftp
29
+ .list(regex_string)
30
+ .map { |row| extracted_meaningful_attributes(row) }
31
+ .sort_by { |item| item[:name] }
32
+ .reverse
33
+ end
34
+
35
+ def delete(filename)
36
+ ftp.delete(filename)
37
+ end
38
+
39
+ def persist(file, name)
40
+ ftp.putbinaryfile(file.path, name, CHUNK_SIZE)
41
+ end
42
+
43
+ def get(file, name)
44
+ ftp.getbinaryfile(name, file.path, CHUNK_SIZE)
45
+ end
46
+
47
+ def to_s
48
+ host
49
+ end
50
+
51
+ def ftp
52
+ @ftp ||= open_ftp
53
+ end
54
+
55
+ private
56
+
57
+ attr_reader :host, :user, :password
58
+
59
+ def extracted_meaningful_attributes(item)
60
+ MEANINGFUL_KEYS.zip(item.split(' ').values_at(8, 4)).to_h
61
+ end
62
+
63
+ MEANINGFUL_KEYS = %i[name size].freeze
64
+ private_constant :MEANINGFUL_KEYS
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-initializer'
4
+ require 'pg_export/lib/pg_export/types'
5
+
6
+ class PgExport
7
+ module Entities
8
+ class Dump
9
+ extend Dry::Initializer[undefined: false]
10
+
11
+ option :name, Types::DumpName
12
+ option :type, Types::DumpType
13
+ option :database, Types::Strict::String.constrained(filled: true)
14
+ option :file, Types::DumpFile, default: proc { PgExport::ValueObjects::DumpFile.new }
15
+
16
+ def encrypt(cipher_factory:)
17
+ self.file = file.copy(cipher: cipher_factory.encryptor)
18
+ self.type = :encrypted
19
+
20
+ self
21
+ end
22
+
23
+ def decrypt(cipher_factory:)
24
+ self.file = file.copy(cipher: cipher_factory.decryptor)
25
+ self.type = :plain
26
+
27
+ self
28
+ end
29
+
30
+ def to_s
31
+ "#{name} (#{file.size_human})"
32
+ end
33
+
34
+ def file=(f)
35
+ @file = Types::DumpFile[f]
36
+ end
37
+
38
+ protected
39
+
40
+ def type=(t)
41
+ @type = Types::DumpType[t]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'pg_export/import'
5
+
6
+ class PgExport
7
+ module Factories
8
+ class CipherFactory
9
+ include Import['config']
10
+
11
+ def encryptor
12
+ cipher(:encrypt)
13
+ end
14
+
15
+ def decryptor
16
+ cipher(:decrypt)
17
+ end
18
+
19
+ private
20
+
21
+ ALGORITHM = 'AES-128-CBC'
22
+ private_constant :ALGORITHM
23
+
24
+ def cipher(type)
25
+ OpenSSL::Cipher.new(ALGORITHM).tap do |cipher|
26
+ cipher.public_send(type)
27
+ cipher.key = config.dump_encryption_key
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'tempfile'
5
+
6
+ require 'pg_export/lib/pg_export/entities/dump'
7
+ require 'pg_export/import'
8
+
9
+ class PgExport
10
+ module Factories
11
+ class DumpFactory
12
+ def plain(database:, file:)
13
+ Entities::Dump.new(
14
+ name: [database, timestamp].join('_'),
15
+ database: database,
16
+ file: file,
17
+ type: :plain
18
+ )
19
+ end
20
+
21
+ private
22
+
23
+ TIMESTAMP_FORMAT = '%Y%m%d_%H%M%S'
24
+ private_constant :TIMESTAMP_FORMAT
25
+
26
+ def timestamp
27
+ Time.now.strftime(TIMESTAMP_FORMAT)
28
+ end
29
+ end
30
+ end
31
+ end