zfs_mgmt 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/bin/zfsmgr +15 -0
- data/lib/zfs_mgmt.rb +349 -77
- data/lib/zfs_mgmt/restic.rb +21 -9
- data/lib/zfs_mgmt/version.rb +1 -1
- data/lib/zfs_mgmt/zfs_mgr.rb +1 -0
- data/lib/zfs_mgmt/zfs_mgr/list.rb +37 -4
- data/lib/zfs_mgmt/zfs_mgr/restic.rb +12 -2
- data/lib/zfs_mgmt/zfs_mgr/send.rb +63 -0
- data/lib/zfs_mgmt/zfs_mgr/snapshot.rb +17 -9
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 96ec8b7a9e8ba4111f9a8e5d58d347de8a62d00c3d5a681f427abcb21e3560a5
|
|
4
|
+
data.tar.gz: 2b11bd3aaa99cc664a3a4308ad33e40a640db631c86490ebb2e571718aa5e5c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0dd229c996513d93439e82e08cfeeefc712202c3af28e72df3256c0a58254f8c525c47e4b1457dc0c50a8d3f3a871cebe9e3c5d9a34974c03adec921e8ed870c
|
|
7
|
+
data.tar.gz: 1a659593c4efa623cb41a8d1586e5d3f34516f960527a1829635b26baad33fcfe8a988585d0be78749692b4b5d6c05cb23dbc4c30be160de8a1c303ed975c13c
|
data/Gemfile.lock
CHANGED
data/bin/zfsmgr
CHANGED
|
@@ -5,10 +5,23 @@ require "zfs_mgmt/zfs_mgr"
|
|
|
5
5
|
require "zfs_mgmt/zfs_mgr/restic"
|
|
6
6
|
|
|
7
7
|
class ZfsMgr < Thor
|
|
8
|
+
class_option :zfs_binary, :type => :string, :default => 'zfs',
|
|
9
|
+
:desc => 'zfs binary'
|
|
10
|
+
class_option :zpool_binary, :type => :string, :default => 'zpool',
|
|
11
|
+
:desc => 'zpool binary'
|
|
12
|
+
class_option :mbuffer_binary, :type => :string, :default => 'mbuffer',
|
|
13
|
+
:desc => 'mbuffer binary'
|
|
14
|
+
class_option :pv_binary, :type => :string, :default => 'pv',
|
|
15
|
+
:desc => 'pv binary'
|
|
16
|
+
class_option :loglevel, :type => :string, :default => ( $stdout.isatty ? 'info' : 'warn' ),
|
|
17
|
+
:enum => ['debug','error','fatal','info','warn'],
|
|
18
|
+
:desc => 'set logging level to specified severity'
|
|
8
19
|
desc "zfsget [ZFS]", "execute zfs get for the given properties and types and parse the output into a nested hash"
|
|
9
20
|
method_option :properties, :type => :array, :default => ['name'], :desc => "List of properties passed to zfs get"
|
|
10
21
|
method_option :types, :type => :array, :default => ['filesystem','volume'], enum: ['filesystem','volume','snapshot'], :desc => "list of types"
|
|
11
22
|
def zfsget(zfs)
|
|
23
|
+
ZfsMgmt.set_log_level(options[:loglevel])
|
|
24
|
+
ZfsMgmt.global_options = options
|
|
12
25
|
pp ZfsMgmt.zfsget(properties: options[:properties],
|
|
13
26
|
types: options[:types],
|
|
14
27
|
zfs: zfs)
|
|
@@ -19,6 +32,8 @@ class ZfsMgr < Thor
|
|
|
19
32
|
subcommand "list", ZfsMgmt::ZfsMgr::List
|
|
20
33
|
desc "restic SUBCOMMAND ...ARGS", "backup zfs to restic"
|
|
21
34
|
subcommand "restic", ZfsMgmt::ZfsMgr::Restic
|
|
35
|
+
desc "send SUBCOMMAND ...ARGS", "send zfs"
|
|
36
|
+
subcommand "send", ZfsMgmt::ZfsMgr::Send
|
|
22
37
|
end
|
|
23
38
|
|
|
24
39
|
ZfsMgr.start(ARGV)
|
data/lib/zfs_mgmt.rb
CHANGED
|
@@ -9,7 +9,7 @@ require 'text-table'
|
|
|
9
9
|
require 'open3'
|
|
10
10
|
require 'filesize'
|
|
11
11
|
|
|
12
|
-
$logger = Logger.new(STDERR)
|
|
12
|
+
$logger = Logger.new(STDERR, progname: 'zfs_mgmt')
|
|
13
13
|
|
|
14
14
|
$date_patterns = {
|
|
15
15
|
'hourly' => '%F Hour %H',
|
|
@@ -38,6 +38,11 @@ $properties_xlate = {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
module ZfsMgmt
|
|
41
|
+
class << self
|
|
42
|
+
attr_accessor :global_options
|
|
43
|
+
end
|
|
44
|
+
class ZfsGetError < StandardError
|
|
45
|
+
end
|
|
41
46
|
def self.custom_properties()
|
|
42
47
|
return [
|
|
43
48
|
'policy',
|
|
@@ -50,6 +55,9 @@ module ZfsMgmt
|
|
|
50
55
|
'snapshot',
|
|
51
56
|
'snap_prefix',
|
|
52
57
|
'snap_timestamp',
|
|
58
|
+
'send',
|
|
59
|
+
'remote',
|
|
60
|
+
'destination',
|
|
53
61
|
].map do |p|
|
|
54
62
|
['zfsmgmt',p].join(':')
|
|
55
63
|
end
|
|
@@ -67,7 +75,7 @@ module ZfsMgmt
|
|
|
67
75
|
end
|
|
68
76
|
|
|
69
77
|
def self.zfs_holds(snapshot)
|
|
70
|
-
com = ['
|
|
78
|
+
com = [global_options['zfs_binary'], 'holds', '-H', snapshot]
|
|
71
79
|
$logger.debug("#{com.join(' ')}")
|
|
72
80
|
out = %x(#{com.join(' ')})
|
|
73
81
|
unless $?.success?
|
|
@@ -83,9 +91,8 @@ module ZfsMgmt
|
|
|
83
91
|
end
|
|
84
92
|
|
|
85
93
|
def self.zfs_hold(hold,snapshot)
|
|
86
|
-
com = ['
|
|
87
|
-
|
|
88
|
-
system(com.join(' '))
|
|
94
|
+
com = [global_options['zfs_binary'], 'hold', hold, snapshot]
|
|
95
|
+
system_com(com)
|
|
89
96
|
unless $?.success?
|
|
90
97
|
errstr = "unable to set hold: #{hold} for snapshot: #{snapshot}"
|
|
91
98
|
$logger.error(errstr)
|
|
@@ -94,9 +101,8 @@ module ZfsMgmt
|
|
|
94
101
|
end
|
|
95
102
|
|
|
96
103
|
def self.zfs_release(hold,snapshot)
|
|
97
|
-
com = ['
|
|
98
|
-
|
|
99
|
-
system(com.join(' '))
|
|
104
|
+
com = [@global_options['zfs_binary'], 'release', hold, snapshot]
|
|
105
|
+
system_com(com)
|
|
100
106
|
unless $?.success?
|
|
101
107
|
errstr = "unable to release hold: #{hold} for snapshot: #{snapshot}"
|
|
102
108
|
$logger.error(errstr)
|
|
@@ -104,19 +110,20 @@ module ZfsMgmt
|
|
|
104
110
|
end
|
|
105
111
|
end
|
|
106
112
|
|
|
107
|
-
def self.zfsget(properties: ['
|
|
113
|
+
def self.zfsget(properties: ['all'],types: ['filesystem','volume'],zfs: '', command_prefix: [])
|
|
108
114
|
results={}
|
|
109
|
-
com = [
|
|
110
|
-
|
|
115
|
+
com = [ZfsMgmt.global_options[:zfs_binary], 'get', '-Hp', properties.join(','), '-t', types.join(','), zfs]
|
|
116
|
+
$logger.debug((command_prefix+com).join(' '))
|
|
117
|
+
so,se,status = Open3.capture3((command_prefix+com).join(' '))
|
|
111
118
|
if status.signaled?
|
|
112
119
|
$logger.error("process was signalled \"#{com.join(' ')}\", termsig #{status.termsig}")
|
|
113
|
-
raise
|
|
120
|
+
raise ZfsGetError, "process was signalled \"#{com.join(' ')}\", termsig #{status.termsig}"
|
|
114
121
|
end
|
|
115
122
|
unless status.success?
|
|
116
123
|
$logger.error("failed to execute \"#{com.join(' ')}\", exit status #{status.exitstatus}")
|
|
117
124
|
so.split("\n").each { |l| $logger.debug("stdout: #{l}") }
|
|
118
125
|
se.split("\n").each { |l| $logger.error("stderr: #{l}") }
|
|
119
|
-
raise
|
|
126
|
+
raise ZfsGetError, "failed to execute \"#{com.join(' ')}\", exit status #{status.exitstatus}"
|
|
120
127
|
end
|
|
121
128
|
so.split("\n").each do |line|
|
|
122
129
|
params = line.split("\t")
|
|
@@ -231,7 +238,7 @@ module ZfsMgmt
|
|
|
231
238
|
}
|
|
232
239
|
return saved,saved_snaps,deleteme
|
|
233
240
|
end
|
|
234
|
-
def self.zfs_managed_list(filter: '.+', properties:
|
|
241
|
+
def self.zfs_managed_list(filter: '.+', properties: ['all'], property_match: { 'zfsmgmt:manage' => 'true' } )
|
|
235
242
|
zfss = [] # array of arrays
|
|
236
243
|
zfsget(properties: properties).each do |zfs,props|
|
|
237
244
|
unless /#{filter}/ =~ zfs
|
|
@@ -254,20 +261,20 @@ module ZfsMgmt
|
|
|
254
261
|
end
|
|
255
262
|
return zfss
|
|
256
263
|
end
|
|
257
|
-
def self.snapshot_policy(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
$logger.level = Logger::INFO
|
|
262
|
-
end
|
|
263
|
-
zfs_managed_list(filter: filter).each do |zdata|
|
|
264
|
-
(zfs,props,snaps) = zdata
|
|
265
|
-
unless props.has_key?('zfsmgmt:policy') and policy_parser(props['zfsmgmt:policy'])
|
|
266
|
-
$logger.error("zfs_mgmt is configured to manage #{zfs}, but there is no valid policy configuration, skipping")
|
|
264
|
+
def self.snapshot_policy(filter: '.+')
|
|
265
|
+
zfs_managed_list(filter: filter).each do |zfs,props,snaps|
|
|
266
|
+
unless props.has_key?('zfsmgmt:policy')
|
|
267
|
+
$logger.error("zfs_mgmt is configured to manage #{zfs}, but there is no policy configuration in zfsmgmt:policy, skipping")
|
|
267
268
|
next # zfs
|
|
268
269
|
end
|
|
269
|
-
|
|
270
|
-
|
|
270
|
+
|
|
271
|
+
begin
|
|
272
|
+
# call the function that decides who to save and who to delete
|
|
273
|
+
(saved,saved_snaps,deleteme) = snapshot_destroy_policy(zfs,props,snaps)
|
|
274
|
+
rescue ArgumentError
|
|
275
|
+
$logger.error("zfs_mgmt is configured to manage #{zfs}, but there is no valid policy configuration, skipping")
|
|
276
|
+
next
|
|
277
|
+
end
|
|
271
278
|
|
|
272
279
|
if saved_snaps.length == 0
|
|
273
280
|
$logger.info("no snapshots marked as saved by policy for #{zfs}")
|
|
@@ -283,21 +290,20 @@ module ZfsMgmt
|
|
|
283
290
|
print table.to_s
|
|
284
291
|
end
|
|
285
292
|
end
|
|
286
|
-
def self.snapshot_destroy(noop: false,
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
$logger.level = Logger::INFO
|
|
291
|
-
end
|
|
292
|
-
zfs_managed_list(filter: filter).each do |zdata|
|
|
293
|
-
(zfs,props,snaps) = zdata
|
|
294
|
-
unless props.has_key?('zfsmgmt:policy') and policy_parser(props['zfsmgmt:policy'])
|
|
295
|
-
$logger.error("zfs_mgmt is configured to manage #{zfs}, but there is no valid policy configuration, skipping")
|
|
293
|
+
def self.snapshot_destroy(noop: false, verbose: false, filter: '.+')
|
|
294
|
+
zfs_managed_list(filter: filter).each do |zfs,props,snaps|
|
|
295
|
+
unless props.has_key?('zfsmgmt:policy')
|
|
296
|
+
$logger.error("zfs_mgmt is configured to manage #{zfs}, but there is no policy configuration in zfsmgmt:policy, skipping")
|
|
296
297
|
next # zfs
|
|
297
298
|
end
|
|
298
299
|
|
|
299
|
-
|
|
300
|
-
|
|
300
|
+
begin
|
|
301
|
+
# call the function that decides who to save and who to delete
|
|
302
|
+
(saved,saved_snaps,deleteme) = snapshot_destroy_policy(zfs,props,snaps)
|
|
303
|
+
rescue ArgumentError
|
|
304
|
+
$logger.error("zfs_mgmt is configured to manage #{zfs}, but there is no valid policy configuration, skipping")
|
|
305
|
+
next
|
|
306
|
+
end
|
|
301
307
|
|
|
302
308
|
$logger.info("deleting #{deleteme.length} snapshots for #{zfs}")
|
|
303
309
|
deleteme.reverse! # oldest first for removal
|
|
@@ -305,32 +311,22 @@ module ZfsMgmt
|
|
|
305
311
|
$logger.debug("delete: #{snap_name} #{local_epoch_to_datetime(snaps[snap_name]['creation']).strftime('%F %T')}")
|
|
306
312
|
end
|
|
307
313
|
|
|
308
|
-
com_base =
|
|
309
|
-
if deleteme.length > 0
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if noop
|
|
313
|
-
com_base = "#{com_base}n"
|
|
314
|
-
end
|
|
315
|
-
if verbopt
|
|
316
|
-
com_base = "#{com_base}v"
|
|
317
|
-
end
|
|
314
|
+
com_base = ['zfs', 'destroy']
|
|
315
|
+
com_base.push('-d') if deleteme.length > 0 # why?
|
|
316
|
+
com_base.push('-n') if noop
|
|
317
|
+
com_base.push('-v') if verbose
|
|
318
318
|
while deleteme.length > 0
|
|
319
319
|
for i in 0..(deleteme.length - 1) do
|
|
320
320
|
max = deleteme.length - 1 - i
|
|
321
321
|
$logger.debug("attempting to remove snaps 0 through #{max} out of #{deleteme.length} snapshots")
|
|
322
322
|
bigarg = "#{zfs}@#{deleteme[0..max].map { |s| s.split('@')[1] }.join(',')}"
|
|
323
|
-
com =
|
|
323
|
+
com = com_base + [bigarg]
|
|
324
324
|
$logger.debug("size of bigarg: #{bigarg.length} size of com: #{com.length}")
|
|
325
325
|
if bigarg.length >= 131072 or com.length >= (2097152-10000)
|
|
326
326
|
next
|
|
327
327
|
end
|
|
328
|
-
$logger.info(com)
|
|
329
328
|
deleteme = deleteme - deleteme[0..max]
|
|
330
|
-
|
|
331
|
-
if $?.exitstatus != 0
|
|
332
|
-
$logger.error("zfs exited with non-zero status: #{$?.exitstatus}")
|
|
333
|
-
end
|
|
329
|
+
system_com(com) # pass -n, always run the command though
|
|
334
330
|
break
|
|
335
331
|
end
|
|
336
332
|
end
|
|
@@ -344,7 +340,7 @@ module ZfsMgmt
|
|
|
344
340
|
end
|
|
345
341
|
p = str.scan(/\d+[#{$time_pattern_map.keys.join('')}]/i)
|
|
346
342
|
unless p.length > 0
|
|
347
|
-
raise "unable to parse the policy configuration #{str}"
|
|
343
|
+
raise ArgumentError.new("unable to parse the policy configuration #{str}")
|
|
348
344
|
end
|
|
349
345
|
p.each do |pi|
|
|
350
346
|
scn = /(\d+)([#{$time_pattern_map.keys.join('')}])/i.match(pi)
|
|
@@ -352,33 +348,309 @@ module ZfsMgmt
|
|
|
352
348
|
end
|
|
353
349
|
res
|
|
354
350
|
end
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
351
|
+
# snapshot all filesystems configured for snapshotting
|
|
352
|
+
def self.snapshot_create(noop: false, verbose: false, filter: '.+')
|
|
353
|
+
dt = DateTime.now
|
|
354
|
+
zfsget.select { |zfs,props|
|
|
355
|
+
# must match filter
|
|
356
|
+
match_filter?(zfs: zfs, filter: filter) and
|
|
357
|
+
# snapshot must be on or true
|
|
358
|
+
(
|
|
359
|
+
key_comp?(props,'zfsmgmt:snapshot') or
|
|
360
|
+
# or snapshot can be recursive and local, but only if the source is local or received
|
|
361
|
+
( key_comp?(props,'zfsmgmt:snapshot',['recursive','local']) and key_comp?(props,'zfsmgmt:snapshot@source',['local','received']) )
|
|
362
|
+
)
|
|
363
|
+
}.each do |zfs,props|
|
|
364
|
+
prefix = ( props.has_key?('zfsmgmt:snap_prefix') ? props['zfsmgmt:snap_prefix'] : 'zfsmgmt' )
|
|
365
|
+
ts = ( props.has_key?('zfsmgmt:snap_timestamp') ? props['zfsmgmt:snap_timestamp'] : '%FT%T%z' )
|
|
366
|
+
com = [global_options['zfs_binary'],'snapshot']
|
|
367
|
+
if key_comp?(props,'zfsmgmt:snapshot','recursive') and key_comp?(props,'zfsmgmt:snapshot@source',['local','received'])
|
|
368
|
+
com.push('-r')
|
|
369
|
+
end
|
|
370
|
+
com.push('-v') if verbose
|
|
371
|
+
com.push("#{zfs}@#{[prefix,dt.strftime(ts)].join('-')}")
|
|
372
|
+
system_com(com,noop)
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
def self.system_com(com, noop = false)
|
|
376
|
+
comstr = com.join(' ')
|
|
377
|
+
$logger.info(comstr)
|
|
378
|
+
unless noop
|
|
379
|
+
system(comstr)
|
|
380
|
+
unless $?.success?
|
|
381
|
+
$logger.error("command failed: #{$?.exitstatus}")
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
def self.zfs_send(options,zfs,props,snaps)
|
|
386
|
+
sorted = snaps.keys.sort { |a,b| snaps[a]['creation'] <=> snaps[b]['creation'] }
|
|
387
|
+
# compute the zfs "path"
|
|
388
|
+
# ternary operator 4eva
|
|
389
|
+
destination_path = ( options[:destination] ? options[:destination] : props['zfsmgmt:destination'] )
|
|
390
|
+
if props['zfsmgmt:destination@source'] == 'local'
|
|
391
|
+
destination_path = File.join( destination_path,
|
|
392
|
+
File.basename(zfs)
|
|
393
|
+
)
|
|
394
|
+
elsif m = /inherited from (.+)/.match(props['zfsmgmt:destination@source'])
|
|
395
|
+
destination_path = File.join( destination_path,
|
|
396
|
+
File.basename(m[1]),
|
|
397
|
+
zfs.sub(m[1],'')
|
|
398
|
+
)
|
|
358
399
|
else
|
|
359
|
-
$logger.
|
|
400
|
+
$logger.error("fatal error: #{props['zfsmgmt:destination']} source: #{props['zfsmgmt:destination@source']}")
|
|
401
|
+
exit(1)
|
|
360
402
|
end
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
403
|
+
# does the destination zfs already exist?
|
|
404
|
+
remote_zfs_state = ''
|
|
405
|
+
begin
|
|
406
|
+
recv_zfs = zfsget(zfs: destination_path,
|
|
407
|
+
command_prefix: recv_command_prefix(options,props),
|
|
408
|
+
#properties: ['receive_resume_token'],
|
|
409
|
+
)
|
|
410
|
+
rescue ZfsGetError
|
|
411
|
+
$logger.debug("recv filesystem doesn't exist: #{destination_path}")
|
|
412
|
+
remote_zfs_state = 'missing'
|
|
413
|
+
else
|
|
414
|
+
if recv_zfs[destination_path].has_key?('receive_resume_token')
|
|
415
|
+
remote_zfs_state = recv_zfs[destination_path]['receive_resume_token']
|
|
416
|
+
else
|
|
417
|
+
remote_zfs_state = 'present'
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
if remote_zfs_state == 'missing'
|
|
422
|
+
# the zfs does not exist, send initial (oldest?) snapshot
|
|
423
|
+
com = []
|
|
424
|
+
source = sorted[0]
|
|
425
|
+
if options[:initial_snapshot] == 'newest' or
|
|
426
|
+
( options.has_key?('replicate') and options['replicate'] == true ) or
|
|
427
|
+
( props.has_key?('zfsmgmt:send_replicate') and props['zfsmgmt:send_replicate'] == 'true' )
|
|
428
|
+
source = sorted[-1]
|
|
365
429
|
end
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
430
|
+
com += zfs_send_com(options,
|
|
431
|
+
props,
|
|
432
|
+
[],
|
|
433
|
+
source,
|
|
434
|
+
)
|
|
435
|
+
e = zfs_send_estimate(com) if options[:verbose] == 'pv'
|
|
436
|
+
com += mbuffer_command(options) if options[:mbuffer]
|
|
437
|
+
com += pv_command(options,e) if options[:verbose] == 'pv'
|
|
438
|
+
com += zfs_recv_com(options,[],props,destination_path)
|
|
439
|
+
|
|
440
|
+
system_com(com)
|
|
441
|
+
unless $?.success?
|
|
442
|
+
return
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
elsif remote_zfs_state != 'present'
|
|
446
|
+
# should be resumable!
|
|
447
|
+
com = [ ]
|
|
448
|
+
com.push( ZfsMgmt.global_options[:zfs_binary], 'send', '-t', remote_zfs_state )
|
|
449
|
+
com.push('-v','-P') if options[:verbose] and options[:verbose] == 'send'
|
|
450
|
+
com.push('|')
|
|
451
|
+
e = zfs_send_estimate(com) if options[:verbose] == 'pv'
|
|
452
|
+
com += mbuffer_command(options) if options[:mbuffer]
|
|
453
|
+
com += pv_command(options,e) if options[:verbose] == 'pv'
|
|
454
|
+
|
|
455
|
+
recv = [ ZfsMgmt.global_options[:zfs_binary], 'recv', '-s' ]
|
|
456
|
+
recv.push('-n') if options[:noop]
|
|
457
|
+
recv.push('-u') if options[:unmount]
|
|
458
|
+
recv.push('-v') if options[:verbose] and ( options[:verbose] == 'receive' or options[:verbose] == 'recv' )
|
|
459
|
+
recv.push(dq(destination_path))
|
|
460
|
+
|
|
461
|
+
if options[:remote] or props['zfsmgmt:remote']
|
|
462
|
+
if options[:mbuffer]
|
|
463
|
+
recv = mbuffer_command(options) + recv
|
|
377
464
|
end
|
|
378
|
-
|
|
379
|
-
$logger.info(com)
|
|
380
|
-
system(com.join(' '))
|
|
465
|
+
recv = recv_command_prefix(options,props) + [ sq(recv.join(' ')) ]
|
|
381
466
|
end
|
|
467
|
+
|
|
468
|
+
com += recv
|
|
469
|
+
|
|
470
|
+
system_com(com)
|
|
471
|
+
unless $?.success?
|
|
472
|
+
return
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# the zfs already exists, so update with incremental?
|
|
477
|
+
begin
|
|
478
|
+
remote_snaps = zfsget(zfs: destination_path,
|
|
479
|
+
types: ['snapshot'],
|
|
480
|
+
command_prefix: recv_command_prefix(options,props),
|
|
481
|
+
properties: ['creation','userrefs'],
|
|
482
|
+
)
|
|
483
|
+
rescue ZfsGetError
|
|
484
|
+
$logger.error("unable to get remote snapshot information for #{destination_path}")
|
|
485
|
+
return
|
|
486
|
+
end
|
|
487
|
+
unless remote_snaps and remote_snaps.keys.length > 0
|
|
488
|
+
$logger.error("receiving filesystem has NO snapshots, it must be destroyed: #{destination_path}")
|
|
489
|
+
return
|
|
490
|
+
end
|
|
491
|
+
if remote_snaps.has_key?(sorted[-1].sub(zfs,destination_path))
|
|
492
|
+
$logger.info("the most recent local snapshot (#{sorted[-1]}) already exists on the remote side (#{sorted[-1].sub(zfs,destination_path)})")
|
|
493
|
+
return
|
|
494
|
+
end
|
|
495
|
+
remote_snaps.sort_by { |k,v| -v['creation'] }.each do |rsnap,v|
|
|
496
|
+
# oldest first
|
|
497
|
+
#pp rsnap,rsnap.sub(destination_path,zfs)
|
|
498
|
+
#pp snaps
|
|
499
|
+
if snaps.has_key?(rsnap.sub(destination_path,zfs))
|
|
500
|
+
$logger.debug("process #{rsnap} to #{sorted[-1]}")
|
|
501
|
+
com = []
|
|
502
|
+
com += zfs_send_com(options,props,[(options[:intermediary] ? '-I' : '-i'),dq(rsnap.split('@')[1])],sorted[-1])
|
|
503
|
+
e = zfs_send_estimate(com) if options[:verbose] == 'pv'
|
|
504
|
+
com += mbuffer_command(options) if options[:mbuffer]
|
|
505
|
+
com += pv_command(options,e) if options[:verbose] == 'pv'
|
|
506
|
+
com += zfs_recv_com(options,[],props,destination_path)
|
|
507
|
+
|
|
508
|
+
system_com(com)
|
|
509
|
+
return
|
|
510
|
+
end
|
|
511
|
+
$logger.debug("skipping remote snapshot #{rsnap} because the same snapshot doesn't exist locally #{rsnap.sub(destination_path,zfs)}")
|
|
512
|
+
end
|
|
513
|
+
$logger.error("receiving filesystem has no snapshots that still exists on the sending side, it must be destroyed: #{destination_path}")
|
|
514
|
+
|
|
515
|
+
end
|
|
516
|
+
def self.mbuffer_command(options)
|
|
517
|
+
mbuffer_command = [ ZfsMgmt.global_options[:mbuffer_binary] ]
|
|
518
|
+
mbuffer_command.push('-q') unless options[:verbose] == 'mbuffer'
|
|
519
|
+
mbuffer_command.push('-m',options[:mbuffer_size]) if options[:mbuffer_size]
|
|
520
|
+
mbuffer_command.push('|')
|
|
521
|
+
mbuffer_command
|
|
522
|
+
end
|
|
523
|
+
def self.zfs_send_com(options,props,extra_opts,target)
|
|
524
|
+
zfs_send_com = [ ZfsMgmt.global_options[:zfs_binary], 'send' ]
|
|
525
|
+
zfs_send_com.push('-v','-P') if options[:verbose] and options[:verbose] == 'send'
|
|
526
|
+
send_opts = {
|
|
527
|
+
'backup' => '-b',
|
|
528
|
+
'compressed' => '-c',
|
|
529
|
+
'embed' => '-e',
|
|
530
|
+
'holds' => '-h',
|
|
531
|
+
'large_block' => '-L',
|
|
532
|
+
'props' => '-p',
|
|
533
|
+
'raw' => '-w',
|
|
534
|
+
'replicate' => '-R',
|
|
535
|
+
}
|
|
536
|
+
send_opts.each do |p,o|
|
|
537
|
+
if options.has_key?(p)
|
|
538
|
+
zfs_send_com.push(o) if options[p] == true
|
|
539
|
+
elsif props.has_key?("zfsmgmt:send_#{p}")
|
|
540
|
+
zfs_send_com.push(o) if props["zfsmgmt:send_#{p}"] == 'true'
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
zfs_send_com + extra_opts + [dq(target),'|']
|
|
544
|
+
end
|
|
545
|
+
def self.zfs_recv_com(options,extra_opts,props,target)
|
|
546
|
+
zfs_recv_com = [ ZfsMgmt.global_options[:zfs_binary], 'recv', '-F', '-s' ]
|
|
547
|
+
recv_opts = {
|
|
548
|
+
'noop' => '-n',
|
|
549
|
+
'drop_holds' => '-h',
|
|
550
|
+
'unmount' => '-u',
|
|
551
|
+
}
|
|
552
|
+
recv_opts.each do |p,o|
|
|
553
|
+
if options.has_key?(p)
|
|
554
|
+
zfs_recv_com.push(o) if options[p] == true
|
|
555
|
+
elsif props.has_key?("zfsmgmt:recv_#{p}")
|
|
556
|
+
zfs_recv_com.push(o) if props["zfsmgmt:recv_#{p}"] == 'true'
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
zfs_recv_com.push('-v') if options[:verbose] and ( options[:verbose] == 'receive' or options[:verbose] == 'recv' )
|
|
560
|
+
if options[:exclude]
|
|
561
|
+
options[:exclude].each do |x|
|
|
562
|
+
zfs_recv_com.push('-x',x)
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
if options[:option]
|
|
566
|
+
options[:option].each do |x|
|
|
567
|
+
zfs_recv_com.push('-o',x)
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
zfs_recv_com += extra_opts
|
|
571
|
+
zfs_recv_com.push(dq(target))
|
|
572
|
+
|
|
573
|
+
if options[:remote] or props['zfsmgmt:remote']
|
|
574
|
+
if options[:mbuffer]
|
|
575
|
+
zfs_recv_com = mbuffer_command(options) + zfs_recv_com
|
|
576
|
+
end
|
|
577
|
+
zfs_recv_com = recv_command_prefix(options,props) + [ sq(zfs_recv_com.join(' ')) ]
|
|
578
|
+
end
|
|
579
|
+
zfs_recv_com
|
|
580
|
+
end
|
|
581
|
+
def self.recv_command_prefix(options,props)
|
|
582
|
+
( (options[:remote] or props['zfsmgmt:remote']) ?
|
|
583
|
+
[ 'ssh', ( options[:remote] ? options[:remote] : props['zfsmgmt:remote'] ) ] :
|
|
584
|
+
[] )
|
|
585
|
+
end
|
|
586
|
+
def self.zfs_send_estimate(com)
|
|
587
|
+
lcom = com.dup
|
|
588
|
+
lcom.pop() # remove the pipe symbol
|
|
589
|
+
precom = [ lcom.shift, lcom.shift ]
|
|
590
|
+
lcom.unshift('-P') unless lcom.include?('-P')
|
|
591
|
+
lcom.unshift('-n')
|
|
592
|
+
lcom.push('2>&1')
|
|
593
|
+
lcom = precom + lcom
|
|
594
|
+
$logger.debug(lcom.join(' '))
|
|
595
|
+
%x[#{lcom.join(' ')}].each_line do |l|
|
|
596
|
+
if m = /(incremental|size).*\s+(\d+)$/.match(l)
|
|
597
|
+
return m[2].to_i
|
|
598
|
+
end
|
|
599
|
+
end
|
|
600
|
+
$logger.error("no estimate available")
|
|
601
|
+
return nil
|
|
602
|
+
end
|
|
603
|
+
def self.pv_command(options,estimate)
|
|
604
|
+
a = []
|
|
605
|
+
a += [options[:pv_binary], '-prb' ]
|
|
606
|
+
if estimate
|
|
607
|
+
a += ['-e', '-s', estimate ]
|
|
608
|
+
end
|
|
609
|
+
a.push('|')
|
|
610
|
+
a
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def self.sq(s)
|
|
614
|
+
"'#{s}'"
|
|
615
|
+
end
|
|
616
|
+
def self.dq(s)
|
|
617
|
+
"\"#{s}\""
|
|
618
|
+
end
|
|
619
|
+
def self.prop_on?(v)
|
|
620
|
+
['true','on'].include?(v)
|
|
621
|
+
end
|
|
622
|
+
def self.match_filter?(zfs:, filter:)
|
|
623
|
+
/#{filter}/ =~ zfs
|
|
624
|
+
end
|
|
625
|
+
def self.key_comp?(h,p,v = method(:prop_on?))
|
|
626
|
+
#$logger.debug("p:#{p}\th[p]:#{h[p]}\tv:#{v}")
|
|
627
|
+
return false unless h.has_key?(p)
|
|
628
|
+
if v.kind_of?(Array)
|
|
629
|
+
return v.include?(h[p])
|
|
630
|
+
elsif v.kind_of?(Hash)
|
|
631
|
+
return v.keys.include?(h[p])
|
|
632
|
+
elsif v.kind_of?(String)
|
|
633
|
+
return h[p] == v
|
|
634
|
+
elsif v.kind_of?(Method)
|
|
635
|
+
return v.call(h[p])
|
|
636
|
+
elsif v.kind_of?(Regexp)
|
|
637
|
+
return v =~ h[p]
|
|
638
|
+
else
|
|
639
|
+
raise ArgumentError
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
def self.set_log_level(sev)
|
|
643
|
+
case sev
|
|
644
|
+
when 'debug'
|
|
645
|
+
$logger.level = Logger::DEBUG
|
|
646
|
+
when 'info'
|
|
647
|
+
$logger.level = Logger::INFO
|
|
648
|
+
when 'warn'
|
|
649
|
+
$logger.level = Logger::WARN
|
|
650
|
+
when 'error'
|
|
651
|
+
$logger.level = Logger::ERROR
|
|
652
|
+
when 'fatal'
|
|
653
|
+
$logger.level = Logger::FATAL
|
|
382
654
|
end
|
|
383
655
|
end
|
|
384
656
|
end
|
data/lib/zfs_mgmt/restic.rb
CHANGED
|
@@ -9,11 +9,16 @@ module ZfsMgmt::Restic
|
|
|
9
9
|
'--tag', 'zfsmgmt',
|
|
10
10
|
'--path', "/#{zfs}",
|
|
11
11
|
]
|
|
12
|
-
if
|
|
13
|
-
com.push(
|
|
12
|
+
if options.has_key?('password_file')
|
|
13
|
+
com.push('-p',options['password_file'])
|
|
14
14
|
end
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
if options.has_key?('repo')
|
|
16
|
+
com.push('--repo', options['repo'])
|
|
17
|
+
elsif props.has_key?('zfsmgmt:restic_repository')
|
|
18
|
+
com.push( '--repo', props['zfsmgmt:restic_repository'] )
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
$logger.info("#{com.join(' ')}")
|
|
17
22
|
restic_output = %x(#{com.join(' ')})
|
|
18
23
|
unless $?.success?
|
|
19
24
|
$logger.error("unable to query the restic database")
|
|
@@ -102,7 +107,7 @@ module ZfsMgmt::Restic
|
|
|
102
107
|
"zfsmgmt:snapshot=#{last_zfs_snapshot}",
|
|
103
108
|
"zfsmgmt:zfs=#{zfs}",
|
|
104
109
|
"zfsmgmt:level=#{level}" ]
|
|
105
|
-
com = [
|
|
110
|
+
com = [ ZfsMgmt.global_options['zfs_binary'], 'send', '-w', '-h', '-p' ]
|
|
106
111
|
if level > 0
|
|
107
112
|
if options[:intermediary]
|
|
108
113
|
com.push('-I')
|
|
@@ -119,8 +124,16 @@ module ZfsMgmt::Restic
|
|
|
119
124
|
tags.each do |tag|
|
|
120
125
|
com.push( '--tag', "\"#{tag}\"" )
|
|
121
126
|
end
|
|
122
|
-
if
|
|
123
|
-
com.push(
|
|
127
|
+
if options.has_key?('limit_upload')
|
|
128
|
+
com.push('--limit-upload', options['limit_upload'])
|
|
129
|
+
end
|
|
130
|
+
if options.has_key?('password_file')
|
|
131
|
+
com.push('-p',options['password_file'])
|
|
132
|
+
end
|
|
133
|
+
if options.has_key?('repo')
|
|
134
|
+
com.push('--repo', options['repo'])
|
|
135
|
+
elsif props.has_key?('zfsmgmt:restic_repository')
|
|
136
|
+
com.push( '--repo', props['zfsmgmt:restic_repository'] )
|
|
124
137
|
end
|
|
125
138
|
if options[:verbose]
|
|
126
139
|
com.push('--verbose',options[:verbose])
|
|
@@ -130,8 +143,7 @@ module ZfsMgmt::Restic
|
|
|
130
143
|
unless ZfsMgmt.zfs_holds(last_zfs_snapshot).include?('zfsmgmt_restic')
|
|
131
144
|
ZfsMgmt.zfs_hold('zfsmgmt_restic',last_zfs_snapshot)
|
|
132
145
|
end
|
|
133
|
-
|
|
134
|
-
system(com.join(' '))
|
|
146
|
+
ZfsMgmt.system_com(com)
|
|
135
147
|
chain_snaps = chain.map do |rsnap|
|
|
136
148
|
rsnap['zfsmgmt:snapshot']
|
|
137
149
|
end
|
data/lib/zfs_mgmt/version.rb
CHANGED
data/lib/zfs_mgmt/zfs_mgr.rb
CHANGED
|
@@ -5,20 +5,53 @@ class ZfsMgmt::ZfsMgr::List < Thor
|
|
|
5
5
|
:desc => 'only act on zfs matching this regexp'
|
|
6
6
|
desc "stale", "list all zfs with stale snapshots"
|
|
7
7
|
method_option :age, :desc => "timeframe outside of which the zfs will be considered stale", :default => '1d'
|
|
8
|
+
method_option :format, :desc => "output format", :type => :string, :enum => ['table','tab'], :default => 'table'
|
|
8
9
|
def stale()
|
|
10
|
+
ZfsMgmt.set_log_level(options[:loglevel])
|
|
11
|
+
ZfsMgmt.global_options = options
|
|
9
12
|
cutoff = Time.at(Time.now.to_i - ZfsMgmt.timespec_to_seconds(options[:age]))
|
|
10
13
|
table = Text::Table.new
|
|
11
14
|
table.head = ['zfs','snapshot','age']
|
|
12
15
|
table.rows = []
|
|
13
|
-
ZfsMgmt.zfs_managed_list(filter: options[:filter]).each do |
|
|
14
|
-
zfs,props,snaps = blob
|
|
16
|
+
ZfsMgmt.zfs_managed_list(filter: options[:filter]).each do |zfs,props,snap|
|
|
15
17
|
last = snaps.keys.sort { |a,b| snaps[a]['creation'] <=> snaps[b]['creation'] }.last
|
|
16
18
|
snap_time = Time.at(snaps[last]['creation'])
|
|
17
19
|
if snap_time < cutoff
|
|
18
|
-
|
|
20
|
+
line = [zfs,last.split('@')[1],snap_time]
|
|
21
|
+
table.rows << line
|
|
22
|
+
if options[:format] == 'tab'
|
|
23
|
+
print line.join("\t"),"\n"
|
|
24
|
+
end
|
|
19
25
|
end
|
|
20
26
|
end
|
|
21
|
-
if table.rows.count > 0
|
|
27
|
+
if options[:format] == 'table' and table.rows.count > 0
|
|
28
|
+
print table.to_s
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
desc "holds", "list all holds on snapshots"
|
|
32
|
+
method_option :format, :desc => "output format", :type => :string, :enum => ['table','tab','release'], :default => 'table'
|
|
33
|
+
def holds()
|
|
34
|
+
ZfsMgmt.set_log_level(options[:loglevel])
|
|
35
|
+
ZfsMgmt.global_options = options
|
|
36
|
+
table = Text::Table.new
|
|
37
|
+
table.head = ['snapshot','userrefs','holds']
|
|
38
|
+
table.rows = []
|
|
39
|
+
ZfsMgmt.zfs_managed_list(filter: options[:filter], property_match: {} ).each do |zfs,props,snaps|
|
|
40
|
+
snaps.sort_by { |x,y| y['creation'] }.each do |snap,d|
|
|
41
|
+
if d['userrefs'] > 0
|
|
42
|
+
line = [snap,d['userrefs'].to_s,ZfsMgmt.zfs_holds(snap).join(',')]
|
|
43
|
+
table.rows << line
|
|
44
|
+
if options[:format] == 'tab'
|
|
45
|
+
print line.join("\t"),"\n"
|
|
46
|
+
elsif options[:format] == 'release'
|
|
47
|
+
ZfsMgmt.zfs_holds(snap).each do |hold|
|
|
48
|
+
print "zfs release #{hold} #{snap}\n"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
if options[:format] == 'table' and table.rows.count > 0
|
|
22
55
|
print table.to_s
|
|
23
56
|
end
|
|
24
57
|
end
|
|
@@ -5,25 +5,35 @@ class ZfsMgmt::ZfsMgr::Backup < Thor
|
|
|
5
5
|
:desc => 'only act on zfs matching this regexp'
|
|
6
6
|
class_option :restic_binary, :type => :string, :default => 'restic',
|
|
7
7
|
:desc => 'restic binary'
|
|
8
|
-
class_option :zfs_binary, :type => :string, :default => 'zfs',
|
|
9
|
-
:desc => 'zfs binary'
|
|
10
8
|
class_option :verbose, :alias => '-v', :type => :numeric,
|
|
11
9
|
:desc => 'verbosity level for restic'
|
|
12
10
|
class_option :buffer, :type => :string, :default => '256m',
|
|
13
11
|
:desc => 'buffer size for mbuffer'
|
|
12
|
+
class_option :password_file, :alias => '-p', :type => :string,
|
|
13
|
+
:desc => 'passed to restic'
|
|
14
|
+
class_option :limit_upload, :type => :numeric,
|
|
15
|
+
:desc => 'passed to restic'
|
|
16
|
+
class_option :repo, :type => :string,
|
|
17
|
+
:desc => 'passed to restic'
|
|
14
18
|
desc "incremental", "perform incremental backup"
|
|
15
19
|
method_option :level, :desc => "backup level in integer form", :default => 2, :type => :numeric
|
|
16
20
|
method_option :intermediary, :alias => '-I', :desc => "pass -I (intermediary) option to zfs send", :default => false, :type => :boolean
|
|
17
21
|
def incremental()
|
|
22
|
+
ZfsMgmt.set_log_level(options[:loglevel])
|
|
23
|
+
ZfsMgmt.global_options = options
|
|
18
24
|
ZfsMgmt::Restic.backup(backup_level: options[:level], options: options)
|
|
19
25
|
end
|
|
20
26
|
desc "differential", "perform differential backup"
|
|
21
27
|
method_option :intermediary, :alias => '-I', :desc => "pass -I (intermediary) option to zfs send", :default => false, :type => :boolean
|
|
22
28
|
def differential()
|
|
29
|
+
ZfsMgmt.set_log_level(options[:loglevel])
|
|
30
|
+
ZfsMgmt.global_options = options
|
|
23
31
|
ZfsMgmt::Restic.backup(backup_level: 1, options: options)
|
|
24
32
|
end
|
|
25
33
|
desc "full", "perform full backup"
|
|
26
34
|
def full()
|
|
35
|
+
ZfsMgmt.set_log_level(options[:loglevel])
|
|
36
|
+
ZfsMgmt.global_options = options
|
|
27
37
|
ZfsMgmt::Restic.backup(backup_level: 0, options: options)
|
|
28
38
|
end
|
|
29
39
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# zfs send stuff
|
|
2
|
+
|
|
3
|
+
class ZfsMgmt::ZfsMgr::Send < Thor
|
|
4
|
+
class_option :filter, :type => :string, :default => '.+',
|
|
5
|
+
:desc => 'only act on zfs matching this regexp'
|
|
6
|
+
desc "all", "send all zfs configured via user properties"
|
|
7
|
+
method_option :remote, :type => :string,
|
|
8
|
+
:desc => 'remote specification like root@otherhost or localhost'
|
|
9
|
+
method_option :destination, :type => :string,
|
|
10
|
+
:desc => 'destination path like otherpool/ourpool'
|
|
11
|
+
method_option :verbose, :type => :string, :aliases => :'-v', :enum => ['send','receive','recv','mbuffer','pv'],
|
|
12
|
+
:desc => 'enable verbose output on the specified element of the pipe'
|
|
13
|
+
method_option :initial_snapshot, :type => :string, :enum => ['oldest','newest'], :default => 'oldest',
|
|
14
|
+
:desc => 'when sending the initial snapshot use the oldest or most recent snapshot'
|
|
15
|
+
|
|
16
|
+
method_option :intermediary, :aliases => :'-I', :desc => "pass -I option to zfs send", :type => :boolean
|
|
17
|
+
method_option :backup, :aliases => :'-p', :desc => "pass -b (--backup) option to zfs send", :type => :boolean
|
|
18
|
+
method_option :compressed, :aliases => :'-c', :desc => "pass -c (compressed) option to zfs send", :type => :boolean
|
|
19
|
+
method_option :embed, :aliases => :'-e', :desc => "pass -e (--embed) option to zfs send", :type => :boolean
|
|
20
|
+
method_option :holds, :aliases => :'-h', :desc => "pass the -h (--holds) option to zfs send", :type => :boolean
|
|
21
|
+
method_option :large_block, :aliases => :'-L', :desc => "pass -L (--large-block) option to zfs send", :type => :boolean
|
|
22
|
+
method_option :props, :aliases => :'-p', :desc => "pass -p (--props) option to zfs send", :type => :boolean
|
|
23
|
+
method_option :raw, :aliases => :'-w', :desc => "pass -w (--raw) option to zfs send", :type => :boolean
|
|
24
|
+
method_option :replicate, :aliases => :'-R', :desc => "pass -R (--replicate) option to zfs send", :type => :boolean
|
|
25
|
+
|
|
26
|
+
method_option :noop, :aliases => :'-n', :desc => "pass -n (noop) option to zfs send", :type => :boolean
|
|
27
|
+
method_option :unmount, :aliases => :'-u', :desc => "pass -u (unmount) option to zfs receive", :type => :boolean
|
|
28
|
+
method_option :exclude, :aliases => :'-x', :desc => "passed to -x option of receive side", :type => :array
|
|
29
|
+
method_option :option, :aliases => :'-o', :desc => "passed to -o option of receive side", :type => :array
|
|
30
|
+
method_option :drop_holds, :desc => "pass the -h option to zfs recv, indicating holds should be ignored", :type => :boolean
|
|
31
|
+
|
|
32
|
+
method_option :mbuffer, :desc => "insert mbuffer between send and recv", :default => true, :type => :boolean
|
|
33
|
+
method_option :mbuffer_size, :desc => "passed to mbuffer -s option", :type => :string
|
|
34
|
+
def all()
|
|
35
|
+
ZfsMgmt.set_log_level(options[:loglevel])
|
|
36
|
+
ZfsMgmt.global_options = options
|
|
37
|
+
|
|
38
|
+
[
|
|
39
|
+
{ 'zfsmgmt:send' => 'true' },
|
|
40
|
+
# {
|
|
41
|
+
# 'zfsmgmt:send' => 'replicate',
|
|
42
|
+
# 'zfsmgmt:send@source' => 'local'
|
|
43
|
+
# },
|
|
44
|
+
].each do |match|
|
|
45
|
+
ZfsMgmt.zfs_managed_list(filter: options[:filter],
|
|
46
|
+
property_match: match).each do |zfs,props,snaps|
|
|
47
|
+
if props['zfsmgmt:send@source'] == 'received'
|
|
48
|
+
$logger.debug("skipping received filesystem: #{zfs}")
|
|
49
|
+
next
|
|
50
|
+
end
|
|
51
|
+
if props.has_key?('zfsmgmt:send_replicate') and props['zfsmgmt:send_replicate'] == 'true' and props['zfsmgmt:send_replicate@source'] != 'local'
|
|
52
|
+
$logger.debug("skipping descendant of replicated filesystems: #{zfs}")
|
|
53
|
+
next
|
|
54
|
+
end
|
|
55
|
+
unless props['zfsmgmt:destination']
|
|
56
|
+
$logger.error("#{zfs}: you must specify a destination zfs path via the user property zfsmgmt:destination, even if using --destination on the command line, skipping")
|
|
57
|
+
next
|
|
58
|
+
end
|
|
59
|
+
ZfsMgmt.zfs_send(options,zfs,props,snaps)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
# implement snapshot management
|
|
2
2
|
|
|
3
3
|
class ZfsMgmt::ZfsMgr::Snapshot < Thor
|
|
4
|
-
class_option :noop, :type => :boolean, :default => false,
|
|
5
|
-
:desc => 'pass -n option to zfs commands'
|
|
6
|
-
class_option :verbose, :type => :boolean, :default => false,
|
|
7
|
-
:desc => 'pass -v option to zfs commands'
|
|
8
|
-
class_option :debug, :type => :boolean, :default => false,
|
|
9
|
-
:desc => 'set logging level to debug'
|
|
10
4
|
class_option :filter, :type => :string, :default => '.+',
|
|
11
5
|
:desc => 'only act on zfs matching this regexp'
|
|
12
6
|
desc "destroy", "apply the snapshot destroy policy to zfs"
|
|
7
|
+
method_option :noop, :type => :boolean, :default => false,
|
|
8
|
+
:desc => 'pass -n option to zfs commands'
|
|
9
|
+
method_option :verbose, :type => :boolean, :default => false,
|
|
10
|
+
:desc => 'pass -v option to zfs commands'
|
|
13
11
|
def destroy()
|
|
14
|
-
ZfsMgmt.
|
|
12
|
+
ZfsMgmt.set_log_level(options[:loglevel])
|
|
13
|
+
ZfsMgmt.global_options = options
|
|
14
|
+
ZfsMgmt.snapshot_destroy(noop: options[:noop], verbose: options[:verbose], filter: options[:filter])
|
|
15
15
|
end
|
|
16
16
|
desc "policy", "print the policy table for zfs"
|
|
17
17
|
def policy()
|
|
18
|
-
ZfsMgmt.
|
|
18
|
+
ZfsMgmt.set_log_level(options[:loglevel])
|
|
19
|
+
ZfsMgmt.global_options = options
|
|
20
|
+
ZfsMgmt.snapshot_policy(filter: options[:filter])
|
|
19
21
|
end
|
|
20
22
|
desc "create", "execute zfs snapshot based on zfs properties"
|
|
23
|
+
method_option :noop, :type => :boolean, :default => false,
|
|
24
|
+
:desc => 'log snapshot commands without running zfs snapshot'
|
|
25
|
+
method_option :verbose, :type => :boolean, :default => false,
|
|
26
|
+
:desc => 'pass -v option to zfs commands'
|
|
21
27
|
def create()
|
|
22
|
-
ZfsMgmt.
|
|
28
|
+
ZfsMgmt.set_log_level(options[:loglevel])
|
|
29
|
+
ZfsMgmt.global_options = options
|
|
30
|
+
ZfsMgmt.snapshot_create(noop: options[:noop], verbose: options[:verbose], filter: options[:filter])
|
|
23
31
|
end
|
|
24
32
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zfs_mgmt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aran Cox
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-
|
|
11
|
+
date: 2021-03-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -130,6 +130,7 @@ files:
|
|
|
130
130
|
- lib/zfs_mgmt/zfs_mgr.rb
|
|
131
131
|
- lib/zfs_mgmt/zfs_mgr/list.rb
|
|
132
132
|
- lib/zfs_mgmt/zfs_mgr/restic.rb
|
|
133
|
+
- lib/zfs_mgmt/zfs_mgr/send.rb
|
|
133
134
|
- lib/zfs_mgmt/zfs_mgr/snapshot.rb
|
|
134
135
|
- zfs_mgmt.gemspec
|
|
135
136
|
homepage: https://github.com/aranc23/zfs_mgmt
|