roundup 0.1.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/CHANGELOG +3 -0
- data/README.md +22 -0
- data/lib/roundup.rb +118 -0
- data/test/helper.rb +3 -0
- data/test/test_roundup.rb +48 -0
- metadata +66 -0
data/CHANGELOG
ADDED
data/README.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Roundup
|
2
|
+
|
3
|
+
* http://github.com/deepfryed/roundup
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
A simple snapshot cleaner to keep the right number of historical data backups.
|
8
|
+
|
9
|
+
## Synopsis
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
require 'roundup'
|
13
|
+
|
14
|
+
Roundup.new(path: '/path/to/snapshots/**/*.gz', dryrun: false).clean!
|
15
|
+
Roundup.new(path: '/path/to/snapshots/**/*.gz').files_to_keep #=> [Array]
|
16
|
+
Roundup.new(path: '/path/to/snapshots/**/*.gz').files_to_remove #=> [Array]
|
17
|
+
Roundup.new(path: '/path/to/snapshots/**/*.gz', policy: {hourly: 24, daily: 30, monthly: 12}).clean!
|
18
|
+
```
|
19
|
+
|
20
|
+
## License
|
21
|
+
|
22
|
+
BSD
|
data/lib/roundup.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Roundup
|
5
|
+
DEFAULT_POLICY = {:hourly => 48, :daily => 31, :monthly => 12}
|
6
|
+
|
7
|
+
attr_reader :path, :policy, :scanner
|
8
|
+
|
9
|
+
def initialize path, options = {}
|
10
|
+
@path = path
|
11
|
+
@dryrun = options.fetch(:dryrun, true)
|
12
|
+
@verbose = options.fetch(:verbose, false)
|
13
|
+
@policy = options.fetch(:policy, DEFAULT_POLICY)
|
14
|
+
@scanner = options.fetch(:scanner, Scanner).new(path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def files_to_keep
|
18
|
+
policy.map {|interval, n| scanner.find(interval).take(n)}.flatten.uniq
|
19
|
+
end
|
20
|
+
|
21
|
+
def files_to_remove
|
22
|
+
files - files_to_keep
|
23
|
+
end
|
24
|
+
|
25
|
+
def clean!
|
26
|
+
remove = files_to_remove
|
27
|
+
|
28
|
+
info 'deleting %d files' % remove.count
|
29
|
+
info remove.join($/) unless remove.empty?
|
30
|
+
|
31
|
+
if dryrun?
|
32
|
+
info 'dry run - so nothing deleted'
|
33
|
+
else
|
34
|
+
FileUtils.rm_f(remove)
|
35
|
+
info 'cleaned!'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def verbose?
|
42
|
+
!!@verbose
|
43
|
+
end
|
44
|
+
|
45
|
+
def logger
|
46
|
+
@logger ||= Logger.new($stdout)
|
47
|
+
end
|
48
|
+
|
49
|
+
def info message
|
50
|
+
logger.info(message) if verbose?
|
51
|
+
end
|
52
|
+
|
53
|
+
def dryrun?
|
54
|
+
@dryrun == true
|
55
|
+
end
|
56
|
+
|
57
|
+
class Scanner
|
58
|
+
DAYS = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
59
|
+
|
60
|
+
attr_reader :path
|
61
|
+
|
62
|
+
def self.custom &block
|
63
|
+
Class.new(self).tap do |klass|
|
64
|
+
klass.send(:define_method, :created_time, &block)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def initialize path
|
69
|
+
@path = path
|
70
|
+
end
|
71
|
+
|
72
|
+
def find interval
|
73
|
+
send(interval)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def timeslice
|
79
|
+
time = nil
|
80
|
+
files.sort_by(&method(:created_time)).reverse.each_with_object([]) do |file, keep|
|
81
|
+
created_at = created_time(file)
|
82
|
+
if time.nil? or yield(time, created_at)
|
83
|
+
time = created_at
|
84
|
+
keep << file
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def hourly
|
90
|
+
timeslice {|a, b| (a - b) >= 3600}
|
91
|
+
end
|
92
|
+
|
93
|
+
def daily
|
94
|
+
timeslice {|a, b| (a - b) >= 86400}
|
95
|
+
end
|
96
|
+
|
97
|
+
def monthly
|
98
|
+
timeslice do |a, b|
|
99
|
+
case
|
100
|
+
when a < b
|
101
|
+
b - DAYS[a.month] * 86400 >= a
|
102
|
+
when a > b
|
103
|
+
a - DAYS[b.month] * 86400 >= b
|
104
|
+
else
|
105
|
+
false
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def files
|
111
|
+
Dir[path]
|
112
|
+
end
|
113
|
+
|
114
|
+
def created_time file
|
115
|
+
File.stat(file).ctime
|
116
|
+
end
|
117
|
+
end # Scanner
|
118
|
+
end # Roundup
|
data/test/helper.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
describe 'Roundup' do
|
5
|
+
before do
|
6
|
+
@files = 10000.times.map {|n| (Time.now - (1800 * n)).strftime('snapshot-%Y%m%d%H%M%S.gz')}
|
7
|
+
@scanner = Roundup::Scanner.custom {|file| DateTime.strptime(file, 'snapshot-%Y%m%d%H%M%S').to_time}
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should keep hourlies' do
|
11
|
+
Dir.stub(:[], @files) do
|
12
|
+
roundup = Roundup.new('/tmp', policy: {hourly: 24}, scanner: @scanner)
|
13
|
+
roundup.files_to_keep.must_equal @files.select.with_index {|n, i| i.even? && i < 48}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should keep dailies' do
|
18
|
+
Dir.stub(:[], @files) do
|
19
|
+
roundup = Roundup.new('/tmp', policy: {daily: 7}, scanner: @scanner)
|
20
|
+
roundup.files_to_keep.must_equal @files.select.with_index {|n, i| i % 48 == 0}.take(7)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should keep monthlies' do
|
25
|
+
files = %w(
|
26
|
+
snapshot-20121103000000.gz
|
27
|
+
snapshot-20121102000000.gz
|
28
|
+
snapshot-20121101000000.gz
|
29
|
+
snapshot-20121003000000.gz
|
30
|
+
snapshot-20121004000000.gz
|
31
|
+
snapshot-20121002000000.gz
|
32
|
+
snapshot-20120903000000.gz
|
33
|
+
snapshot-20120902000000.gz
|
34
|
+
snapshot-20120901000000.gz
|
35
|
+
snapshot-20120803000000.gz
|
36
|
+
snapshot-20120802000000.gz
|
37
|
+
snapshot-20120801000000.gz
|
38
|
+
snapshot-20120701000000.gz
|
39
|
+
snapshot-20120723000000.gz
|
40
|
+
snapshot-20120725000000.gz
|
41
|
+
)
|
42
|
+
|
43
|
+
Dir.stub(:[], files) do
|
44
|
+
roundup = Roundup.new('/tmp', policy: {monthly: 12}, scanner: @scanner)
|
45
|
+
roundup.files_to_keep.must_equal files.select.with_index {|n, i| i % 3 == 0}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: roundup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Bharanee Rathna
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Clean periodic snaphots while keeping files that matter
|
31
|
+
email:
|
32
|
+
- deepfryed@gmail.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- test/helper.rb
|
38
|
+
- test/test_roundup.rb
|
39
|
+
- lib/roundup.rb
|
40
|
+
- README.md
|
41
|
+
- CHANGELOG
|
42
|
+
homepage: http://github.com/deepfryed/roundup
|
43
|
+
licenses: []
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.8.24
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: A file system cleaner for periodic snapshots
|
66
|
+
test_files: []
|