pg_export 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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