roundup 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ == 0.1.0 (2012-11-04)
2
+
3
+ * Initial version.
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ require 'minitest/mock'
2
+ require 'minitest/autorun'
3
+ require 'roundup'
@@ -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: []