webtranslateit-safe 0.4.3 → 0.4.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43afe3f6fffb6bb4178cfd6c6181767f67f7995f2e836d35c9d5c66556b567fb
4
- data.tar.gz: 3fe840117d0c799b65edcafc92b57f73e7ba928297fe53bc1f8262059404e5fe
3
+ metadata.gz: 63bd94e905a8a424ce2b408fa3d0d2898fc5d22c3be9739a95ff73668cd612ad
4
+ data.tar.gz: 2f33a476839a38aa403e89a3f0e35ad5c78a9c403756c0472f15c45337a0b575
5
5
  SHA512:
6
- metadata.gz: 1ad57b335c7585cea8621bbd6007fcd1f2d10e6fe7ba9fa0b1381ce00ce96eb38eb11925912185e06ab3ba2e69ebec165b2fa9a268c0cbeeb0b765e43b166b6f
7
- data.tar.gz: c51e5399dc02aa3fe4b0d0bfb99ed6397c9abdbc16ebf5dc8994d056151fcee9d340f0b8085d52f333d00b3eb8d1c0bbbd5b60c28afdde36b7065ed0a821492d
6
+ metadata.gz: b50f77cc5ee6b609ad05e85fa5fc106ed21af49e014397427aa9a77de4bba9e5d6e3cff8773d78e14314e44388d6f0cf6cbf258d0e3ffd9db0ddeedc063a525d
7
+ data.tar.gz: e21c75b914bf576842f22893098c872659ba5d7dd27b84d4f087226e7aa80a43f32a133f668a20c09492762f2459707ae1c3e7f9e074db4a8b9fc7c933b4f885
data/CHANGELOG CHANGED
@@ -1,36 +1,48 @@
1
- 0.4.3
1
+ ## 0.4.5 - 2023-06-30
2
+
3
+ * Add SCP backup strategy.
4
+
5
+ ## 0.4.4 - 2023-06-30
6
+
7
+ * Remove `default_executable` line and simplify executables definition.
8
+ * Autocorrect rubocop offences.
9
+ * Convert rspec syntax from `should` to `expect`.
10
+ * Remove unused extension `Dir.mktmpdir`.
11
+ * Upload retry logic for SFTP.
12
+
13
+ ## 0.4.3 - 2023-06-29
2
14
 
3
15
  * Bug fix on builder.
4
16
  * Move version number to gemspec file.
5
17
 
6
- 0.4.2
18
+ ## 0.4.2 - 2023-05-22
7
19
 
8
20
  * Bug fix on executable
9
21
 
10
- 0.4.1
22
+ ## 0.4.1 - 2023-05-20
11
23
 
12
24
  * Add ruby 3.2 compantibility
13
25
  * Modernize gem
14
26
  * Rename astrails-safe to webtranslateit-safe
15
27
 
16
- 0.3.1
28
+ ## 0.3.1
17
29
 
18
30
  * plain ftp support from seroy
19
31
  * mongodump support from Matt Berther
20
32
 
21
- 0.3.0
33
+ ## 0.3.0
22
34
 
23
35
  * switch to bundler
24
36
  * fixed the rspec
25
37
 
26
- 0.2.8
38
+ ## 0.2.8
27
39
 
28
40
  * ruby 1.9.2 compatibility (tests mostly)
29
41
  * code review, and tons of small fixes
30
42
  * check file size before attempting to upload to cloudfiles
31
43
  * testing framework changed from micronaut to rspec
32
44
 
33
- 0.2.7
45
+ ## 0.2.7
34
46
 
35
47
  * default options for gpg now include '--no-use-agent'
36
48
  * support for 'command' option for gpg
@@ -38,13 +50,13 @@
38
50
  * add 'lib' to $:
39
51
  * [EXPERIMENTAL] Rackspace Cloud Files support
40
52
 
41
- 0.2.6
53
+ ## 0.2.6
42
54
 
43
55
  * fix typo in the template config file. (change option to options in pgdump)
44
56
  * add example 'options' for tar in the template config file.
45
57
  * do not try to upload more then 5G of data to S3. print error instead
46
58
 
47
- 0.2.5
59
+ ## 0.2.5
48
60
 
49
61
  * Safety mesure: Disable overwrite of existing configuration keys except for multi-value keys
50
62
  supported multi-value keys: skip_tables, exclude, files
data/README.markdown CHANGED
@@ -155,6 +155,13 @@ The procedure to create and transfer the key is as follows:
155
155
  password "ssh password for sftp"
156
156
  end
157
157
 
158
+ sftp do
159
+ host "sftp.astrails.com"
160
+ user "astrails"
161
+ # port 8023
162
+ password "ssh password for scp"
163
+ end
164
+
158
165
  gpg do
159
166
  command "/usr/local/bin/gpg"
160
167
  options "--no-use-agent"
@@ -170,6 +177,7 @@ The procedure to create and transfer the key is as follows:
170
177
  s3 100
171
178
  cloudfiles 100
172
179
  sftp 100
180
+ scp 100
173
181
  end
174
182
 
175
183
  mysqldump do
@@ -12,16 +12,16 @@ def die(msg)
12
12
  end
13
13
 
14
14
  def usage
15
- puts <<-END
16
- Usage: webtranslateit-safe [OPTIONS] CONFIG_FILE
17
- Options:
18
- -h, --help This help screen
19
- -v, --verbose be verbose, duh!
20
- -n, --dry-run just pretend, don't do anything.
21
- -L, --local skip S3 and Cloud Files
22
-
23
- Note: config file will be created from template if missing
24
- END
15
+ puts <<~END
16
+ Usage: webtranslateit-safe [OPTIONS] CONFIG_FILE
17
+ Options:
18
+ -h, --help This help screen
19
+ -v, --verbose be verbose, duh!
20
+ -n, --dry-run just pretend, don't do anything.
21
+ -L, --local skip S3 and Cloud Files
22
+
23
+ Note: config file will be created from template if missing
24
+ END
25
25
  exit 1
26
26
  end
27
27
 
@@ -30,7 +30,7 @@ OPTS = [
30
30
  '-v', '--verbose', '--not-verbose',
31
31
  '-n', '--dry-run', '--not-dry-run',
32
32
  '-L', '--local', '--not-local'
33
- ]
33
+ ].freeze
34
34
  def main
35
35
  opts = ARGV & OPTS
36
36
  args = ARGV - OPTS
@@ -40,7 +40,7 @@ def main
40
40
 
41
41
  config_file = File.expand_path(args.first)
42
42
 
43
- is_dry = (opts.delete('-n') || opts.delete('--dry-run')) && ! opts.delete('--not-dry-run')
43
+ is_dry = (opts.delete('-n') || opts.delete('--dry-run')) && !opts.delete('--not-dry-run')
44
44
  is_verbose = (opts.delete('-v') || opts.delete('--verbose')) && !opts.delete('--not-verbose')
45
45
  is_local_only = (opts.delete('-L') || opts.delete('--local')) && !opts.delete('--not-local')
46
46
 
@@ -61,4 +61,4 @@ def main
61
61
  process config
62
62
  end
63
63
 
64
- main
64
+ main
@@ -1,24 +1,29 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Archive < Source
4
6
 
5
7
  def command
6
8
  "tar -cf - #{config[:options]} #{tar_exclude_files} #{tar_files}"
7
9
  end
8
10
 
9
- def extension; '.tar'; end
11
+ def extension = '.tar'
10
12
 
11
13
  protected
12
14
 
13
15
  def tar_exclude_files
14
- [*config[:exclude]].compact.map{|x| "--exclude=#{x}"}.join(' ')
16
+ [*config[:exclude]].compact.map { |x| "--exclude=#{x}" }.join(' ')
15
17
  end
16
18
 
17
19
  def tar_files
18
- raise RuntimeError, 'missing files for tar' unless config[:files]
19
- [*config[:files]].map{|s| s.strip}.join(' ')
20
+ raise 'missing files for tar' unless config[:files]
21
+
22
+ [*config[:files]].map(&:strip).join(' ')
20
23
  end
21
24
 
22
25
  end
26
+
23
27
  end
28
+
24
29
  end
@@ -1,10 +1,14 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Backup
6
+
4
7
  attr_accessor :id, :kind, :filename, :extension, :command, :compressed, :timestamp, :path
8
+
5
9
  def initialize(opts = {})
6
10
  opts.each do |k, v|
7
- self.send("#{k}=", v)
11
+ send("#{k}=", v)
8
12
  end
9
13
  end
10
14
 
@@ -15,6 +19,9 @@ module WebTranslateIt
15
19
  WebTranslateIt::Safe.const_get(mod).new(config, self).process
16
20
  end
17
21
  end
22
+
18
23
  end
24
+
19
25
  end
20
- end
26
+
27
+ end
@@ -1,7 +1,10 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Cloudfiles < Sink
4
- MAX_CLOUDFILES_FILE_SIZE = 5368709120
6
+
7
+ MAX_CLOUDFILES_FILE_SIZE = 5_368_709_120
5
8
 
6
9
  def active?
7
10
  container && user && api_key
@@ -21,24 +24,24 @@ module WebTranslateIt
21
24
  end
22
25
 
23
26
  def save
24
- raise RuntimeError, 'pipe-streaming not supported for S3.' unless @backup.path
27
+ raise 'pipe-streaming not supported for S3.' unless @backup.path
25
28
 
26
29
  # needed in cleanup even on dry run
27
30
  cf = CloudFiles::Connection.new(user, api_key, true, service_net) unless local_only?
28
31
  puts "Uploading #{container}:#{full_path} from #{@backup.path}" if verbose? || dry_run?
29
- unless dry_run? || local_only?
30
- if get_file_size(@backup.path) > MAX_CLOUDFILES_FILE_SIZE
31
- STDERR.puts "ERROR: File size exceeds maximum allowed for upload to Cloud Files (#{MAX_CLOUDFILES_FILE_SIZE}): #{@backup.path}"
32
- return
33
- end
34
- benchmark = Benchmark.realtime do
35
- cf_container = cf.create_container(container)
36
- o = cf_container.create_object(full_path,true)
37
- o.write(File.open(@backup.path))
38
- end
39
- puts '...done' if verbose?
40
- puts('Upload took ' + sprintf('%.2f', benchmark) + ' second(s).') if verbose?
32
+ return if dry_run? || local_only?
33
+
34
+ if get_file_size(@backup.path) > MAX_CLOUDFILES_FILE_SIZE
35
+ warn "ERROR: File size exceeds maximum allowed for upload to Cloud Files (#{MAX_CLOUDFILES_FILE_SIZE}): #{@backup.path}"
36
+ return
41
37
  end
38
+ benchmark = Benchmark.realtime do
39
+ cf_container = cf.create_container(container)
40
+ o = cf_container.create_object(full_path, true)
41
+ o.write(File.open(@backup.path))
42
+ end
43
+ puts '...done' if verbose?
44
+ puts("Upload took #{format('%.2f', benchmark)} second(s).") if verbose?
42
45
  end
43
46
 
44
47
  def cleanup
@@ -49,7 +52,7 @@ module WebTranslateIt
49
52
  puts "listing files: #{container}:#{base}*" if verbose?
50
53
  cf = CloudFiles::Connection.new(user, api_key, true, service_net) unless local_only?
51
54
  cf_container = cf.container(container)
52
- files = cf_container.objects(:prefix => base).sort
55
+ files = cf_container.objects(prefix: base).sort
53
56
 
54
57
  cleanup_with_limit(files, keep) do |f|
55
58
  puts "removing Cloud File #{container}:#{f}" if dry_run? || verbose?
@@ -72,6 +75,9 @@ module WebTranslateIt
72
75
  def service_net
73
76
  config[:cloudfiles, :service_net] || false
74
77
  end
78
+
75
79
  end
80
+
76
81
  end
82
+
77
83
  end
@@ -1,15 +1,19 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  module Config
6
+
4
7
  class Builder
5
8
 
6
9
  def initialize(node, data = {})
7
10
  @node = node
8
- data.each { |k, v| self.send k, v }
11
+ data.each { |k, v| send k, v }
9
12
  end
10
13
 
11
14
 
12
15
  class << self
16
+
13
17
  def simple_value(*names)
14
18
  names.each do |m|
15
19
  define_method(m) do |value|
@@ -40,7 +44,7 @@ module WebTranslateIt
40
44
 
41
45
  def mixed_value(*names)
42
46
  names.each do |m|
43
- define_method(m) do |data={}, &block|
47
+ define_method(m) do |data = {}, &block|
44
48
  ensure_uniq(m)
45
49
  if data.is_a?(Hash) || block
46
50
  ensure_hash(m, data) if block
@@ -54,8 +58,9 @@ module WebTranslateIt
54
58
 
55
59
  def collection(*names)
56
60
  names.each do |m|
57
- define_method(m) do |id, data={}, &block|
61
+ define_method(m) do |id, data = {}, &block|
58
62
  raise "bad collection id: #{id.inspect}" unless id
63
+
59
64
  ensure_hash(m, data)
60
65
 
61
66
  name = "#{m}s"
@@ -64,14 +69,15 @@ module WebTranslateIt
64
69
  end
65
70
  end
66
71
  end
72
+
67
73
  end
68
74
 
69
75
  simple_value :verbose, :dry_run, :local_only, :path, :command,
70
- :options, :user, :host, :port, :password, :key, :secret, :bucket,
71
- :api_key, :container, :socket, :service_net, :repo_path
76
+ :options, :user, :host, :port, :password, :key, :secret, :bucket,
77
+ :api_key, :container, :socket, :service_net, :repo_path
72
78
  multi_value :skip_tables, :exclude, :files
73
79
  mixed_value :s3, :local, :cloudfiles, :sftp, :mysqldump, :tar, :gpg, :keep, :pgdump, :tar, :svndump,
74
- :ftp, :mongodump
80
+ :ftp, :mongodump, :scp
75
81
  collection :database, :archive, :repo
76
82
 
77
83
  private
@@ -83,7 +89,11 @@ module WebTranslateIt
83
89
  def ensure_hash(k, v)
84
90
  raise "#{k}: hash expected: #{v.inspect}" unless v.is_a?(Hash)
85
91
  end
92
+
86
93
  end
94
+
87
95
  end
96
+
88
97
  end
98
+
89
99
  end
@@ -1,8 +1,12 @@
1
1
  require 'webtranslateit/safe/config/builder'
2
2
  module WebTranslateIt
3
+
3
4
  module Safe
5
+
4
6
  module Config
7
+
5
8
  class Node
9
+
6
10
  attr_reader :parent, :data
7
11
 
8
12
  def initialize(parent = nil, data = {}, &block)
@@ -11,7 +15,7 @@ module WebTranslateIt
11
15
  merge data, &block
12
16
  end
13
17
 
14
- def merge data = {}, &block
18
+ def merge(data = {}, &block)
15
19
  builder = Builder.new(self, data)
16
20
  builder.instance_eval(&block) if block
17
21
  self
@@ -21,27 +25,27 @@ module WebTranslateIt
21
25
  def get(*path)
22
26
  key = path.shift
23
27
  value = @data[key.to_s]
24
- return value if (nil != value) && path.empty?
28
+ return value if !value.nil? && path.empty?
25
29
 
26
- value && value.get(*path)
30
+ value&.get(*path)
27
31
  end
28
32
 
29
33
  # recursive find
30
34
  # starts at the node and continues to the parent
31
35
  def find(*path)
32
- get(*path) || @parent && @parent.find(*path)
36
+ get(*path) || @parent&.find(*path)
33
37
  end
34
- alias :[] :find
38
+ alias [] find
35
39
 
36
40
  def set_multi(key, value)
37
41
  @data[key.to_s] ||= []
38
- @data[key.to_s].concat [*value]
42
+ @data[key.to_s].push(*value)
39
43
  end
40
44
 
41
45
  def set(key, value)
42
46
  @data[key.to_s] = value
43
47
  end
44
- alias :[]= :set
48
+ alias []= set
45
49
 
46
50
  def each(&block)
47
51
  @data.each(&block)
@@ -49,10 +53,9 @@ module WebTranslateIt
49
53
  include Enumerable
50
54
 
51
55
  def to_hash
52
- @data.keys.inject({}) do |res, key|
56
+ @data.keys.each_with_object({}) do |key, res|
53
57
  value = @data[key]
54
58
  res[key] = value.is_a?(Node) ? value.to_hash : value
55
- res
56
59
  end
57
60
  end
58
61
 
@@ -60,13 +63,17 @@ module WebTranslateIt
60
63
  @data.each do |key, value|
61
64
  if value.is_a?(Node)
62
65
  puts "#{indent}#{key}:"
63
- value.dump(indent + ' ')
66
+ value.dump("#{indent} ")
64
67
  else
65
68
  puts "#{indent}#{key}: #{value.inspect}"
66
69
  end
67
70
  end
68
71
  end
72
+
69
73
  end
74
+
70
75
  end
76
+
71
77
  end
78
+
72
79
  end
@@ -1,5 +1,7 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Ftp < Sink
4
6
 
5
7
  protected
@@ -13,26 +15,24 @@ module WebTranslateIt
13
15
  end
14
16
 
15
17
  def save
16
- raise RuntimeError, 'pipe-streaming not supported for FTP.' unless @backup.path
18
+ raise 'pipe-streaming not supported for FTP.' unless @backup.path
17
19
 
18
20
  puts "Uploading #{host}:#{full_path} via FTP" if verbose? || dry_run?
19
21
 
20
- unless dry_run? || local_only?
21
- if !port
22
- port = 21
23
- end
24
- Net::FTP.open(host) do |ftp|
25
- ftp.connect(host, port)
26
- ftp.login(user, password)
27
- puts "Sending #{@backup.path} to #{full_path}" if verbose?
28
- begin
29
- ftp.put(@backup.path, full_path)
30
- rescue Net::FTPPermError
31
- puts "Ensuring remote path (#{path}) exists" if verbose?
32
- end
22
+ return if dry_run? || local_only?
23
+
24
+ port ||= 21
25
+ Net::FTP.open(host) do |ftp|
26
+ ftp.connect(host, port)
27
+ ftp.login(user, password)
28
+ puts "Sending #{@backup.path} to #{full_path}" if verbose?
29
+ begin
30
+ ftp.put(@backup.path, full_path)
31
+ rescue Net::FTPPermError
32
+ puts "Ensuring remote path (#{path}) exists" if verbose?
33
33
  end
34
- puts '...done' if verbose?
35
34
  end
35
+ puts '...done' if verbose?
36
36
  end
37
37
 
38
38
  def cleanup
@@ -41,20 +41,18 @@ module WebTranslateIt
41
41
  return unless keep = config[:keep, :ftp]
42
42
 
43
43
  puts "listing files: #{host}:#{base}*" if verbose?
44
- if !port
45
- port = 21
46
- end
44
+ port ||= 21
47
45
  Net::FTP.open(host) do |ftp|
48
46
  ftp.connect(host, port)
49
47
  ftp.login(user, password)
50
48
  files = ftp.nlst(path)
51
- pattern = File.basename("#{base}")
52
- files = files.reject{ |x| !x.start_with?(pattern)}
53
- puts files.collect {|x| x} if verbose?
49
+ pattern = File.basename(base.to_s)
50
+ files = files.select { |x| x.start_with?(pattern) }
51
+ puts files.collect { |x| x } if verbose?
54
52
 
55
- files = files.
56
- collect {|x| x }.
57
- sort
53
+ files = files
54
+ .collect { |x| x }
55
+ .sort
58
56
 
59
57
  cleanup_with_limit(files, keep) do |f|
60
58
  file = File.join(path, f)
@@ -81,5 +79,7 @@ module WebTranslateIt
81
79
  end
82
80
 
83
81
  end
82
+
84
83
  end
85
- end
84
+
85
+ end
@@ -1,9 +1,11 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Gpg < Pipe
4
6
 
5
7
  def active?
6
- raise RuntimeError, "can't use both gpg password and pubkey" if key && password
8
+ raise "can't use both gpg password and pubkey" if key && password
7
9
 
8
10
  !!(password || key)
9
11
  end
@@ -39,8 +41,12 @@ module WebTranslateIt
39
41
 
40
42
  def gpg_password_file(pass)
41
43
  return 'TEMP_GENERATED_FILENAME' if dry_run?
44
+
42
45
  WebTranslateIt::Safe::TmpFile.create('gpg-pass') { |file| file.write(pass) }
43
46
  end
47
+
44
48
  end
49
+
45
50
  end
46
- end
51
+
52
+ end
@@ -1,5 +1,7 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Gzip < Pipe
4
6
 
5
7
  protected
@@ -21,5 +23,7 @@ module WebTranslateIt
21
23
  end
22
24
 
23
25
  end
26
+
24
27
  end
25
- end
28
+
29
+ end
@@ -1,5 +1,7 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Local < Sink
4
6
 
5
7
  def active?
@@ -11,7 +13,7 @@ module WebTranslateIt
11
13
  protected
12
14
 
13
15
  def path
14
- @path ||= File.expand_path(expand(config[:local, :path] || raise(RuntimeError, 'missing :local/:path')))
16
+ @path ||= File.expand_path(expand(config[:local, :path] || raise('missing :local/:path')))
15
17
  end
16
18
 
17
19
  def save
@@ -20,14 +22,13 @@ module WebTranslateIt
20
22
  # FIXME: probably need to change this to smth like @backup.finalize!
21
23
  @backup.path = full_path # need to do it outside DRY_RUN so that it will be avialable for S3 DRY_RUN
22
24
 
23
- unless dry_run?
24
- FileUtils.mkdir_p(path) unless File.directory?(path)
25
- benchmark = Benchmark.realtime do
26
- system "#{@backup.command}>#{@backup.path}"
27
- end
28
- puts('command took ' + sprintf('%.2f', benchmark) + ' second(s).') if verbose?
29
- end
25
+ return if dry_run?
30
26
 
27
+ FileUtils.mkdir_p(path) unless File.directory?(path)
28
+ benchmark = Benchmark.realtime do
29
+ system "#{@backup.command}>#{@backup.path}"
30
+ end
31
+ puts("command took #{format('%.2f', benchmark)} second(s).") if verbose?
31
32
  end
32
33
 
33
34
  def cleanup
@@ -37,15 +38,18 @@ module WebTranslateIt
37
38
 
38
39
  # TODO: cleanup ALL zero-length files
39
40
 
40
- files = Dir["#{base}*"] .
41
- select{|f| File.file?(f) && File.size(f) > 0} .
42
- sort
41
+ files = Dir["#{base}*"]
42
+ .select { |f| File.file?(f) && File.size(f).positive? }
43
+ .sort
43
44
 
44
45
  cleanup_with_limit(files, keep) do |f|
45
46
  puts "removing local file #{f}" if dry_run? || verbose?
46
47
  File.unlink(f) unless dry_run?
47
48
  end
48
49
  end
50
+
49
51
  end
52
+
50
53
  end
54
+
51
55
  end
@@ -1,23 +1,29 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Mongodump < Source
4
-
6
+
5
7
  def command
6
8
  opts = []
7
9
  opts << "--host #{config[:host]}" if config[:host]
8
10
  opts << "-u #{config[:user]}" if config[:user]
9
11
  opts << "-p #{config[:password]}" if config[:password]
10
12
  opts << "--out #{output_directory}"
11
-
12
- "mongodump -q \"{xxxx : { \\$ne : 0 } }\" --db #{@id} #{opts.join(" ")} && cd #{output_directory} && tar cf - ."
13
+
14
+ "mongodump -q \"{xxxx : { \\$ne : 0 } }\" --db #{@id} #{opts.join(' ')} && cd #{output_directory} && tar cf - ."
13
15
  end
14
-
15
- def extension; '.tar'; end
16
-
16
+
17
+ def extension = '.tar'
18
+
17
19
  protected
20
+
18
21
  def output_directory
19
22
  File.join(TmpFile.tmproot, 'mongodump')
20
23
  end
24
+
21
25
  end
26
+
22
27
  end
28
+
23
29
  end