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.
- checksums.yaml +5 -5
- data/.rubocop.yml +9 -13
- data/.travis.yml +2 -2
- data/CHANGELOG.md +9 -0
- data/Gemfile +2 -0
- data/README.md +28 -29
- data/Rakefile +2 -0
- data/bin/console +2 -8
- data/bin/pg_export +44 -45
- data/lib/pg_export/build_logger.rb +4 -2
- data/lib/pg_export/configuration.rb +22 -2
- data/lib/pg_export/container.rb +48 -0
- data/lib/pg_export/import.rb +7 -0
- data/lib/pg_export/lib/pg_export/adapters/bash_adapter.rb +37 -0
- data/lib/pg_export/lib/pg_export/adapters/ftp_adapter.rb +67 -0
- data/lib/pg_export/lib/pg_export/entities/dump.rb +45 -0
- data/lib/pg_export/lib/pg_export/factories/cipher_factory.rb +32 -0
- data/lib/pg_export/lib/pg_export/factories/dump_factory.rb +31 -0
- data/lib/pg_export/lib/pg_export/factories/ftp_adapter_factory.rb +22 -0
- data/lib/pg_export/lib/pg_export/listeners/interactive/build_dump.rb +19 -0
- data/lib/pg_export/lib/pg_export/listeners/interactive/close_ftp_connection.rb +19 -0
- data/lib/pg_export/lib/pg_export/listeners/interactive/decrypt_dump.rb +19 -0
- data/lib/pg_export/lib/pg_export/listeners/interactive/download_dump_from_ftp.rb +19 -0
- data/lib/pg_export/lib/pg_export/listeners/interactive/encrypt_dump.rb +19 -0
- data/lib/pg_export/lib/pg_export/listeners/interactive/fetch_dumps_from_ftp.rb +19 -0
- data/lib/pg_export/lib/pg_export/listeners/interactive/open_ftp_connection.rb +19 -0
- data/lib/pg_export/lib/pg_export/listeners/interactive/remove_old_dumps_from_ftp.rb +23 -0
- data/lib/pg_export/lib/pg_export/listeners/interactive/restore.rb +19 -0
- data/lib/pg_export/lib/pg_export/listeners/interactive/upload_dump_to_ftp.rb +19 -0
- data/lib/pg_export/lib/pg_export/listeners/interactive_listener.rb +49 -0
- data/lib/pg_export/lib/pg_export/listeners/plain/build_dump.rb +15 -0
- data/lib/pg_export/lib/pg_export/listeners/plain/close_ftp_connection.rb +15 -0
- data/lib/pg_export/lib/pg_export/listeners/plain/decrypt_dump.rb +15 -0
- data/lib/pg_export/lib/pg_export/listeners/plain/download_dump_from_ftp.rb +15 -0
- data/lib/pg_export/lib/pg_export/listeners/plain/encrypt_dump.rb +15 -0
- data/lib/pg_export/lib/pg_export/listeners/plain/fetch_dumps_from_ftp.rb +15 -0
- data/lib/pg_export/lib/pg_export/listeners/plain/open_ftp_connection.rb +15 -0
- data/lib/pg_export/lib/pg_export/listeners/plain/remove_old_dumps_from_ftp.rb +17 -0
- data/lib/pg_export/lib/pg_export/listeners/plain/restore.rb +15 -0
- data/lib/pg_export/lib/pg_export/listeners/plain/upload_dump_to_ftp.rb +15 -0
- data/lib/pg_export/lib/pg_export/listeners/plain_listener.rb +17 -0
- data/lib/pg_export/lib/pg_export/operations/decrypt_dump.rb +20 -0
- data/lib/pg_export/lib/pg_export/operations/encrypt_dump.rb +18 -0
- data/lib/pg_export/lib/pg_export/operations/open_ftp_connection.rb +19 -0
- data/lib/pg_export/lib/pg_export/operations/remove_old_dumps_from_ftp.rb +22 -0
- data/lib/pg_export/lib/pg_export/repositories/ftp_dump_file_repository.rb +19 -0
- data/lib/pg_export/lib/pg_export/repositories/ftp_dump_repository.rb +36 -0
- data/lib/pg_export/lib/pg_export/transactions/export_dump.rb +56 -0
- data/lib/pg_export/lib/pg_export/transactions/import_dump_interactively.rb +67 -0
- data/lib/pg_export/lib/pg_export/types.rb +14 -0
- data/lib/pg_export/lib/pg_export/ui/interactive/input.rb +36 -0
- data/lib/pg_export/lib/pg_export/ui/plain/input.rb +17 -0
- data/lib/pg_export/lib/pg_export/value_objects/dump_file.rb +70 -0
- data/lib/pg_export/system/boot/config.rb +11 -0
- data/lib/pg_export/system/boot/interactive.rb +32 -0
- data/lib/pg_export/system/boot/logger.rb +15 -0
- data/lib/pg_export/system/boot/plain.rb +33 -0
- data/lib/pg_export/version.rb +3 -1
- data/lib/pg_export.rb +23 -20
- data/pg_export.gemspec +13 -10
- metadata +108 -52
- data/lib/pg_export/aes/base.rb +0 -47
- data/lib/pg_export/aes/decryptor.rb +0 -13
- data/lib/pg_export/aes/encryptor.rb +0 -13
- data/lib/pg_export/aes.rb +0 -3
- data/lib/pg_export/bash/adapter.rb +0 -31
- data/lib/pg_export/bash/factory.rb +0 -23
- data/lib/pg_export/bash/repository.rb +0 -18
- data/lib/pg_export/boot_container.rb +0 -69
- data/lib/pg_export/dump.rb +0 -62
- data/lib/pg_export/errors.rb +0 -5
- data/lib/pg_export/ftp/adapter.rb +0 -41
- data/lib/pg_export/ftp/connection.rb +0 -40
- data/lib/pg_export/ftp/repository.rb +0 -40
- data/lib/pg_export/roles/colourable_string.rb +0 -19
- data/lib/pg_export/roles/human_readable.rb +0 -17
- data/lib/pg_export/roles/interactive.rb +0 -98
- data/lib/pg_export/roles/validatable.rb +0 -24
- data/lib/pg_export/services/create_and_export_dump.rb +0 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e6e920a09c0e04dd2b2e2eb612db083a390cbb74fae09bc1b5cfa510bc74751a
|
|
4
|
+
data.tar.gz: d23c28f594f37ac01bd586668bb62b8641ce56b9b1c5f4ff05a528dc1e2a69b2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92a31d0fa7d86a759060cf0f404fcc35e6d14a6f8043b66c5782e3b69646c2a1a5708276a88697d7f8e40fc85ae5a464ad98129e89e5b9f6bc61f6dfb72039c4
|
|
7
|
+
data.tar.gz: 4b6d1c3a9889a6b7b5a494605e300684680fa21d40ed1f79f9ae64356555f4fea6e5b4245b835e3e705d1c9f53459117ba672ebe70db4306e01f61e80cae9605
|
data/.rubocop.yml
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
-
TargetRubyVersion: 2.
|
|
3
|
-
Exclude:
|
|
4
|
-
- 'spec/spec_helper.rb'
|
|
2
|
+
TargetRubyVersion: 2.3
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
Metrics/BlockLength:
|
|
7
5
|
Exclude:
|
|
8
6
|
- 'bin/pg_export'
|
|
9
|
-
|
|
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.
|
|
4
|
+
- 2.3.0
|
|
5
5
|
addons:
|
|
6
6
|
code_climate:
|
|
7
7
|
repo_token: db03e5968c5bcd68b12ca50f5d41ae07dd74fe80d4e1421d754e31c316e7477a
|
|
8
|
-
before_install: gem install bundler -v 1.
|
|
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
data/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Features:
|
|
|
26
26
|
|
|
27
27
|
## Dependencies
|
|
28
28
|
|
|
29
|
-
* Ruby >= 2.
|
|
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
|
-
-
|
|
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
|
|
67
|
+
__Step 1.__ Prepare ENV variables.
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
BACKUP_FTP_HOST=
|
|
70
|
-
BACKUP_FTP_USER=
|
|
71
|
-
BACKUP_FTP_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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
84
|
+
__Step 2.__ Print the configuration to verify if env variables has been loaded properly.
|
|
86
85
|
|
|
87
86
|
$ pg_export --configuration
|
|
88
|
-
=> {:
|
|
89
|
-
|
|
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
|
|
90
|
+
__Step 3.__ Try connecting to FTP to verify the connection.
|
|
92
91
|
|
|
93
92
|
$ pg_export --ftp
|
|
94
|
-
=>
|
|
93
|
+
=> 230 User your_ftp_user logged in
|
|
95
94
|
|
|
96
|
-
__Step
|
|
95
|
+
__Step 4.__ Perform database export.
|
|
97
96
|
|
|
98
|
-
$ pg_export -d your_database
|
|
99
|
-
=>
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
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/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 |
|
|
24
|
-
|
|
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]',
|
|
28
|
-
|
|
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
|
-
|
|
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
|
|
36
|
-
|
|
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
|
-
|
|
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::
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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,
|
|
12
|
-
attribute :
|
|
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,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
|