webtranslateit-safe 0.4.3 → 0.4.4

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: 1d56d9c1814184138e0ac2f4c7c1c62d977db103d6aecebd441ae6c861a4f85e
4
+ data.tar.gz: 818c66a0f9307e18add81ef25fd29822a77ae1980cbbbdeafbcfdd5684ae65b7
5
5
  SHA512:
6
- metadata.gz: 1ad57b335c7585cea8621bbd6007fcd1f2d10e6fe7ba9fa0b1381ce00ce96eb38eb11925912185e06ab3ba2e69ebec165b2fa9a268c0cbeeb0b765e43b166b6f
7
- data.tar.gz: c51e5399dc02aa3fe4b0d0bfb99ed6397c9abdbc16ebf5dc8994d056151fcee9d340f0b8085d52f333d00b3eb8d1c0bbbd5b60c28afdde36b7065ed0a821492d
6
+ metadata.gz: 10a974ca460fdfd6ccf52f19a55466b70f74ea2f7e929084935453cf04bc67f0c9618c44f7b9d70bca2fd23f7af84ee54e77d2161a0a7219df3800c73e466e77
7
+ data.tar.gz: 6975cc66f99b3220b69556a20e5646c51ace16c7544380a1459f61cd9ce2781b8f5923dd942088676390cb50701daed4b964898562bf001edb4b9da590874ebd
data/CHANGELOG CHANGED
@@ -1,36 +1,44 @@
1
- 0.4.3
1
+ ## 0.4.4 - 2023-06-30
2
+
3
+ * Remove `default_executable` line and simplify executables definition.
4
+ * Autocorrect rubocop offences.
5
+ * Convert rspec syntax from `should` to `expect`.
6
+ * Remove unused extension `Dir.mktmpdir`.
7
+ * Upload retry logic for SFTP.
8
+
9
+ ## 0.4.3 - 2023-06-29
2
10
 
3
11
  * Bug fix on builder.
4
12
  * Move version number to gemspec file.
5
13
 
6
- 0.4.2
14
+ ## 0.4.2 - 2023-05-22
7
15
 
8
16
  * Bug fix on executable
9
17
 
10
- 0.4.1
18
+ ## 0.4.1 - 2023-05-20
11
19
 
12
20
  * Add ruby 3.2 compantibility
13
21
  * Modernize gem
14
22
  * Rename astrails-safe to webtranslateit-safe
15
23
 
16
- 0.3.1
24
+ ## 0.3.1
17
25
 
18
26
  * plain ftp support from seroy
19
27
  * mongodump support from Matt Berther
20
28
 
21
- 0.3.0
29
+ ## 0.3.0
22
30
 
23
31
  * switch to bundler
24
32
  * fixed the rspec
25
33
 
26
- 0.2.8
34
+ ## 0.2.8
27
35
 
28
36
  * ruby 1.9.2 compatibility (tests mostly)
29
37
  * code review, and tons of small fixes
30
38
  * check file size before attempting to upload to cloudfiles
31
39
  * testing framework changed from micronaut to rspec
32
40
 
33
- 0.2.7
41
+ ## 0.2.7
34
42
 
35
43
  * default options for gpg now include '--no-use-agent'
36
44
  * support for 'command' option for gpg
@@ -38,13 +46,13 @@
38
46
  * add 'lib' to $:
39
47
  * [EXPERIMENTAL] Rackspace Cloud Files support
40
48
 
41
- 0.2.6
49
+ ## 0.2.6
42
50
 
43
51
  * fix typo in the template config file. (change option to options in pgdump)
44
52
  * add example 'options' for tar in the template config file.
45
53
  * do not try to upload more then 5G of data to S3. print error instead
46
54
 
47
- 0.2.5
55
+ ## 0.2.5
48
56
 
49
57
  * Safety mesure: Disable overwrite of existing configuration keys except for multi-value keys
50
58
  supported multi-value keys: skip_tables, exclude, files
@@ -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
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
@@ -1,12 +1,14 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Mysqldump < Source
4
6
 
5
7
  def command
6
8
  "mysqldump --defaults-extra-file=#{mysql_password_file} #{config[:options]} #{mysql_skip_tables} #{@id}"
7
9
  end
8
10
 
9
- def extension; '.sql'; end
11
+ def extension = '.sql'
10
12
 
11
13
  protected
12
14
 
@@ -22,11 +24,13 @@ module WebTranslateIt
22
24
  end
23
25
 
24
26
  def mysql_skip_tables
25
- if skip_tables = config[:skip_tables]
26
- [*skip_tables].map{ |t| "--ignore-table=#{@id}.#{t}" }.join(' ')
27
- end
27
+ return unless skip_tables = config[:skip_tables]
28
+
29
+ [*skip_tables].map { |t| "--ignore-table=#{@id}.#{t}" }.join(' ')
28
30
  end
29
31
 
30
32
  end
33
+
31
34
  end
32
- end
35
+
36
+ end
@@ -1,17 +1,15 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Pgdump < Source
4
6
 
5
7
  def command
6
- if config['password']
7
- ENV['PGPASSWORD'] = config['password']
8
- else
9
- ENV['PGPASSWORD'] = nil
10
- end
8
+ ENV['PGPASSWORD'] = (config['password'] || nil)
11
9
  "pg_dump #{postgres_options} #{postgres_username} #{postgres_host} #{postgres_port} #{@id}"
12
10
  end
13
11
 
14
- def extension; '.sql'; end
12
+ def extension = '.sql'
15
13
 
16
14
  protected
17
15
 
@@ -20,17 +18,19 @@ module WebTranslateIt
20
18
  end
21
19
 
22
20
  def postgres_host
23
- config['host'] && "--host='#{config["host"]}'"
21
+ config['host'] && "--host='#{config['host']}'"
24
22
  end
25
23
 
26
24
  def postgres_port
27
- config['port'] && "--port='#{config["port"]}'"
25
+ config['port'] && "--port='#{config['port']}'"
28
26
  end
29
27
 
30
28
  def postgres_username
31
- config['user'] && "--username='#{config["user"]}'"
29
+ config['user'] && "--username='#{config['user']}'"
32
30
  end
33
31
 
34
32
  end
33
+
35
34
  end
35
+
36
36
  end
@@ -1,6 +1,9 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Pipe < Stream
6
+
4
7
  # process adds required commands to the current
5
8
  # shell command string
6
9
  # :active?, :pipe, :extension and :post_process are
@@ -12,6 +15,9 @@ module WebTranslateIt
12
15
  @backup.extension << extension
13
16
  post_process
14
17
  end
18
+
15
19
  end
20
+
16
21
  end
22
+
17
23
  end
@@ -1,7 +1,10 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class S3 < Sink
4
- MAX_S3_FILE_SIZE = 5368709120
6
+
7
+ MAX_S3_FILE_SIZE = 5_368_709_120
5
8
 
6
9
  def active?
7
10
  bucket && key && secret
@@ -15,26 +18,26 @@ module WebTranslateIt
15
18
 
16
19
  def save
17
20
  # FIXME: user friendly error here :)
18
- raise RuntimeError, 'pipe-streaming not supported for S3.' unless @backup.path
21
+ raise 'pipe-streaming not supported for S3.' unless @backup.path
19
22
 
20
23
  # needed in cleanup even on dry run
21
- AWS::S3::Base.establish_connection!(:access_key_id => key, :secret_access_key => secret, :use_ssl => true) unless local_only?
24
+ AWS::S3::Base.establish_connection!(access_key_id: key, secret_access_key: secret, use_ssl: true) unless local_only?
22
25
 
23
26
  puts "Uploading #{bucket}:#{full_path}" if verbose? || dry_run?
24
- unless dry_run? || local_only?
25
- if File.stat(@backup.path).size > MAX_S3_FILE_SIZE
26
- STDERR.puts "ERROR: File size exceeds maximum allowed for upload to S3 (#{MAX_S3_FILE_SIZE}): #{@backup.path}"
27
- return
28
- end
29
- benchmark = Benchmark.realtime do
30
- AWS::S3::Bucket.create(bucket) unless bucket_exists?(bucket)
31
- File.open(@backup.path) do |file|
32
- AWS::S3::S3Object.store(full_path, file, bucket)
33
- end
27
+ return if dry_run? || local_only?
28
+
29
+ if File.stat(@backup.path).size > MAX_S3_FILE_SIZE
30
+ warn "ERROR: File size exceeds maximum allowed for upload to S3 (#{MAX_S3_FILE_SIZE}): #{@backup.path}"
31
+ return
32
+ end
33
+ benchmark = Benchmark.realtime do
34
+ AWS::S3::Bucket.create(bucket) unless bucket_exists?(bucket)
35
+ File.open(@backup.path) do |file|
36
+ AWS::S3::S3Object.store(full_path, file, bucket)
34
37
  end
35
- puts '...done' if verbose?
36
- puts('Upload took ' + sprintf('%.2f', benchmark) + ' second(s).') if verbose?
37
38
  end
39
+ puts '...done' if verbose?
40
+ puts("Upload took #{format('%.2f', benchmark)} second(s).") if verbose?
38
41
  end
39
42
 
40
43
  def cleanup
@@ -43,16 +46,16 @@ module WebTranslateIt
43
46
  return unless keep = config[:keep, :s3]
44
47
 
45
48
  puts "listing files: #{bucket}:#{base}*" if verbose?
46
- files = AWS::S3::Bucket.objects(bucket, :prefix => base, :max_keys => keep * 2)
47
- puts files.collect {|x| x.key} if verbose?
49
+ files = AWS::S3::Bucket.objects(bucket, prefix: base, max_keys: keep * 2)
50
+ puts files.collect(&:key) if verbose?
48
51
 
49
- files = files.
50
- collect {|x| x.key}.
51
- sort
52
+ files = files
53
+ .collect(&:key)
54
+ .sort
52
55
 
53
56
  cleanup_with_limit(files, keep) do |f|
54
57
  puts "removing s3 file #{bucket}:#{f}" if dry_run? || verbose?
55
- AWS::S3::Bucket.objects(bucket, :prefix => f)[0].delete unless dry_run? || local_only?
58
+ AWS::S3::Bucket.objects(bucket, prefix: f)[0].delete unless dry_run? || local_only?
56
59
  end
57
60
  end
58
61
 
@@ -69,12 +72,15 @@ module WebTranslateIt
69
72
  end
70
73
 
71
74
  private
72
-
75
+
73
76
  def bucket_exists?(bucket)
74
77
  true if AWS::S3::Bucket.find(bucket)
75
78
  rescue AWS::S3::NoSuchBucket
76
79
  false
77
80
  end
81
+
78
82
  end
83
+
79
84
  end
85
+
80
86
  end
@@ -1,7 +1,11 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Sftp < Sink
4
6
 
7
+ MAX_RETRIES = 5
8
+
5
9
  protected
6
10
 
7
11
  def active?
@@ -12,33 +16,46 @@ module WebTranslateIt
12
16
  @path ||= expand(config[:sftp, :path] || config[:local, :path] || ':kind/:id')
13
17
  end
14
18
 
15
- def save
16
- raise RuntimeError, 'pipe-streaming not supported for SFTP.' unless @backup.path
19
+ def save # rubocop:todo Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
20
+ raise 'pipe-streaming not supported for SFTP.' unless @backup.path
17
21
 
18
22
  puts "Uploading #{host}:#{full_path} via SFTP" if verbose? || dry_run?
19
23
 
20
- unless dry_run? || local_only?
21
- opts = {}
22
- opts[:password] = password if password
23
- opts[:port] = port if port
24
- Net::SFTP.start(host, user, opts) do |sftp|
25
- puts "Sending #{@backup.path} to #{full_path}" if verbose?
26
- begin
27
- sftp.upload! @backup.path, full_path
28
- rescue Net::SFTP::StatusException
29
- puts "Ensuring remote path (#{path}) exists" if verbose?
30
- # mkdir -p
31
- folders = path.split('/')
32
- folders.each_index do |i|
33
- folder = folders[0..i].join('/')
34
- puts "Creating #{folder} on remote" if verbose?
35
- sftp.mkdir!(folder) rescue Net::SFTP::StatusException
24
+ return if dry_run? || local_only?
25
+
26
+ retries = 0
27
+ opts = {}
28
+ opts[:password] = password if password
29
+ opts[:port] = port if port
30
+ Net::SFTP.start(host, user, opts) do |sftp|
31
+ puts "Sending #{@backup.path} to #{full_path}" if verbose?
32
+ begin
33
+ sftp.upload! @backup.path, full_path
34
+ rescue IO::TimeoutError
35
+ puts 'Upload timed out, retrying'
36
+ retries += 1
37
+ if retries >= MAX_RETRIES
38
+ puts "Tried #{retries} times. Giving up."
39
+ else
40
+ retry unless retries >= MAX_RETRIES
41
+ end
42
+ rescue Net::SFTP::StatusException
43
+ puts "Ensuring remote path (#{path}) exists" if verbose?
44
+ # mkdir -p
45
+ folders = path.split('/')
46
+ folders.each_index do |i|
47
+ folder = folders[0..i].join('/')
48
+ puts "Creating #{folder} on remote" if verbose?
49
+ begin
50
+ sftp.mkdir!(folder)
51
+ rescue StandardError
52
+ Net::SFTP::StatusException
36
53
  end
37
- retry
38
54
  end
55
+ retry
39
56
  end
40
- puts '...done' if verbose?
41
57
  end
58
+ puts '...done' if verbose?
42
59
  end
43
60
 
44
61
  def cleanup
@@ -53,11 +70,9 @@ module WebTranslateIt
53
70
  Net::SFTP.start(host, user, opts) do |sftp|
54
71
  files = sftp.dir.glob(path, File.basename("#{base}*"))
55
72
 
56
- puts files.collect {|x| x.name } if verbose?
73
+ puts files.collect(&:name) if verbose?
57
74
 
58
- files = files.
59
- collect {|x| x.name }.
60
- sort
75
+ files = files.collect(&:name).sort
61
76
 
62
77
  cleanup_with_limit(files, keep) do |f|
63
78
  file = File.join(path, f)
@@ -84,5 +99,7 @@ module WebTranslateIt
84
99
  end
85
100
 
86
101
  end
102
+
87
103
  end
88
- end
104
+
105
+ end
@@ -1,5 +1,7 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Sink < Stream
4
6
 
5
7
  def process
@@ -15,7 +17,7 @@ module WebTranslateIt
15
17
  # base is used in 'cleanup' to find all files that begin with base. the '.'
16
18
  # at the end is essential to distinguish b/w foo.* and foobar.* archives for example
17
19
  def base
18
- @base ||= File.join(path, File.basename(@backup.filename).split('.').first + '.')
20
+ @base ||= File.join(path, "#{File.basename(@backup.filename).split('.').first}.")
19
21
  end
20
22
 
21
23
  def full_path
@@ -30,6 +32,9 @@ module WebTranslateIt
30
32
  # TODO: validate here
31
33
  to_remove.each(&block)
32
34
  end
35
+
33
36
  end
37
+
34
38
  end
35
- end
39
+
40
+ end
@@ -1,10 +1,14 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Source < Stream
4
6
 
5
7
  attr_accessor :id
8
+
6
9
  def initialize(id, config)
7
- @id, @config = id.to_s, config
10
+ @id = id.to_s
11
+ @config = config
8
12
  end
9
13
 
10
14
  def timestamp
@@ -21,12 +25,13 @@ module WebTranslateIt
21
25
 
22
26
  def backup
23
27
  return @backup if @backup
28
+
24
29
  @backup = Backup.new(
25
- :id => @id,
26
- :kind => kind,
27
- :extension => extension,
28
- :command => command,
29
- :timestamp => timestamp
30
+ id: @id,
31
+ kind: kind,
32
+ extension: extension,
33
+ command: command,
34
+ timestamp: timestamp
30
35
  )
31
36
  # can't do this in the initializer hash above since
32
37
  # filename() calls expand() which requires @backup
@@ -35,13 +40,12 @@ module WebTranslateIt
35
40
  @backup
36
41
  end
37
42
 
38
- protected
39
-
40
43
  def self.human_name
41
44
  name.split('::').last.downcase
42
45
  end
43
46
 
44
47
  end
48
+
45
49
  end
46
- end
47
50
 
51
+ end
@@ -1,17 +1,22 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Stream
4
6
 
5
7
  attr_accessor :config, :backup
8
+
6
9
  def initialize(config, backup)
7
- @config, @backup = config, backup
10
+ @config = config
11
+ @backup = backup
8
12
  end
13
+
9
14
  # FIXME: move to Backup
10
15
  def expand(path)
11
- path .
12
- gsub(/:kind\b/, @backup.kind.to_s) .
13
- gsub(/:id\b/, @backup.id.to_s) .
14
- gsub(/:timestamp\b/, @backup.timestamp)
16
+ path
17
+ .gsub(/:kind\b/, @backup.kind.to_s)
18
+ .gsub(/:id\b/, @backup.id.to_s)
19
+ .gsub(/:timestamp\b/, @backup.timestamp)
15
20
  end
16
21
 
17
22
  private
@@ -27,6 +32,9 @@ module WebTranslateIt
27
32
  def dry_run?
28
33
  config[:dry_run]
29
34
  end
35
+
30
36
  end
37
+
31
38
  end
32
- end
39
+
40
+ end
@@ -1,13 +1,17 @@
1
1
  module WebTranslateIt
2
+
2
3
  module Safe
4
+
3
5
  class Svndump < Source
4
6
 
5
7
  def command
6
8
  "svnadmin dump #{config[:options]} #{config[:repo_path]}"
7
9
  end
8
10
 
9
- def extension; '.svn'; end
11
+ def extension = '.svn'
10
12
 
11
13
  end
14
+
12
15
  end
16
+
13
17
  end
@@ -1,7 +1,10 @@
1
1
  require 'tmpdir'
2
2
  module WebTranslateIt
3
+
3
4
  module Safe
5
+
4
6
  module TmpFile
7
+
5
8
  @keep_files = []
6
9
 
7
10
  def self.tmproot
@@ -12,22 +15,21 @@ module WebTranslateIt
12
15
  begin
13
16
  FileUtils.remove_entry_secure tmproot
14
17
  rescue ArgumentError => e
15
- if e.message =~ /parent directory is world writable/
16
- puts <<-ERR
18
+ raise unless e.message.include?('parent directory is world writable')
19
+
20
+ puts <<~ERR
17
21
 
18
22
 
19
- ********************************************************************************
20
- It looks like you have wrong permissions on your TEMP directory. The usual
21
- case is when you have world writable TEMP directory withOUT the sticky bit.
23
+ ********************************************************************************
24
+ It looks like you have wrong permissions on your TEMP directory. The usual
25
+ case is when you have world writable TEMP directory withOUT the sticky bit.
22
26
 
23
- Try "chmod +t" on it.
27
+ Try "chmod +t" on it.
24
28
 
25
- ********************************************************************************
29
+ ********************************************************************************
26
30
 
27
- ERR
28
- else
29
- raise
30
- end
31
+ ERR
32
+ raise
31
33
  end
32
34
  @tmproot = nil
33
35
  end
@@ -43,6 +45,9 @@ ERR
43
45
  @keep_files << file # so that it will not get gcollected and removed from filesystem until the end
44
46
  file.path
45
47
  end
48
+
46
49
  end
50
+
47
51
  end
52
+
48
53
  end
@@ -6,7 +6,6 @@ require 'fileutils'
6
6
  require 'benchmark'
7
7
 
8
8
  require 'tempfile'
9
- require 'extensions/mktmpdir'
10
9
 
11
10
  require 'webtranslateit/safe/tmp_file'
12
11
 
@@ -36,7 +35,9 @@ require 'webtranslateit/safe/sftp'
36
35
  require 'webtranslateit/safe/ftp'
37
36
 
38
37
  module WebTranslateIt
38
+
39
39
  module Safe
40
+
40
41
  ROOT = File.join(File.dirname(__FILE__), '..', '..')
41
42
 
42
43
  def safe(&block)
@@ -44,17 +45,15 @@ module WebTranslateIt
44
45
  end
45
46
 
46
47
  def process(config)
48
+ [[Mysqldump, %i[mysqldump databases]],
49
+ [Pgdump, %i[pgdump databases]],
50
+ [Mongodump, %i[mongodump databases]],
51
+ [Archive, %i[tar archives]],
52
+ [Svndump, %i[svndump repos]]].each do |klass, path|
53
+ next unless collection = config[*path]
47
54
 
48
- [[Mysqldump, [:mysqldump, :databases]],
49
- [Pgdump, [:pgdump, :databases]],
50
- [Mongodump, [:mongodump, :databases]],
51
- [Archive, [:tar, :archives]],
52
- [Svndump, [:svndump, :repos]]
53
- ].each do |klass, path|
54
- if collection = config[*path]
55
- collection.each do |name, c|
56
- klass.new(name, c).backup.run(c, :gpg, :gzip, :local, :s3, :cloudfiles, :sftp, :ftp)
57
- end
55
+ collection.each do |name, c|
56
+ klass.new(name, c).backup.run(c, :gpg, :gzip, :local, :s3, :cloudfiles, :sftp, :ftp)
58
57
  end
59
58
  end
60
59
 
@@ -62,5 +61,7 @@ module WebTranslateIt
62
61
  end
63
62
  module_function :safe
64
63
  module_function :process
64
+
65
65
  end
66
+
66
67
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webtranslateit-safe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Edouard Briere
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-06-29 00:00:00.000000000 Z
12
+ date: 2023-06-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: aws-s3
@@ -67,7 +67,6 @@ files:
67
67
  - CHANGELOG
68
68
  - README.markdown
69
69
  - bin/webtranslateit-safe
70
- - lib/extensions/mktmpdir.rb
71
70
  - lib/webtranslateit/safe.rb
72
71
  - lib/webtranslateit/safe/archive.rb
73
72
  - lib/webtranslateit/safe/backup.rb
@@ -1,45 +0,0 @@
1
- require 'tmpdir'
2
-
3
- unless Dir.respond_to?(:mktmpdir)
4
- # backward compat for 1.8.6
5
- class Dir
6
- def Dir.mktmpdir(prefix_suffix=nil, tmpdir=nil)
7
- case prefix_suffix
8
- when nil
9
- prefix = 'd'
10
- suffix = ''
11
- when String
12
- prefix = prefix_suffix
13
- suffix = ''
14
- when Array
15
- prefix = prefix_suffix[0]
16
- suffix = prefix_suffix[1]
17
- else
18
- raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
19
- end
20
- tmpdir ||= Dir.tmpdir
21
- t = Time.now.strftime('%Y%m%d')
22
- n = nil
23
- begin
24
- path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
25
- path << "-#{n}" if n
26
- path << suffix
27
- Dir.mkdir(path, 0700)
28
- rescue Errno::EEXIST
29
- n ||= 0
30
- n += 1
31
- retry
32
- end
33
-
34
- if block_given?
35
- begin
36
- yield path
37
- ensure
38
- FileUtils.remove_entry_secure path
39
- end
40
- else
41
- path
42
- end
43
- end
44
- end
45
- end