rb.rotate 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/.document +5 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +20 -0
- data/README.md +81 -0
- data/Rakefile +41 -0
- data/VERSION +1 -0
- data/bin/rb.rotate +12 -0
- data/lib/rb.rotate.rb +30 -0
- data/lib/rb.rotate/configuration.rb +312 -0
- data/lib/rb.rotate/directory.rb +201 -0
- data/lib/rb.rotate/dispatcher.rb +112 -0
- data/lib/rb.rotate/file.rb +174 -0
- data/lib/rb.rotate/hook.rb +135 -0
- data/lib/rb.rotate/install/defaults.yaml.initial +25 -0
- data/lib/rb.rotate/install/rotate.yaml.initial +349 -0
- data/lib/rb.rotate/log.rb +80 -0
- data/lib/rb.rotate/mail.rb +40 -0
- data/lib/rb.rotate/reader.rb +94 -0
- data/lib/rb.rotate/state.rb +211 -0
- data/lib/rb.rotate/state/archive.rb +109 -0
- data/lib/rb.rotate/state/file.rb +139 -0
- data/lib/rb.rotate/storage.rb +208 -0
- data/lib/rb.rotate/storage/entry.rb +120 -0
- data/lib/rb.rotate/storage/item.rb +415 -0
- data/rb.rotate.gemspec +85 -0
- metadata +153 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
|
5
|
+
gem "pony", ">= 1.1"
|
6
|
+
gem "sys-uname", ">= 0.8.5"
|
7
|
+
|
8
|
+
# Add dependencies to develop your gem here.
|
9
|
+
# Include everything needed to run rake, tests, features, etc.
|
10
|
+
group :development do
|
11
|
+
gem "bundler", "~> 1.0.0"
|
12
|
+
gem "jeweler", "~> 1.5.2"
|
13
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activesupport (3.0.3)
|
5
|
+
git (1.2.5)
|
6
|
+
i18n (0.5.0)
|
7
|
+
jeweler (1.5.2)
|
8
|
+
bundler (~> 1.0.0)
|
9
|
+
git (>= 1.2.5)
|
10
|
+
rake
|
11
|
+
mail (2.2.12)
|
12
|
+
activesupport (>= 2.3.6)
|
13
|
+
i18n (>= 0.4.0)
|
14
|
+
mime-types (~> 1.16)
|
15
|
+
treetop (~> 1.4.8)
|
16
|
+
mime-types (1.16)
|
17
|
+
polyglot (0.3.1)
|
18
|
+
pony (1.1)
|
19
|
+
mail (> 2.0)
|
20
|
+
rake (0.8.7)
|
21
|
+
sys-uname (0.8.5)
|
22
|
+
treetop (1.4.9)
|
23
|
+
polyglot (>= 0.3.1)
|
24
|
+
|
25
|
+
PLATFORMS
|
26
|
+
ruby
|
27
|
+
|
28
|
+
DEPENDENCIES
|
29
|
+
bundler (~> 1.0.0)
|
30
|
+
jeweler (~> 1.5.2)
|
31
|
+
pony (>= 1.1)
|
32
|
+
sys-uname (>= 0.8.5)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Martin Kozák
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
rb.rotate
|
2
|
+
=========
|
3
|
+
|
4
|
+
**rb.rotate** is an alternative to classical `logrotate` tool.
|
5
|
+
It implements very similar functionallity, features openess and
|
6
|
+
flexibility of the scripting environment and removes some most known
|
7
|
+
`logrotate` limitations.
|
8
|
+
|
9
|
+
And of sure, it adds some features and it doesn't implement some
|
10
|
+
features of the original for now.
|
11
|
+
|
12
|
+
### Removed `logrotate` Limitations
|
13
|
+
|
14
|
+
* log directory and archive directory can be on **different drives**,
|
15
|
+
* it can **follow symbolic links** both for directories and files,
|
16
|
+
* it can **respect the subdirectories structure** of the log directory,
|
17
|
+
so archive directory can have the same structure as original log
|
18
|
+
directory.
|
19
|
+
|
20
|
+
### Additional Features
|
21
|
+
|
22
|
+
* elegant, well documented and easy-to-understand [YAML][2] configuration
|
23
|
+
file,
|
24
|
+
* flexible and in fact unlimited possibility to define hooks while
|
25
|
+
"put file to archive" action and run them in mix with pre-build
|
26
|
+
actions in whatever required order.
|
27
|
+
|
28
|
+
Installation
|
29
|
+
------------
|
30
|
+
|
31
|
+
After installing the `rb.rotate` gem, simply type following as root and
|
32
|
+
then add the `rb.rotate` entry to cron.
|
33
|
+
|
34
|
+
rb.rotate install
|
35
|
+
|
36
|
+
Be warn, it works for *Linux* and *FreeBSD* only. On other platforms,
|
37
|
+
please:
|
38
|
+
|
39
|
+
1. copy initial configuration files in `<gem-path>/lib/rb.rotate/install`
|
40
|
+
to appropriate location on your platform (remove the `.initial` extension,
|
41
|
+
of sure),
|
42
|
+
2. create the `<gem-path>/lib/paths.conf` file with path to
|
43
|
+
`rotate.yaml` file,
|
44
|
+
3. change paths in `rotate.yaml` file to directories appropriate for
|
45
|
+
your platfom,
|
46
|
+
4. [report back][1] to the project output of the `rb.rotate sysname`
|
47
|
+
command and appropriate configuration locations for including them in
|
48
|
+
next release.
|
49
|
+
|
50
|
+
|
51
|
+
Status
|
52
|
+
------
|
53
|
+
Currently in **pre-beta** version. All features are implemented, some
|
54
|
+
of them such as *hooks* are untested.
|
55
|
+
|
56
|
+
### Documentation
|
57
|
+
|
58
|
+
See the configuration file. It's pretty well documented with possible values
|
59
|
+
listed and a lot of explaining.
|
60
|
+
|
61
|
+
|
62
|
+
Contributing
|
63
|
+
------------
|
64
|
+
|
65
|
+
1. Fork it.
|
66
|
+
2. Create a branch (`git checkout -b 20101220-my-change`).
|
67
|
+
3. Commit your changes (`git commit -am "Added something"`).
|
68
|
+
4. Push to the branch (`git push origin 20101220-my-change`).
|
69
|
+
5. Create an [Issue][1] with a link to your branch.
|
70
|
+
6. Enjoy a refreshing Diet Coke and wait.
|
71
|
+
|
72
|
+
|
73
|
+
Copyright
|
74
|
+
---------
|
75
|
+
|
76
|
+
Copyright (c) 2010 [Martin Kozák][3]. See `LICENSE.txt` for
|
77
|
+
further details.
|
78
|
+
|
79
|
+
[1]: http://github.com/martinkozak/rb.rotate/issues
|
80
|
+
[2]: http://www.yaml.org/
|
81
|
+
[3]: http://www.martinkozak.net/
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler'
|
4
|
+
begin
|
5
|
+
Bundler.setup(:default, :development)
|
6
|
+
rescue Bundler::BundlerError => e
|
7
|
+
$stderr.puts e.message
|
8
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
9
|
+
exit e.status_code
|
10
|
+
end
|
11
|
+
require 'rake'
|
12
|
+
|
13
|
+
require 'jeweler'
|
14
|
+
Jeweler::Tasks.new do |gem|
|
15
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
16
|
+
gem.name = "rb.rotate"
|
17
|
+
gem.homepage = "http://github.com/martinkozak/rb.rotate"
|
18
|
+
gem.license = "MIT"
|
19
|
+
gem.summary = %Q{More modern alternative to 'logrotate' with more features and less limitations.}
|
20
|
+
gem.description = %Q{An alternative to classical 'logrotate' tool. It implements very similar functionallity, features openess and flexibility of the scripting environment and removes some most known 'logrotate' limitations.}
|
21
|
+
gem.email = "martinkozak@martinkozak.net"
|
22
|
+
gem.authors = ["Martin Kozák"]
|
23
|
+
gem.executables = ["rb.rotate"]
|
24
|
+
gem.post_install_message = "\nINSTALLATION DONE!\nFor remaining part of installation run 'rb.rotate install'\nand then eventually setup running the 'rb.rotate' by cron.\n\nBe warn, it's still BETA VERSION.\n\n"
|
25
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
26
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
27
|
+
# gem.add_runtime_dependency 'sys-uname', '>= 0.8.5'
|
28
|
+
# gem.add_runtime_dependency 'pony', '>= 1.1'
|
29
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
30
|
+
end
|
31
|
+
Jeweler::RubygemsDotOrgTasks.new
|
32
|
+
|
33
|
+
require 'rake/rdoctask'
|
34
|
+
Rake::RDocTask.new do |rdoc|
|
35
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
36
|
+
|
37
|
+
rdoc.rdoc_dir = 'rdoc'
|
38
|
+
rdoc.title = "rb.rotate #{version}"
|
39
|
+
rdoc.rdoc_files.include('README*')
|
40
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
41
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/rb.rotate
ADDED
data/lib/rb.rotate.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "rb.rotate/dispatcher"
|
3
|
+
|
4
|
+
module RbRotate
|
5
|
+
|
6
|
+
##
|
7
|
+
# Runs the application.
|
8
|
+
#
|
9
|
+
|
10
|
+
def self.run!
|
11
|
+
Dispatcher::new::run!
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Installs the application.
|
16
|
+
#
|
17
|
+
|
18
|
+
def self.install!
|
19
|
+
Dispatcher::new::install!
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Prints out the system name.
|
24
|
+
#
|
25
|
+
|
26
|
+
def self.sysname!
|
27
|
+
Dispatcher::new::sysname!
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,312 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
require "rb.rotate/directory"
|
5
|
+
|
6
|
+
module RbRotate
|
7
|
+
|
8
|
+
##
|
9
|
+
# Directory configuration.
|
10
|
+
#
|
11
|
+
|
12
|
+
class DirectoryConfiguration
|
13
|
+
|
14
|
+
##
|
15
|
+
# Holds currently selected directory section.
|
16
|
+
#
|
17
|
+
|
18
|
+
@directory
|
19
|
+
|
20
|
+
##
|
21
|
+
# Holds configuration data.
|
22
|
+
#
|
23
|
+
|
24
|
+
@data
|
25
|
+
|
26
|
+
##
|
27
|
+
# Constructor.
|
28
|
+
#
|
29
|
+
# Expects file path and directory section specification
|
30
|
+
# as symbol.
|
31
|
+
#
|
32
|
+
|
33
|
+
def initialize(directory)
|
34
|
+
# TODO: this should handle configuration inheritance
|
35
|
+
@directory = directory
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Returns configuration file setting.
|
40
|
+
#
|
41
|
+
|
42
|
+
def configuration
|
43
|
+
Configuration::get
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Returns configuration data.
|
48
|
+
#
|
49
|
+
|
50
|
+
def data
|
51
|
+
if @data.nil?
|
52
|
+
data = self.configuration[:dirs][@directory]
|
53
|
+
@data = { }
|
54
|
+
|
55
|
+
# Converts strings to symbols
|
56
|
+
data.each_pair do |key, value|
|
57
|
+
@data[key.to_sym] = value
|
58
|
+
end
|
59
|
+
|
60
|
+
self.handle_inheritance!
|
61
|
+
end
|
62
|
+
|
63
|
+
return @data
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# Handles configuration inheritance.
|
68
|
+
#
|
69
|
+
|
70
|
+
def handle_ihneritance!
|
71
|
+
if @data.include? :parent
|
72
|
+
@data.merge! self.class::new(@data[:parent]).data
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# Returns some item.
|
78
|
+
#
|
79
|
+
|
80
|
+
def [](name)
|
81
|
+
self.data[name]
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Handles method calls.
|
86
|
+
#
|
87
|
+
|
88
|
+
def method_missing(name)
|
89
|
+
self[name]
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# General configuration file.
|
96
|
+
#
|
97
|
+
|
98
|
+
class Configuration
|
99
|
+
|
100
|
+
##
|
101
|
+
# Brings self instance. (Class is singletone.)
|
102
|
+
#
|
103
|
+
|
104
|
+
@@self = nil
|
105
|
+
|
106
|
+
##
|
107
|
+
# Holds data.
|
108
|
+
#
|
109
|
+
|
110
|
+
@data
|
111
|
+
|
112
|
+
##
|
113
|
+
# Opens the file. (Shortcut for instance call.)
|
114
|
+
#
|
115
|
+
|
116
|
+
def self.read(file)
|
117
|
+
self::get.read(file)
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# Returns the single instance.
|
122
|
+
#
|
123
|
+
|
124
|
+
def self.get
|
125
|
+
if @@self.nil?
|
126
|
+
@@self = self::new
|
127
|
+
end
|
128
|
+
|
129
|
+
return @@self
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# Traverses through each directory configuration.
|
134
|
+
# (Shortcut for instace call.)
|
135
|
+
|
136
|
+
def self.each_directory(&block)
|
137
|
+
self::get.each_directory(&block)
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Alias for #find_path.
|
142
|
+
#
|
143
|
+
|
144
|
+
def self.find_path(path)
|
145
|
+
self::get.find_path(path)
|
146
|
+
end
|
147
|
+
|
148
|
+
##
|
149
|
+
# Opens the file.
|
150
|
+
#
|
151
|
+
|
152
|
+
def read(file)
|
153
|
+
data = YAML.load(::File.read(file))
|
154
|
+
@data = { }
|
155
|
+
|
156
|
+
# Converts strings to symbols
|
157
|
+
data.each_pair do |name, section|
|
158
|
+
section_data = { }
|
159
|
+
@data[name.to_sym]= section_data
|
160
|
+
|
161
|
+
if section.kind_of? Hash
|
162
|
+
section.each_pair do |key, value|
|
163
|
+
section_data[key.to_sym] = value
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
defaults = YAML.load(::File.read(@data[:paths][:"defaults file"]))
|
169
|
+
|
170
|
+
# Merges with defaults
|
171
|
+
defaults.each_pair do |name, section|
|
172
|
+
name = name.to_sym
|
173
|
+
if not @data.has_key? name
|
174
|
+
@data[name] = value
|
175
|
+
elsif section.kind_of? Hash
|
176
|
+
section.each_pair do |key, value|
|
177
|
+
key = key.to_sym
|
178
|
+
if not @data[name].has_key? key
|
179
|
+
@data[name][key] = value
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# Returns an item.
|
188
|
+
#
|
189
|
+
|
190
|
+
def [](name)
|
191
|
+
@data[name]
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
# Handles method calls.
|
196
|
+
#
|
197
|
+
|
198
|
+
def method_missing(name)
|
199
|
+
self[name]
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Traverses through each directory configuration.
|
204
|
+
#
|
205
|
+
|
206
|
+
def each_directory
|
207
|
+
@data[:dirs].each_pair do |name, dir|
|
208
|
+
yield Directory::new(name, dir)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
##
|
213
|
+
# Looks for directory in configuration.
|
214
|
+
#
|
215
|
+
|
216
|
+
def find_path(path)
|
217
|
+
@data.each_pair do |name, dir|
|
218
|
+
if dir[:directory] == path
|
219
|
+
return Directory::new(name, dir)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
return nil
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
230
|
+
# String extensions
|
231
|
+
|
232
|
+
class String
|
233
|
+
|
234
|
+
TO_TIME_MATCHER = /(\d+)\s*((?:year|month|week|day|hour|minute|second))s?/i
|
235
|
+
|
236
|
+
##
|
237
|
+
# Converts number with units to bytes count.
|
238
|
+
#
|
239
|
+
|
240
|
+
def to_bytes
|
241
|
+
value = self.to_i
|
242
|
+
if value == 0
|
243
|
+
raise Exception::new("Invalid size specification: " << self << ".")
|
244
|
+
end
|
245
|
+
|
246
|
+
exponent = nil
|
247
|
+
case self[-1]
|
248
|
+
when ?M
|
249
|
+
exponent = 2
|
250
|
+
when ?G
|
251
|
+
exponent = 3
|
252
|
+
when ?K
|
253
|
+
exponent = 1
|
254
|
+
when ?0, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9
|
255
|
+
exponent = 0
|
256
|
+
else
|
257
|
+
raise Exception::new("Invalid unit in size specification: " << self << ".")
|
258
|
+
end
|
259
|
+
|
260
|
+
return value * (1024 ** exponent)
|
261
|
+
end
|
262
|
+
|
263
|
+
##
|
264
|
+
# Converts string identifier of time period to seconds.
|
265
|
+
#
|
266
|
+
|
267
|
+
def to_seconds
|
268
|
+
|
269
|
+
period = nil
|
270
|
+
case self.to_sym
|
271
|
+
when :yearly
|
272
|
+
period = "1 year"
|
273
|
+
when :monthly
|
274
|
+
period = "1 month"
|
275
|
+
when :weekly
|
276
|
+
period = "1 week"
|
277
|
+
when :daily
|
278
|
+
period = "1 day"
|
279
|
+
when :hourly
|
280
|
+
period = "1 hour"
|
281
|
+
else
|
282
|
+
period = self
|
283
|
+
end
|
284
|
+
|
285
|
+
matches = period.match(self.class::TO_TIME_MATCHER)
|
286
|
+
if matches.nil?
|
287
|
+
raise Exception::new("Invalid period specification: " << self << ".")
|
288
|
+
end
|
289
|
+
|
290
|
+
count = matches[1].to_i
|
291
|
+
unit = matches[2].to_sym
|
292
|
+
seconds = nil
|
293
|
+
|
294
|
+
case unit
|
295
|
+
when :year
|
296
|
+
seconds = 365 * 24 * 60 * 60
|
297
|
+
when :month
|
298
|
+
seconds = 30 * 24 * 60 * 60
|
299
|
+
when :week
|
300
|
+
seconds = 7 * 24 * 60 * 60
|
301
|
+
when :day
|
302
|
+
seconds = 24 * 60 * 60
|
303
|
+
when :hour
|
304
|
+
seconds = 60 * 60
|
305
|
+
when :second
|
306
|
+
seconds = 1
|
307
|
+
end
|
308
|
+
|
309
|
+
return seconds * count
|
310
|
+
end
|
311
|
+
|
312
|
+
end
|