zfs-tools 0.2.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.
- data/HISTORY +24 -0
- data/LICENSE +23 -0
- data/README +30 -0
- data/bin/zfs_list_obsolete_snapshots +64 -0
- data/bin/zfs_safe_destroy +80 -0
- data/bin/zfs_snapshot +24 -0
- data/lib/snapshot_set.rb +113 -0
- data/lib/zfs/dataset.rb +80 -0
- data/lib/zfs/snapshot_list.rb +29 -0
- data/lib/zfs.rb +7 -0
- metadata +106 -0
data/HISTORY
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= 0.2.0
|
2
|
+
|
3
|
+
. first public release
|
4
|
+
- zfs_incremental_sync deleted. Not the general solution of the problem we'd
|
5
|
+
hoped for.
|
6
|
+
|
7
|
+
== 0.1.8
|
8
|
+
|
9
|
+
! Pool name can now contain spaces.
|
10
|
+
* Update to the newest activesupport gem, works with Ruby 1.9.2
|
11
|
+
* Dismiss the use of pfexec to run commands that need special privileges, in
|
12
|
+
favor of sudo.
|
13
|
+
* Reworked tools to use the library instead of issuing commands themselves.
|
14
|
+
* [zfs_incremental_sync] Fixed: full synch.
|
15
|
+
* [zfs_incremental_sync] Fixed: Missing snapshots on the target
|
16
|
+
would be the cause for a failed synch. I hope this is fixed now.
|
17
|
+
* [zfs_list_obsolete_snapshots] Works with datasets that have no snapshots
|
18
|
+
* [zfs_incremental_sync] Fixed: Bug where sync would abort because there
|
19
|
+
were no initial snapshots.
|
20
|
+
* zfs_incremental_sync doesn't use sudo anymore. All commands that need
|
21
|
+
special privileges are run through pfexec.
|
22
|
+
* Now gives incremental sync snapshots special names. This avoids collision
|
23
|
+
with the ones done hourly and it also provides information about where
|
24
|
+
the snapshot went (to which host).
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
Copyright (c) 2012,2013 Kaspar Schiess
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person
|
5
|
+
obtaining a copy of this software and associated documentation
|
6
|
+
files (the "Software"), to deal in the Software without
|
7
|
+
restriction, including without limitation the rights to use,
|
8
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the
|
10
|
+
Software is furnished to do so, subject to the following
|
11
|
+
conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
18
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
20
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
21
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
22
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
23
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
A few ZFS tools, mostly related to snapshotting, cleaning up and synching.
|
3
|
+
|
4
|
+
SYNOPSIS
|
5
|
+
|
6
|
+
# Snapshot any dataset, snapshot name is the current date/time in a
|
7
|
+
# normalized format:
|
8
|
+
zfs_snapshot pool1/my/dataset
|
9
|
+
|
10
|
+
# List snapshots that are obsolete, given some rules about what is
|
11
|
+
# considered obsolete:
|
12
|
+
echo pool1 | zfs_list_obsolete_snapshots
|
13
|
+
|
14
|
+
# Safely destroy some dataset (recursively). This will always ask for
|
15
|
+
# permission!
|
16
|
+
zfs_safe_destroy pool1/my/dataset
|
17
|
+
|
18
|
+
STATUS
|
19
|
+
|
20
|
+
This is useful in production; the code needs to be cleaned up.
|
21
|
+
|
22
|
+
LICENSE
|
23
|
+
|
24
|
+
MIT license, see LICENSE file.
|
25
|
+
|
26
|
+
HACKING
|
27
|
+
|
28
|
+
To run the specs, type `rspec`.
|
29
|
+
|
30
|
+
Pull requests welcome.
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
4
|
+
require 'snapshot_set'
|
5
|
+
require 'zfs'
|
6
|
+
|
7
|
+
default = {
|
8
|
+
1.hour => 24,
|
9
|
+
1.day => 7,
|
10
|
+
1.month => 12,
|
11
|
+
1.year => 2
|
12
|
+
}
|
13
|
+
options = {
|
14
|
+
}
|
15
|
+
|
16
|
+
require 'optparse'
|
17
|
+
OptionParser.new { |o|
|
18
|
+
o.banner = %Q{Usage: #{$0} [options]
|
19
|
+
|
20
|
+
Interprets each line on stdin as a zfs dataset. Outputs all snapshots on the
|
21
|
+
dataset that are obsolete by the given rule. Example:
|
22
|
+
|
23
|
+
echo 'pool1/backup' | zfs_list_obsolete_snapshots --daily=3
|
24
|
+
|
25
|
+
When called without arguments, it will assumes a default of
|
26
|
+
|
27
|
+
--hourly=24 --daily=7 --monthly=12 --yearly=2
|
28
|
+
|
29
|
+
}
|
30
|
+
o.separator %Q{ Options are:}
|
31
|
+
|
32
|
+
o.on('--hourly [N]', Integer, "keep N hourly backups (doubles as switch for this help)") do |v|
|
33
|
+
if v
|
34
|
+
options[1.hour] = v
|
35
|
+
else
|
36
|
+
puts o
|
37
|
+
exit 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
{
|
41
|
+
:daily => 1.day,
|
42
|
+
:weekly => 1.week,
|
43
|
+
:monthly => 1.month,
|
44
|
+
:quarterly => 3.months,
|
45
|
+
:semianually => 6.months,
|
46
|
+
:yearly => 1.year
|
47
|
+
}.each do |name, period|
|
48
|
+
o.on("--#{name} N", Integer, "keep N #{name} backups") do |v|
|
49
|
+
options[period] = v
|
50
|
+
end
|
51
|
+
end
|
52
|
+
}.parse!(ARGV)
|
53
|
+
|
54
|
+
|
55
|
+
opts = options.empty? ? default : options
|
56
|
+
while line=gets
|
57
|
+
dataset = ZFS::Dataset.new(line.chomp)
|
58
|
+
snapshots = dataset.snapshots
|
59
|
+
|
60
|
+
keep = SnapshotSet.new(snapshots).gpc(opts).to_a
|
61
|
+
remove = (snapshots - keep).sort
|
62
|
+
|
63
|
+
puts remove.map { |sn| "#{dataset.name}@#{sn}" }
|
64
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
4
|
+
|
5
|
+
require 'mixlib/shellout'
|
6
|
+
|
7
|
+
if ARGV.empty?
|
8
|
+
puts %Q{
|
9
|
+
Usage: zfs_safe_destroy DATASET
|
10
|
+
|
11
|
+
Scans the given dataset and prints a summary of what zfs objects
|
12
|
+
(filesystems, volumes and snapshots) would be affected if one was to
|
13
|
+
recursively destroy the given dataset. If you type 'yes' at the prompt,
|
14
|
+
it will then perform a recursive destroy command on the dataset.
|
15
|
+
|
16
|
+
CAUTION: By typing yes at the prompt, you will loose data. If this is
|
17
|
+
your goal, this is your command.
|
18
|
+
}
|
19
|
+
exit 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def enumerate_objects dataset
|
23
|
+
zfs = Mixlib::ShellOut.new(
|
24
|
+
'zfs', 'list -r -t all -Ho name,type ', dataset)
|
25
|
+
zfs.run_command
|
26
|
+
zfs.error!
|
27
|
+
|
28
|
+
summary = Hash.new(0)
|
29
|
+
|
30
|
+
zfs.stdout.lines.map do |line|
|
31
|
+
name, _, type = line.chomp.rpartition(' ')
|
32
|
+
name.strip!
|
33
|
+
|
34
|
+
next if name.size == 0
|
35
|
+
|
36
|
+
summary[type] += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
summary
|
40
|
+
rescue => ex
|
41
|
+
warn ex.to_s
|
42
|
+
warn "Aborting."
|
43
|
+
exit(3)
|
44
|
+
end
|
45
|
+
def zfs_recursive_destroy dataset
|
46
|
+
destroy = Mixlib::ShellOut.new('zfs', 'destroy -r ', dataset)
|
47
|
+
destroy.run_command
|
48
|
+
destroy.error!
|
49
|
+
|
50
|
+
rescue => ex
|
51
|
+
warn ex.to_s
|
52
|
+
warn "Aborting."
|
53
|
+
exit(4)
|
54
|
+
end
|
55
|
+
|
56
|
+
# ----------------------------------------------------------------- main logic
|
57
|
+
|
58
|
+
dataset = ARGV.first
|
59
|
+
before = enumerate_objects(dataset)
|
60
|
+
|
61
|
+
puts "You're about to permanently destroy: "
|
62
|
+
before.each do |type, count|
|
63
|
+
printf "%5d %ss\n", count, type
|
64
|
+
end
|
65
|
+
|
66
|
+
puts "\n'zfs destroy -r #{dataset}'"
|
67
|
+
print "Please confirm the operation by typing 'yes': "
|
68
|
+
confirmation = $stdin.gets
|
69
|
+
exit(1) unless confirmation.chomp == 'yes'
|
70
|
+
|
71
|
+
after = enumerate_objects(dataset)
|
72
|
+
if before != after
|
73
|
+
warn "The data that would have been destroyed by the command has changed."
|
74
|
+
exit(2)
|
75
|
+
end
|
76
|
+
|
77
|
+
$stdout.sync = true
|
78
|
+
print "Destroying..."
|
79
|
+
zfs_recursive_destroy dataset
|
80
|
+
puts ' Done.'
|
data/bin/zfs_snapshot
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
4
|
+
require 'zfs'
|
5
|
+
|
6
|
+
if ARGV.empty?
|
7
|
+
puts %Q{
|
8
|
+
Usage: zfs_snapshot DATASET [COMMENT]
|
9
|
+
|
10
|
+
Snapshots a zfs dataset recursively using a timestamp as snapshot name. If
|
11
|
+
you give a comment after the dataset, it will be sanititzed and appended
|
12
|
+
to the snapshot name.
|
13
|
+
|
14
|
+
The name of the created snapshot is output to STDOUT.
|
15
|
+
}
|
16
|
+
exit 0
|
17
|
+
end
|
18
|
+
|
19
|
+
dataset_name, comment = ARGV.first(2)
|
20
|
+
|
21
|
+
dataset = ZFS::Dataset.new(dataset_name)
|
22
|
+
snapshot_name = dataset.snapshot_with_timestamp(comment)
|
23
|
+
|
24
|
+
puts snapshot_name
|
data/lib/snapshot_set.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
|
2
|
+
require 'active_support/core_ext/numeric/time'
|
3
|
+
require 'active_support/core_ext/integer/time'
|
4
|
+
require 'active_support/core_ext/date/calculations'
|
5
|
+
require 'time'
|
6
|
+
require 'set'
|
7
|
+
|
8
|
+
# A set of snapshots. This class has methods that allow to determine which
|
9
|
+
# snapshots must be deleted in order to clean up space.
|
10
|
+
#
|
11
|
+
class SnapshotSet
|
12
|
+
attr_reader :snapshots
|
13
|
+
|
14
|
+
# Keeps metadata for each snapshot. Original name is stored as just +name+,
|
15
|
+
# extracted timestamp is +time+.
|
16
|
+
#
|
17
|
+
class Snapshot
|
18
|
+
attr_reader :name, :time
|
19
|
+
|
20
|
+
def initialize(name, default_time=Time.now)
|
21
|
+
@time = default_time
|
22
|
+
@name = name
|
23
|
+
extract_timestamp(name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def extract_timestamp(name)
|
27
|
+
if md=name.match(/(\d+)-(\d+)-(\d+)-(\d{2})(\d{2})(\d{2})(.*)/)
|
28
|
+
@time = Time.parse(md.captures.join)
|
29
|
+
elsif md=name.match(/(\d+)-(\d+)-(\d+)_(\d+)-(\d+)(.*)/)
|
30
|
+
@time = Time.parse(md.captures.join)
|
31
|
+
else
|
32
|
+
@time = Time.parse(name)
|
33
|
+
end
|
34
|
+
rescue ArgumentError
|
35
|
+
# no time information in "foobar"
|
36
|
+
return nil
|
37
|
+
end
|
38
|
+
def to_s
|
39
|
+
name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Initialize this class with an unordered set of snapshot names. Names that
|
44
|
+
# are of the form 'YYYYMMDDHHMM*' or 'YYYYMMDD*' will be treated as
|
45
|
+
# timestamps to which time based rules apply.
|
46
|
+
#
|
47
|
+
def initialize(snapshot_names)
|
48
|
+
@snapshots = snapshot_names.
|
49
|
+
map { |name| Snapshot.new(name) }.
|
50
|
+
sort_by { |snapshot| snapshot.time }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the size of this set.
|
54
|
+
#
|
55
|
+
def size
|
56
|
+
snapshots.size
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the set as an array of snapshot names.
|
60
|
+
#
|
61
|
+
def to_a
|
62
|
+
snapshots.map(&:name)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Computes snapshots to keep according to grandparent-parent-child
|
66
|
+
# algorithm. If called with
|
67
|
+
#
|
68
|
+
# set.gpc(1.day: 3, 1.week: 3)
|
69
|
+
#
|
70
|
+
# it will return a snapshot set that contains (ideally) 6 snapshots, 3 in
|
71
|
+
# the current week starting one day ago, spaced out by one day. The other
|
72
|
+
# three will be on week boundaries starting one week ago and going back 3
|
73
|
+
# weeks.
|
74
|
+
#
|
75
|
+
# The algorithm will also return all the snapshots that are less than one
|
76
|
+
# day ago.
|
77
|
+
#
|
78
|
+
def gpc(keep_specification, now=Time.now)
|
79
|
+
keep_snapshot_names = Set.new
|
80
|
+
|
81
|
+
# No snapshots, nothing to keep
|
82
|
+
if snapshots.empty?
|
83
|
+
return self.class.new([])
|
84
|
+
end
|
85
|
+
|
86
|
+
# Filter snapshots that we need to keep according to keep_specification
|
87
|
+
keep_specification.each do |interval, keep_number|
|
88
|
+
next if keep_number <= 0
|
89
|
+
|
90
|
+
# We would like to sample the existing snapshots at regular offsets
|
91
|
+
# (n * interval).
|
92
|
+
sampling_points = Array(1..keep_number).map { |offset| now - (offset*interval) }
|
93
|
+
|
94
|
+
# For all sampling points, we'll compute the best snapshot to keep.
|
95
|
+
winners = sampling_points.map { |sp|
|
96
|
+
snapshots.map { |sh| [(sh.time-sp).abs, sh] }. # <score, snapshot>
|
97
|
+
sort_by { |score, sh| score }. # sort by score
|
98
|
+
first. # best match
|
99
|
+
last # snapshot
|
100
|
+
}
|
101
|
+
|
102
|
+
keep_snapshot_names += winners.map(&:name)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Add snapshots that are within [now, smallest_interval]
|
106
|
+
smallest_interval = keep_specification.map { |i,c| i }.min
|
107
|
+
keep_snapshot_names += snapshots.
|
108
|
+
select { |snapshot| snapshot.time > now-smallest_interval }.
|
109
|
+
map(&:name)
|
110
|
+
|
111
|
+
self.class.new(keep_snapshot_names.to_a)
|
112
|
+
end
|
113
|
+
end
|
data/lib/zfs/dataset.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
# A zfs dataset such as 'pool1/backup'
|
5
|
+
#
|
6
|
+
class ZFS::Dataset
|
7
|
+
attr_reader :name
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns an array of snapshots on the dataset. This does not include
|
13
|
+
# snapshots on the datasets children.
|
14
|
+
#
|
15
|
+
def snapshots
|
16
|
+
list(name).
|
17
|
+
lines.
|
18
|
+
select { |l| l.index('@') }. # only snapshots
|
19
|
+
map { |l| l.chomp.split('@') }. # <path, snapshot_name>
|
20
|
+
select { |path, sn| path==name }. # only direct snapshots
|
21
|
+
map { |path,sn| sn } # only snapshot names
|
22
|
+
end
|
23
|
+
|
24
|
+
# Snapshots the dataset with the aid of 'zfs snapshot'. +name+ is sanitized
|
25
|
+
# to something zfs accepts, which means that spaces and non-word chars
|
26
|
+
# are replaced with '_'.
|
27
|
+
#
|
28
|
+
def snapshot(name, recursive=false)
|
29
|
+
arguments = []
|
30
|
+
|
31
|
+
snapshot_name = sanitize_snapshot_name(name)
|
32
|
+
|
33
|
+
arguments << '-r' if recursive
|
34
|
+
arguments << "'#{self.name}@#{snapshot_name}'"
|
35
|
+
|
36
|
+
zfs_snapshot(*arguments)
|
37
|
+
|
38
|
+
return snapshot_name
|
39
|
+
end
|
40
|
+
|
41
|
+
# Snapshots the dataset with a timestamp that looks like 201101011345 (year,
|
42
|
+
# month, day, hour and minute). If a +comment+ is provided, the snapshot
|
43
|
+
# will have the comment appended to it, separated by a dash
|
44
|
+
# (201101011345-my_example_comment). Note that the sanitizing that #snapshot
|
45
|
+
# does applies to the comment as well.
|
46
|
+
#
|
47
|
+
# This snapshotting is always recursive.
|
48
|
+
#
|
49
|
+
def snapshot_with_timestamp(comment=nil, time=Time.now)
|
50
|
+
name = time.strftime("%Y%m%d%H%M")
|
51
|
+
name << "-#{comment}" if comment
|
52
|
+
|
53
|
+
snapshot(name, true)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Sanitizes a name to be used as a snapshot name.
|
59
|
+
#
|
60
|
+
def sanitize_snapshot_name(name)
|
61
|
+
name.gsub(/[^-_a-z0-9]/i, '_')
|
62
|
+
end
|
63
|
+
|
64
|
+
# Raw command 'zfs snapshot', args are passed as command line arguments.
|
65
|
+
#
|
66
|
+
def zfs_snapshot(*args)
|
67
|
+
zfs "snapshot", *args
|
68
|
+
end
|
69
|
+
|
70
|
+
# Raw command 'zfs', args are passed as command line arguments.
|
71
|
+
#
|
72
|
+
def zfs(*args)
|
73
|
+
arguments = args.join(' ')
|
74
|
+
`sudo /sbin/zfs #{arguments}`
|
75
|
+
end
|
76
|
+
|
77
|
+
def list(dataset)
|
78
|
+
zfs 'list', %w(-rH -oname -tall), Shellwords.escape(dataset)
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
# Represents a list of snapshots like the tool 'zfs list' outputs it. This
|
4
|
+
# class allows extracting information from that list.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# snl = ZFS::SnapshotList.new(`zfs list -H -o name`)
|
9
|
+
# snl.datasets # => array of dataset names
|
10
|
+
# snl.snapshots('pool1') # => array of snapshots on the pool1
|
11
|
+
#
|
12
|
+
class ZFS::SnapshotList
|
13
|
+
def initialize(tool_output)
|
14
|
+
@list = Hash.new { |h,k| h[k] = [] }
|
15
|
+
|
16
|
+
tool_output.lines.
|
17
|
+
map { |l| l.chomp.strip.split('@').first(2) }. # extracts path/snapshot tuples
|
18
|
+
each { |path, snapshot|
|
19
|
+
snapshots = @list[path]
|
20
|
+
snapshots << snapshot if snapshot }
|
21
|
+
|
22
|
+
end
|
23
|
+
def datasets
|
24
|
+
@list.keys
|
25
|
+
end
|
26
|
+
def snapshots(dataset_name)
|
27
|
+
@list[dataset_name]
|
28
|
+
end
|
29
|
+
end
|
data/lib/zfs.rb
ADDED
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zfs-tools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kaspar Schiess
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mixlib-shellout
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activesupport
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '3.1'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '3.1'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: i18n
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.6'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.6'
|
62
|
+
description:
|
63
|
+
email: kaspar.schiess@absurd.li
|
64
|
+
executables:
|
65
|
+
- zfs_safe_destroy
|
66
|
+
- zfs_list_obsolete_snapshots
|
67
|
+
- zfs_snapshot
|
68
|
+
extensions: []
|
69
|
+
extra_rdoc_files: []
|
70
|
+
files:
|
71
|
+
- LICENSE
|
72
|
+
- HISTORY
|
73
|
+
- README
|
74
|
+
- lib/snapshot_set.rb
|
75
|
+
- lib/zfs/dataset.rb
|
76
|
+
- lib/zfs/snapshot_list.rb
|
77
|
+
- lib/zfs.rb
|
78
|
+
- bin/zfs_list_obsolete_snapshots
|
79
|
+
- bin/zfs_safe_destroy
|
80
|
+
- bin/zfs_snapshot
|
81
|
+
homepage: http://bitbucket.org/kschiess/zfs-tools
|
82
|
+
licenses: []
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 1.8.25
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: A few ZFS tools, mostly related to snapshotting, cleaning up and synching.
|
105
|
+
test_files: []
|
106
|
+
has_rdoc:
|