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 +4 -4
- data/CHANGELOG +17 -9
- data/bin/webtranslateit-safe +13 -13
- data/lib/webtranslateit/safe/archive.rb +9 -4
- data/lib/webtranslateit/safe/backup.rb +9 -2
- data/lib/webtranslateit/safe/cloudfiles.rb +21 -15
- data/lib/webtranslateit/safe/config/builder.rb +16 -6
- data/lib/webtranslateit/safe/config/node.rb +17 -10
- data/lib/webtranslateit/safe/ftp.rb +25 -25
- data/lib/webtranslateit/safe/gpg.rb +8 -2
- data/lib/webtranslateit/safe/gzip.rb +5 -1
- data/lib/webtranslateit/safe/local.rb +15 -11
- data/lib/webtranslateit/safe/mongodump.rb +12 -6
- data/lib/webtranslateit/safe/mysqldump.rb +9 -5
- data/lib/webtranslateit/safe/pgdump.rb +9 -9
- data/lib/webtranslateit/safe/pipe.rb +6 -0
- data/lib/webtranslateit/safe/s3.rb +28 -22
- data/lib/webtranslateit/safe/sftp.rb +42 -25
- data/lib/webtranslateit/safe/sink.rb +7 -2
- data/lib/webtranslateit/safe/source.rb +13 -9
- data/lib/webtranslateit/safe/stream.rb +14 -6
- data/lib/webtranslateit/safe/svndump.rb +5 -1
- data/lib/webtranslateit/safe/tmp_file.rb +16 -11
- data/lib/webtranslateit/safe.rb +12 -11
- metadata +2 -3
- data/lib/extensions/mktmpdir.rb +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d56d9c1814184138e0ac2f4c7c1c62d977db103d6aecebd441ae6c861a4f85e
|
4
|
+
data.tar.gz: 818c66a0f9307e18add81ef25fd29822a77ae1980cbbbdeafbcfdd5684ae65b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10a974ca460fdfd6ccf52f19a55466b70f74ea2f7e929084935453cf04bc67f0c9618c44f7b9d70bca2fd23f7af84ee54e77d2161a0a7219df3800c73e466e77
|
7
|
+
data.tar.gz: 6975cc66f99b3220b69556a20e5646c51ace16c7544380a1459f61cd9ce2781b8f5923dd942088676390cb50701daed4b964898562bf001edb4b9da590874ebd
|
data/CHANGELOG
CHANGED
@@ -1,36 +1,44 @@
|
|
1
|
-
0.4.
|
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
|
data/bin/webtranslateit-safe
CHANGED
@@ -12,16 +12,16 @@ def die(msg)
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def usage
|
15
|
-
puts
|
16
|
-
Usage: webtranslateit-safe [OPTIONS] CONFIG_FILE
|
17
|
-
Options:
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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')) && !
|
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
|
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
|
19
|
-
|
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
|
-
|
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
|
-
|
26
|
+
|
27
|
+
end
|
@@ -1,7 +1,10 @@
|
|
1
1
|
module WebTranslateIt
|
2
|
+
|
2
3
|
module Safe
|
4
|
+
|
3
5
|
class Cloudfiles < Sink
|
4
|
-
|
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
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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(:
|
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|
|
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
|
-
|
71
|
-
|
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
|
-
|
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
|
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
|
28
|
+
return value if !value.nil? && path.empty?
|
25
29
|
|
26
|
-
value
|
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
|
36
|
+
get(*path) || @parent&.find(*path)
|
33
37
|
end
|
34
|
-
alias
|
38
|
+
alias [] find
|
35
39
|
|
36
40
|
def set_multi(key, value)
|
37
41
|
@data[key.to_s] ||= []
|
38
|
-
@data[key.to_s].
|
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
|
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.
|
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
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
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(
|
52
|
-
files = files.
|
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
|
-
|
57
|
-
|
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
|
-
|
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
|
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
|
-
|
51
|
+
|
52
|
+
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(
|
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
|
-
|
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
|
-
|
42
|
-
|
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(
|
13
|
+
|
14
|
+
"mongodump -q \"{xxxx : { \\$ne : 0 } }\" --db #{@id} #{opts.join(' ')} && cd #{output_directory} && tar cf - ."
|
13
15
|
end
|
14
|
-
|
15
|
-
def extension
|
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
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
-
|
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
|
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[
|
21
|
+
config['host'] && "--host='#{config['host']}'"
|
24
22
|
end
|
25
23
|
|
26
24
|
def postgres_port
|
27
|
-
config['port'] && "--port='#{config[
|
25
|
+
config['port'] && "--port='#{config['port']}'"
|
28
26
|
end
|
29
27
|
|
30
28
|
def postgres_username
|
31
|
-
config['user'] && "--username='#{config[
|
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
|
-
|
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
|
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!(:
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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, :
|
47
|
-
puts files.collect
|
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
|
-
|
51
|
-
|
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, :
|
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
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
:
|
26
|
-
:
|
27
|
-
:
|
28
|
-
:
|
29
|
-
:
|
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
|
10
|
+
@config = config
|
11
|
+
@backup = backup
|
8
12
|
end
|
13
|
+
|
9
14
|
# FIXME: move to Backup
|
10
15
|
def expand(path)
|
11
|
-
path
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
39
|
+
|
40
|
+
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
|
-
|
16
|
-
|
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
|
-
|
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
|
data/lib/webtranslateit/safe.rb
CHANGED
@@ -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
|
-
|
49
|
-
|
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.
|
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-
|
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
|
data/lib/extensions/mktmpdir.rb
DELETED
@@ -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
|