pg_export 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,98 @@
1
+ require 'cli_spinnable'
2
+ require_relative 'colourable_string'
3
+
4
+ class PgExport
5
+ module Roles
6
+ module Interactive
7
+ include CliSpinnable
8
+ using ColourableString
9
+
10
+ def self.extended(_)
11
+ puts 'Interactive mode, for restoring dump into database.'.green
12
+ end
13
+
14
+ def call(database, _keep_dumps = nil)
15
+ initialize_connection
16
+ dumps = print_all_dumps
17
+ dump = download_dump(select_dump(dumps))
18
+ t = Thread.new { container[:ftp_connection].close }
19
+ restore_downloaded_dump(dump, database)
20
+ t.join
21
+ puts 'Success'.green
22
+ self
23
+ end
24
+
25
+ private
26
+
27
+ def initialize_connection
28
+ with_spinner do |cli|
29
+ cli.print 'Connecting to FTP'
30
+ container[:ftp_connection].ftp
31
+ cli.tick
32
+ end
33
+ end
34
+
35
+ def print_all_dumps
36
+ dumps = container[:ftp_repository].all
37
+ dumps.each.with_index(1) do |name, i|
38
+ print "(#{i}) "
39
+ puts name.to_s.gray
40
+ end
41
+
42
+ dumps
43
+ end
44
+
45
+ def select_dump(dumps)
46
+ puts 'Which dump would you like to import?'
47
+ number = loop do
48
+ print "Type from 1 to #{dumps.count} (1): "
49
+ number = gets.chomp.to_i
50
+ break number if (1..dumps.count).cover?(number)
51
+ puts 'Invalid number. Please try again.'.red
52
+ end
53
+
54
+ dumps.fetch(number - 1)
55
+ end
56
+
57
+ def download_dump(name)
58
+ dump = nil
59
+
60
+ with_spinner do |cli|
61
+ cli.print "Downloading dump #{name}"
62
+ encrypted_dump = container[:ftp_repository].get(name)
63
+ cli.print " (#{encrypted_dump.size_human})"
64
+ cli.tick
65
+ cli.print "Decrypting dump #{name}"
66
+ dump = container[:decryptor].call(encrypted_dump)
67
+ cli.print " (#{dump.size_human})"
68
+ cli.tick
69
+ end
70
+
71
+ dump
72
+ rescue OpenSSL::Cipher::CipherError => e
73
+ puts "Problem decrypting dump file: #{e}. Try again.".red
74
+ retry
75
+ end
76
+
77
+ def restore_downloaded_dump(dump, database)
78
+ puts 'To which database you would like to restore the downloaded dump?'
79
+ if database == 'undefined'
80
+ print 'Enter a local database name: '
81
+ else
82
+ print "Enter a local database name (#{database}): "
83
+ end
84
+ db_name = gets.chomp
85
+ db_name = db_name.empty? ? database : db_name
86
+ with_spinner do |cli|
87
+ cli.print "Restoring dump to #{db_name} database"
88
+ container[:bash_repository].persist(dump, db_name)
89
+ cli.tick
90
+ end
91
+ self
92
+ rescue PgRestoreError => e
93
+ puts e.to_s.red
94
+ retry
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,24 @@
1
+ require 'dry-types'
2
+
3
+ class PgExport
4
+ module Roles
5
+ module Validatable
6
+ include Dry::Types.module
7
+
8
+ VALID_NON_EMPTY_STRING = Strict::String.constrained(min_size: 1)
9
+ VALID_POSITIVE_INTEGER = Strict::Int.constrained(gteq: 0)
10
+
11
+ def validate_database_name(database)
12
+ VALID_NON_EMPTY_STRING[database]
13
+ rescue Dry::Types::ConstraintError
14
+ raise ArgumentError, 'The "database" parameter has to be a valid, non-empty string'
15
+ end
16
+
17
+ def validate_keep_dumps(keep_dumps)
18
+ VALID_POSITIVE_INTEGER[keep_dumps]
19
+ rescue Dry::Types::ConstraintError
20
+ raise ArgumentError, 'The "keep_dumps" parameter has to be a valid, non-negative integer'
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ class PgExport
2
+ module Services
3
+ class CreateAndExportDump
4
+ def initialize(bash_factory:, encryptor:, ftp_repository:)
5
+ @bash_factory, @encryptor, @ftp_repository = bash_factory, encryptor, ftp_repository
6
+ end
7
+
8
+ def call(database_name, keep_dumps)
9
+ dump = bash_factory.build_dump(database_name)
10
+ encrypted_dump = encryptor.call(dump)
11
+ ftp_repository.persist(encrypted_dump)
12
+ ftp_repository.remove_old(database_name, keep_dumps)
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :bash_factory, :encryptor, :ftp_repository
18
+ end
19
+ end
20
+ end
@@ -1,3 +1,3 @@
1
1
  class PgExport
2
- VERSION = '0.5.1'.freeze
2
+ VERSION = '0.6.0'.freeze
3
3
  end
data/lib/pg_export.rb CHANGED
@@ -1,67 +1,29 @@
1
- require 'logger'
2
- require 'tempfile'
3
- require 'zlib'
4
- require 'net/ftp'
5
- require 'openssl'
6
- require 'forwardable'
7
- require 'open3'
8
-
9
1
  require 'pg_export/version'
10
- require 'pg_export/includable_modules/logging'
11
- require 'pg_export/includable_modules/dump/size_human'
12
- require 'pg_export/includable_modules/services_container'
13
- require 'pg_export/errors'
14
2
  require 'pg_export/configuration'
15
- require 'pg_export/entities/dump'
16
- require 'pg_export/services/ftp_adapter'
17
- require 'pg_export/services/ftp_connection'
18
- require 'pg_export/services/bash_utils'
19
- require 'pg_export/services/dump_storage'
20
- require 'pg_export/services/aes'
21
- require 'pg_export/services/aes/base'
22
- require 'pg_export/services/aes/encryptor'
23
- require 'pg_export/services/aes/decryptor'
3
+ require 'pg_export/boot_container'
4
+ require 'pg_export/roles/interactive'
5
+ require 'pg_export/errors'
6
+ require 'pg_export/roles/validatable'
24
7
 
25
8
  class PgExport
26
- extend Forwardable
27
- include ServicesContainer
28
-
29
- def_delegators :services_container, :config, :bash_utils, :dump_storage, :ftp_connection, :encryptor, :decryptor
30
-
31
- def initialize
32
- yield config if block_given?
9
+ include Roles::Validatable
10
+
11
+ def initialize(**args)
12
+ config = Configuration.new(**args)
13
+ extend Roles::Interactive if config.interactive
14
+ @container = BootContainer.call(config.to_h)
15
+ rescue Dry::Struct::Error => e
16
+ raise ArgumentError, e
33
17
  end
34
18
 
35
- def call
36
- config.validate
37
- concurrently do |threads|
38
- threads << create_dump
39
- threads << open_ftp_connection
40
- end
41
- dump_storage.upload(dump)
42
- dump_storage.remove_old
43
- self
19
+ def call(database_name, keep_dumps)
20
+ container[:create_and_export_dump].call(
21
+ validate_database_name(database_name),
22
+ validate_keep_dumps(keep_dumps)
23
+ )
44
24
  end
45
25
 
46
26
  private
47
27
 
48
- def concurrently
49
- [].tap do |threads|
50
- yield threads
51
- end.each(&:join)
52
- end
53
-
54
- def dump
55
- create_dump[:dump]
56
- end
57
-
58
- def create_dump
59
- @create_dump ||= Thread.new do
60
- Thread.current[:dump] = encryptor.call(bash_utils.create_dump)
61
- end
62
- end
63
-
64
- def open_ftp_connection
65
- Thread.new { ftp_connection.open }
66
- end
28
+ attr_reader :container
67
29
  end
data/pg_export.gemspec CHANGED
@@ -21,12 +21,13 @@ Gem::Specification.new do |spec|
21
21
  spec.required_ruby_version = '>= 2.1.0'
22
22
 
23
23
  spec.add_dependency 'cli_spinnable', '~> 0.2'
24
+ spec.add_dependency 'dry-types', '~> 0.11.1'
25
+ spec.add_dependency 'dry-struct', '~> 0.3.1'
24
26
 
25
27
  spec.add_development_dependency 'bundler', '~> 1.10'
26
28
  spec.add_development_dependency 'rubocop', '~> 0.44'
27
29
  spec.add_development_dependency 'rake', '~> 10.0'
28
30
  spec.add_development_dependency 'rspec', '~> 3.4'
29
31
  spec.add_development_dependency 'pg', '~> 0.19'
30
- spec.add_development_dependency 'simplecov', '~> 0.12'
31
32
  spec.add_development_dependency 'pry'
32
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_export
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Krzysztof Maicher
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-31 00:00:00.000000000 Z
11
+ date: 2017-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cli_spinnable
@@ -24,6 +24,34 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-types
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.11.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.11.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-struct
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.1
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: bundler
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -94,20 +122,6 @@ dependencies:
94
122
  - - "~>"
95
123
  - !ruby/object:Gem::Version
96
124
  version: '0.19'
97
- - !ruby/object:Gem::Dependency
98
- name: simplecov
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '0.12'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '0.12'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: pry
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -147,22 +161,26 @@ files:
147
161
  - bin/pg_export
148
162
  - bin/setup
149
163
  - lib/pg_export.rb
164
+ - lib/pg_export/aes.rb
165
+ - lib/pg_export/aes/base.rb
166
+ - lib/pg_export/aes/decryptor.rb
167
+ - lib/pg_export/aes/encryptor.rb
168
+ - lib/pg_export/bash/adapter.rb
169
+ - lib/pg_export/bash/factory.rb
170
+ - lib/pg_export/bash/repository.rb
171
+ - lib/pg_export/boot_container.rb
172
+ - lib/pg_export/build_logger.rb
150
173
  - lib/pg_export/configuration.rb
151
- - lib/pg_export/entities/dump.rb
174
+ - lib/pg_export/dump.rb
152
175
  - lib/pg_export/errors.rb
153
- - lib/pg_export/includable_modules/colourable_string.rb
154
- - lib/pg_export/includable_modules/dump/size_human.rb
155
- - lib/pg_export/includable_modules/interactive.rb
156
- - lib/pg_export/includable_modules/logging.rb
157
- - lib/pg_export/includable_modules/services_container.rb
158
- - lib/pg_export/services/aes.rb
159
- - lib/pg_export/services/aes/base.rb
160
- - lib/pg_export/services/aes/decryptor.rb
161
- - lib/pg_export/services/aes/encryptor.rb
162
- - lib/pg_export/services/bash_utils.rb
163
- - lib/pg_export/services/dump_storage.rb
164
- - lib/pg_export/services/ftp_adapter.rb
165
- - lib/pg_export/services/ftp_connection.rb
176
+ - lib/pg_export/ftp/adapter.rb
177
+ - lib/pg_export/ftp/connection.rb
178
+ - lib/pg_export/ftp/repository.rb
179
+ - lib/pg_export/roles/colourable_string.rb
180
+ - lib/pg_export/roles/human_readable.rb
181
+ - lib/pg_export/roles/interactive.rb
182
+ - lib/pg_export/roles/validatable.rb
183
+ - lib/pg_export/services/create_and_export_dump.rb
166
184
  - lib/pg_export/version.rb
167
185
  - pg_export.gemspec
168
186
  homepage: https://github.com/maicher/pg_export
@@ -185,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
185
203
  version: '0'
186
204
  requirements: []
187
205
  rubyforge_project:
188
- rubygems_version: 2.6.8
206
+ rubygems_version: 2.5.1
189
207
  signing_key:
190
208
  specification_version: 4
191
209
  summary: CLI for creating and exporting PostgreSQL dumps to FTP.
@@ -1,17 +0,0 @@
1
- class PgExport
2
- module ColourableString
3
- refine String do
4
- def red
5
- "\e[31m#{self}\e[0m"
6
- end
7
-
8
- def green
9
- "\e[0;32;49m#{self}\e[0m"
10
- end
11
-
12
- def gray
13
- "\e[37m#{self}\e[0m"
14
- end
15
- end
16
- end
17
- end
@@ -1,15 +0,0 @@
1
- class PgExport
2
- class Dump
3
- module SizeHuman
4
- def size_human
5
- {
6
- 'B' => 1024,
7
- 'kB' => 1024 * 1024,
8
- 'MB' => 1024 * 1024 * 1024,
9
- 'GB' => 1024 * 1024 * 1024 * 1024,
10
- 'TB' => 1024 * 1024 * 1024 * 1024 * 1024
11
- }.each_pair { |e, s| return "#{(size.to_f / (s / 1024)).round(2)}#{e}" if size < s }
12
- end
13
- end
14
- end
15
- end
@@ -1,97 +0,0 @@
1
- class PgExport
2
- module Interactive
3
- include CliSpinnable
4
- using ColourableString
5
-
6
- def self.extended(_)
7
- puts 'Interactive mode, for restoring dump into database.'.green
8
- end
9
-
10
- def call
11
- initialize_connection
12
- print_all_dumps
13
- selected_dump = select_dump
14
- dump = download_dump(selected_dump)
15
- concurrently do |threads|
16
- threads << Thread.new(dump) { restore_downloaded_dump(dump) }
17
- threads << Thread.new { ftp_connection.close }
18
- end
19
- puts 'Success'.green
20
- self
21
- end
22
-
23
- private
24
-
25
- def initialize_connection
26
- with_spinner do |cli|
27
- cli.print 'Connecting to FTP'
28
- ftp_connection.open
29
- cli.tick
30
- end
31
- end
32
-
33
- def print_all_dumps
34
- dumps.each.with_index(1) do |name, i|
35
- print "(#{i}) "
36
- puts name.to_s.gray
37
- end
38
- self
39
- end
40
-
41
- def select_dump
42
- puts 'Which dump would you like to import?'
43
- number = loop do
44
- print "Type from 1 to #{dumps.count} (1): "
45
- number = gets.chomp.to_i
46
- break number if (1..dumps.count).cover?(number)
47
- puts 'Invalid number. Please try again.'.red
48
- end
49
-
50
- dumps.fetch(number - 1)
51
- end
52
-
53
- def download_dump(name)
54
- dump = nil
55
-
56
- with_spinner do |cli|
57
- cli.print "Downloading dump #{name}"
58
- encrypted_dump = dump_storage.download(name)
59
- cli.print " (#{encrypted_dump.size_human})"
60
- cli.tick
61
- cli.print "Decrypting dump #{name}"
62
- dump = decryptor.call(encrypted_dump)
63
- cli.print " (#{dump.size_human})"
64
- cli.tick
65
- end
66
-
67
- dump
68
- rescue OpenSSL::Cipher::CipherError => e
69
- puts "Problem decrypting dump file: #{e}. Try again.".red
70
- retry
71
- end
72
-
73
- def restore_downloaded_dump(dump)
74
- puts 'To which database you would like to restore the downloaded dump?'
75
- if config.database == 'undefined'
76
- print 'Enter a local database name: '
77
- else
78
- print "Enter a local database name (#{config.database}): "
79
- end
80
- database = gets.chomp
81
- database = database.empty? ? config.database : database
82
- with_spinner do |cli|
83
- cli.print "Restoring dump to #{database} database"
84
- bash_utils.restore_dump(dump, database)
85
- cli.tick
86
- end
87
- self
88
- rescue PgRestoreError => e
89
- puts e.to_s.red
90
- retry
91
- end
92
-
93
- def dumps
94
- @dumps ||= dump_storage.all
95
- end
96
- end
97
- end
@@ -1,31 +0,0 @@
1
- class PgExport
2
- module Logging
3
- FORMAT_PLAIN = ->(_, _, _, message) { "#{message}\n" }
4
- FORMAT_TIMESTAMPED = ->(severity, datetime, progname, message) { "#{datetime} #{Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{progname} #{severity}: #{message}\n" }
5
- FORMAT_MUTED = ->(_, _, _, _) {}
6
-
7
- class << self
8
- def logger
9
- @logger ||= Logger.new(STDOUT).tap do |logger|
10
- logger.formatter = FORMAT_PLAIN
11
- end
12
- end
13
-
14
- def format_default
15
- logger.formatter = FORMAT_PLAIN
16
- end
17
-
18
- def format_timestamped
19
- logger.formatter = FORMAT_TIMESTAMPED
20
- end
21
-
22
- def mute
23
- logger.formatter = FORMAT_MUTED
24
- end
25
- end
26
-
27
- def logger
28
- Logging.logger
29
- end
30
- end
31
- end
@@ -1,41 +0,0 @@
1
- class PgExport
2
- module ServicesContainer
3
- class << self
4
- def config
5
- @config ||= Configuration.new
6
- end
7
-
8
- def aes
9
- @aes ||= Aes.new(config.dump_encryption_key)
10
- end
11
-
12
- def encryptor
13
- @encryptor ||= aes.build_encryptor
14
- end
15
-
16
- def decryptor
17
- @decryptor ||= aes.build_decryptor
18
- end
19
-
20
- def bash_utils
21
- @bash_utils ||= BashUtils.new(config.database)
22
- end
23
-
24
- def ftp_connection
25
- @ftp_connection ||= FtpConnection.new(config.ftp_params)
26
- end
27
-
28
- def ftp_adapter
29
- @ftp_adapter ||= FtpAdapter.new(ftp_connection)
30
- end
31
-
32
- def dump_storage
33
- @dump_storage ||= DumpStorage.new(ftp_adapter, config.database, config.keep_dumps)
34
- end
35
- end
36
-
37
- def services_container
38
- @services_container ||= ServicesContainer
39
- end
40
- end
41
- end
@@ -1,26 +0,0 @@
1
- class PgExport
2
- class Aes
3
- class Base
4
- include Logging
5
-
6
- def initialize(cipher)
7
- @cipher = cipher
8
- end
9
-
10
- private
11
-
12
- def copy_using(cipher, from:, to:)
13
- cipher.reset
14
- to.open(:write) do |f|
15
- from.each_chunk do |chunk|
16
- f << cipher.update(chunk)
17
- end
18
- f << cipher.final
19
- end
20
- self
21
- end
22
-
23
- attr_reader :cipher
24
- end
25
- end
26
- end
@@ -1,12 +0,0 @@
1
- class PgExport
2
- class Aes
3
- class Decryptor < Base
4
- def call(enc_dump)
5
- dump = Dump.new('Dump')
6
- copy_using(cipher, from: enc_dump, to: dump)
7
- logger.info "Create #{dump}"
8
- dump
9
- end
10
- end
11
- end
12
- end
@@ -1,12 +0,0 @@
1
- class PgExport
2
- class Aes
3
- class Encryptor < Base
4
- def call(dump)
5
- enc_dump = Dump.new('Encrypted Dump')
6
- copy_using(cipher, from: dump, to: enc_dump)
7
- logger.info "Create #{enc_dump}"
8
- enc_dump
9
- end
10
- end
11
- end
12
- end
@@ -1,28 +0,0 @@
1
- class PgExport
2
- class Aes
3
- ALGORITHM = 'AES-128-CBC'.freeze
4
-
5
- def initialize(key)
6
- @key = key
7
- end
8
-
9
- def build_encryptor
10
- Aes::Encryptor.new(cipher(:encrypt))
11
- end
12
-
13
- def build_decryptor
14
- Aes::Decryptor.new(cipher(:decrypt))
15
- end
16
-
17
- private
18
-
19
- attr_reader :key
20
-
21
- def cipher(mode)
22
- OpenSSL::Cipher.new(ALGORITHM).tap do |cipher|
23
- cipher.public_send(mode.to_sym)
24
- cipher.key = key
25
- end
26
- end
27
- end
28
- end