encbs 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +11 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/bin/encbs +207 -0
- data/encbs.gemspec +76 -0
- data/lib/backup/file_item/base.rb +37 -0
- data/lib/backup/file_item/cloud.rb +91 -0
- data/lib/backup/file_item/local.rb +26 -0
- data/lib/backup/file_item.rb +18 -0
- data/lib/backup/jar.rb +163 -0
- data/lib/backup/timestamp.rb +48 -0
- data/lib/backup.rb +120 -0
- data/lib/crypto.rb +43 -0
- data/lib/helpers.rb +80 -0
- data/test/helper.rb +17 -0
- data/test/test_backup.rb +14 -0
- data/test/test_backup_file_item.rb +41 -0
- data/test/test_backup_timestamp.rb +73 -0
- metadata +144 -0
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Timothy Klim
|
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.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= encbs
|
2
|
+
|
3
|
+
Simple backup system for pushing into cloud.
|
4
|
+
|
5
|
+
== Contributing to encbs
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
+
* Fork the project
|
10
|
+
* Start a feature/bugfix branch
|
11
|
+
* Commit and push until you are happy with your contribution
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2011 Timothy Klim. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "encbs"
|
18
|
+
gem.homepage = "http://github.com/TimothyKlim/encbs"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Simple backup system for pushing into cloud}
|
21
|
+
gem.description = %Q{Simple backup system for pushing into cloud}
|
22
|
+
gem.email = "klimtimothy@gmail.com"
|
23
|
+
gem.authors = ["Timothy Klim"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/test_*.rb'
|
39
|
+
test.verbose = true
|
40
|
+
test.rcov_opts << '--exclude "gems/*"'
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "encbs #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/encbs
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift(File.expand_path("../../lib/", __FILE__))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'yaml'
|
6
|
+
require 'digest'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'openssl'
|
9
|
+
require 'socket'
|
10
|
+
require 'helpers'
|
11
|
+
|
12
|
+
safe_require do
|
13
|
+
require 'slop'
|
14
|
+
require 'fog'
|
15
|
+
require 'progressbar'
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'backup'
|
19
|
+
|
20
|
+
opts = Slop.parse :help => true do
|
21
|
+
on :a, :add, "Add path to backup", true
|
22
|
+
on :b, :bucket, "Set Amazon S3 bucket to backup", true
|
23
|
+
on :k, :key, "Set API key to access Amazon S3", true
|
24
|
+
on :s, :secret, "Set API secret to access Amazon S3", true
|
25
|
+
on :c, :config, "Use config file to upload backup", true #TODO
|
26
|
+
on :colorize, "Colorize print to console"
|
27
|
+
on :d, :date, "Date for backup restore (default: last)", true
|
28
|
+
on :g, :generate, "Generate RSA keys (option: 4096, 2048)", true
|
29
|
+
on :h, :hostname, "Set hostname (default: system)", true
|
30
|
+
on :i, :increment, "Use increment mode for backup (default: false)"
|
31
|
+
on :j, :jar, "Versions of jar (option: hash or path)", true
|
32
|
+
on :t, :token, "RSA Key to encrypt/decrypt backup data", true
|
33
|
+
on :l, :local, "Backup in local directory", true
|
34
|
+
on :list, "List of jars"
|
35
|
+
on :r, :rescue, "Return data from backup (option: jar, path or filter)", true
|
36
|
+
on :t, :to, "Path to recovery (default: /)", true
|
37
|
+
on :v, :verbose, "Verbose mode"
|
38
|
+
|
39
|
+
banner "Usage:\n $ encbs [options]\n\nOptions:"
|
40
|
+
end
|
41
|
+
|
42
|
+
if ARGV.empty?
|
43
|
+
puts opts.help
|
44
|
+
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
|
48
|
+
$PRINT_VERBOSE = opts.verbose?
|
49
|
+
$COLORIZE = opts.colorize?
|
50
|
+
|
51
|
+
#if opts.generate?
|
52
|
+
# puts "Generate 4096 bits RSA keys"
|
53
|
+
# Crypto::create_keys(
|
54
|
+
# File.join(Dir.getwd, "rsa_key"),
|
55
|
+
# File.join(Dir.getwd, "rsa_key.pub")
|
56
|
+
# )
|
57
|
+
# puts "Done!"
|
58
|
+
#
|
59
|
+
# exit
|
60
|
+
#end
|
61
|
+
|
62
|
+
if opts.local?
|
63
|
+
try_create_dir opts[:local]
|
64
|
+
@backup = Backup::Instance.new opts[:local]
|
65
|
+
else
|
66
|
+
[:key, :secret, :bucket].each do |arg|
|
67
|
+
puts_fail "Argument '--#{arg}' should not be empty" if opts[arg].nil?
|
68
|
+
end
|
69
|
+
@backup = Backup::Instance.new(
|
70
|
+
"backups",
|
71
|
+
true,
|
72
|
+
:bucket => opts[:bucket],
|
73
|
+
:key => opts[:key],
|
74
|
+
:secret => opts[:secret]
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
@backup.hostname = opts[:hostname] if opts.hostname?
|
79
|
+
|
80
|
+
if opts.list?
|
81
|
+
jars_list = @backup.jars
|
82
|
+
|
83
|
+
unless jars_list.empty?
|
84
|
+
puts "List of jars:\n"
|
85
|
+
jars_list.keys.sort.each do |key|
|
86
|
+
puts " #{key.dark_green}: #{jars_list[key]}"
|
87
|
+
end
|
88
|
+
else
|
89
|
+
puts "Nothing to listing."
|
90
|
+
end
|
91
|
+
|
92
|
+
exit
|
93
|
+
end
|
94
|
+
|
95
|
+
#TODO: AES or RSA
|
96
|
+
# @backup.key = opts[:key] if opts.key?
|
97
|
+
|
98
|
+
if opts.date?
|
99
|
+
date = opts[:date].split("-")
|
100
|
+
|
101
|
+
unless date.length == 1
|
102
|
+
@start_date = Backup::Timestamp.parse_timestamp date[0]
|
103
|
+
@end_date = Backup::Timestamp.parse_timestamp date[1], true
|
104
|
+
|
105
|
+
puts_fail "Last date less than start date" if start_date > end_date
|
106
|
+
else
|
107
|
+
@start_date = Backup::Timestamp.parse_timestamp date[0]
|
108
|
+
@end_date = Backup::Timestamp.parse_timestamp date[0], true
|
109
|
+
end
|
110
|
+
else
|
111
|
+
@start_date = nil
|
112
|
+
@end_date = Time.now.utc
|
113
|
+
end
|
114
|
+
|
115
|
+
if opts.jar?
|
116
|
+
opts[:jar].split(" ").each do |jar|
|
117
|
+
versions = @backup.jar_versions(jar)
|
118
|
+
|
119
|
+
unless versions.empty?
|
120
|
+
puts "Versions of backup '#{jar}':"
|
121
|
+
|
122
|
+
versions.each do |version|
|
123
|
+
puts " => #{version.dark_green}: #{Backup::Timestamp.to_str(version)}"
|
124
|
+
end
|
125
|
+
else
|
126
|
+
puts "Versions doesn't exists for jar: #{jar}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
exit
|
131
|
+
end
|
132
|
+
|
133
|
+
#TODO: Support rescue option as hash
|
134
|
+
if opts.rescue?
|
135
|
+
paths = opts[:rescue].split(" ")
|
136
|
+
jars_list = @backup.jars
|
137
|
+
|
138
|
+
include_path = lambda {|path| jars_list.keys.include? path}
|
139
|
+
|
140
|
+
jars_hashes = paths.map do |path|
|
141
|
+
path = File.expand_path path
|
142
|
+
|
143
|
+
unless include_path[path] or include_path["#{path}/"]
|
144
|
+
puts_fail "Jar \"#{path}\" not exists."
|
145
|
+
end
|
146
|
+
|
147
|
+
jars_list[path] || jars_list["#{path}/"]
|
148
|
+
end
|
149
|
+
|
150
|
+
if opts.to?
|
151
|
+
@to = File.expand_path opts[:to]
|
152
|
+
try_create_dir @to
|
153
|
+
else
|
154
|
+
@to = "/"
|
155
|
+
end
|
156
|
+
|
157
|
+
#TODO: Confirm flag
|
158
|
+
#TODO: Empty destination directory
|
159
|
+
|
160
|
+
@index = {}
|
161
|
+
|
162
|
+
jars_hashes.each do |hash|
|
163
|
+
versions = @backup.jar_versions(hash)
|
164
|
+
# puts "Versions: #{versions}" #FIXME
|
165
|
+
|
166
|
+
last_version = Backup::Timestamp.last_from(versions, @end_date, @start_date)
|
167
|
+
|
168
|
+
unless last_version.nil?
|
169
|
+
@index[hash] = last_version
|
170
|
+
else
|
171
|
+
error_path = "#{Backup::Jar.hash_to_path(@backup.root_path, hash)}"
|
172
|
+
start_date = Backup::Timestamp.to_s(@start_date)
|
173
|
+
end_date = Backup::Timestamp.to_s(@end_date)
|
174
|
+
|
175
|
+
unless @end_date == @start_date
|
176
|
+
puts_fail "Nothing found for #{error_path}, between date: #{start_date} - #{end_date}"
|
177
|
+
else
|
178
|
+
puts_fail "Nothing found for #{error_path}, for date: #{end_date}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
@index.each do |hash, timestamp|
|
184
|
+
# puts "#{hash}: #{timestamp}" #FIXME
|
185
|
+
@backup.restore_jar_to(hash, timestamp, @to)
|
186
|
+
end
|
187
|
+
|
188
|
+
puts "Done!".green
|
189
|
+
exit
|
190
|
+
end
|
191
|
+
|
192
|
+
if opts.add?
|
193
|
+
paths = opts[:add].split(" ")
|
194
|
+
|
195
|
+
paths = paths.map do |path|
|
196
|
+
path = File.expand_path path
|
197
|
+
puts_fail "Path \"#{path}\" not exists." unless File.exists? path
|
198
|
+
|
199
|
+
path
|
200
|
+
end
|
201
|
+
|
202
|
+
paths.each do |path|
|
203
|
+
@backup.create! path, opts.increment?
|
204
|
+
end
|
205
|
+
|
206
|
+
puts "Done!".green
|
207
|
+
end
|
data/encbs.gemspec
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{encbs}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Timothy Klim"]
|
12
|
+
s.date = %q{2011-05-11}
|
13
|
+
s.default_executable = %q{encbs}
|
14
|
+
s.description = %q{Simple backup system for pushing into cloud}
|
15
|
+
s.email = %q{klimtimothy@gmail.com}
|
16
|
+
s.executables = ["encbs"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
"Gemfile",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"bin/encbs",
|
28
|
+
"encbs.gemspec",
|
29
|
+
"lib/backup.rb",
|
30
|
+
"lib/backup/file_item.rb",
|
31
|
+
"lib/backup/file_item/base.rb",
|
32
|
+
"lib/backup/file_item/cloud.rb",
|
33
|
+
"lib/backup/file_item/local.rb",
|
34
|
+
"lib/backup/jar.rb",
|
35
|
+
"lib/backup/timestamp.rb",
|
36
|
+
"lib/crypto.rb",
|
37
|
+
"lib/helpers.rb",
|
38
|
+
"test/helper.rb",
|
39
|
+
"test/test_backup.rb",
|
40
|
+
"test/test_backup_file_item.rb",
|
41
|
+
"test/test_backup_timestamp.rb"
|
42
|
+
]
|
43
|
+
s.homepage = %q{http://github.com/TimothyKlim/encbs}
|
44
|
+
s.licenses = ["MIT"]
|
45
|
+
s.require_paths = ["lib"]
|
46
|
+
s.rubygems_version = %q{1.6.2}
|
47
|
+
s.summary = %q{Simple backup system for pushing into cloud}
|
48
|
+
|
49
|
+
if s.respond_to? :specification_version then
|
50
|
+
s.specification_version = 3
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
53
|
+
s.add_runtime_dependency(%q<fog>, [">= 0"])
|
54
|
+
s.add_runtime_dependency(%q<slop>, [">= 0"])
|
55
|
+
s.add_runtime_dependency(%q<ruby-progressbar>, [">= 0"])
|
56
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
57
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.0"])
|
58
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<fog>, [">= 0"])
|
61
|
+
s.add_dependency(%q<slop>, [">= 0"])
|
62
|
+
s.add_dependency(%q<ruby-progressbar>, [">= 0"])
|
63
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
64
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
|
65
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
66
|
+
end
|
67
|
+
else
|
68
|
+
s.add_dependency(%q<fog>, [">= 0"])
|
69
|
+
s.add_dependency(%q<slop>, [">= 0"])
|
70
|
+
s.add_dependency(%q<ruby-progressbar>, [">= 0"])
|
71
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
72
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
|
73
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Backup
|
2
|
+
module FileItem
|
3
|
+
class Base
|
4
|
+
def semantic_path(path)
|
5
|
+
if Dir.exists? path
|
6
|
+
path += '/'
|
7
|
+
else
|
8
|
+
path
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def stat(file, timestamp = nil)
|
13
|
+
files = {}
|
14
|
+
|
15
|
+
stat = File.new(file).stat
|
16
|
+
files[file] = {
|
17
|
+
:uid => stat.uid,
|
18
|
+
:gid => stat.gid,
|
19
|
+
:mode => stat.mode
|
20
|
+
}
|
21
|
+
files[file][:timestamp] = timestamp if timestamp
|
22
|
+
|
23
|
+
unless Dir.exists?(file)
|
24
|
+
files[file][:checksum] = Digest::MD5.hexdigest(File.open(file).read)
|
25
|
+
end
|
26
|
+
|
27
|
+
files
|
28
|
+
rescue Exception => e
|
29
|
+
STDERR.puts e
|
30
|
+
end
|
31
|
+
|
32
|
+
def file_hash(file)
|
33
|
+
Digest::MD5.hexdigest file
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'backup/file_item/base'
|
2
|
+
|
3
|
+
module Backup
|
4
|
+
module FileItem
|
5
|
+
class Cloud < Backup::FileItem::Base
|
6
|
+
attr_reader :key, :secret, :backet, :provider
|
7
|
+
|
8
|
+
def initialize(args = {})
|
9
|
+
puts_fail "Empty hash in Cloud initialize method" if args.empty?
|
10
|
+
|
11
|
+
[:key, :secret, :bucket].each do |arg|
|
12
|
+
puts_fail "'#{arg.to_s.green}' should not be empty" if args[arg].nil?
|
13
|
+
instance_eval %{@#{arg} = args[:#{arg}]}
|
14
|
+
end
|
15
|
+
|
16
|
+
try_to_connect_with_cloud
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_directory_once(*directories)
|
20
|
+
# Nothing happen
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_file_once(file, data)
|
24
|
+
try_to_work_with_cloud do
|
25
|
+
@directory.files.create(
|
26
|
+
:key => delete_slashes(file),
|
27
|
+
:body => data
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def read_file(file)
|
33
|
+
try_to_work_with_cloud do
|
34
|
+
file = delete_slashes(file)
|
35
|
+
remote_file = @directory.files.get(file)
|
36
|
+
remote_file.body if remote_file
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def dir(path, mask = "*")
|
41
|
+
path = delete_slashes(path)
|
42
|
+
mask = mask.gsub('.', '\.').gsub('*', '[^\/]')
|
43
|
+
|
44
|
+
files = @directory.files.all(
|
45
|
+
:prefix => path,
|
46
|
+
:max_keys => 30_000
|
47
|
+
).map &:key
|
48
|
+
|
49
|
+
files.map do |item|
|
50
|
+
match = item.match(/^#{path}\/([^\/]+#{mask}).*$/)
|
51
|
+
match[1] if match
|
52
|
+
end.compact.uniq
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def delete_slashes(str)
|
58
|
+
str.chop! if str =~ /\/$/
|
59
|
+
str = str[1, str.length] if str =~ /^\//
|
60
|
+
str
|
61
|
+
end
|
62
|
+
|
63
|
+
def try_to_work_with_cloud(&block)
|
64
|
+
begin
|
65
|
+
yield
|
66
|
+
rescue Exception => e
|
67
|
+
try_to_connect_with_cloud
|
68
|
+
|
69
|
+
yield
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def try_to_connect_with_cloud
|
74
|
+
begin
|
75
|
+
@connection = ::Fog::Storage.new(
|
76
|
+
:provider => 'AWS',
|
77
|
+
:aws_secret_access_key => @secret,
|
78
|
+
:aws_access_key_id => @key
|
79
|
+
)
|
80
|
+
|
81
|
+
@directory = @connection.directories.get(@bucket)
|
82
|
+
rescue Exception => e
|
83
|
+
puts_verbose e.message
|
84
|
+
puts_fail "403 Forbidden"
|
85
|
+
end
|
86
|
+
|
87
|
+
puts_fail "Bucket '#{@bucket}' is not exists." if @directory.nil?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'backup/file_item/base'
|
2
|
+
|
3
|
+
module Backup
|
4
|
+
module FileItem
|
5
|
+
class Local < Backup::FileItem::Base
|
6
|
+
def create_directory_once(*directories)
|
7
|
+
directories.each do |path|
|
8
|
+
FileUtils.mkdir_p(path) unless Dir.exists?(path)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_file_once(file, data)
|
13
|
+
date = date.read if date.is_a? File
|
14
|
+
File.open(file, "w").puts(data) unless File.exists?(file)
|
15
|
+
end
|
16
|
+
|
17
|
+
def read_file(file)
|
18
|
+
open(file).read if File.exists? file
|
19
|
+
end
|
20
|
+
|
21
|
+
def dir(path, mask = "*")
|
22
|
+
Dir["#{path}/#{mask}"]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'backup/file_item/local'
|
2
|
+
require 'backup/file_item/cloud'
|
3
|
+
|
4
|
+
module Backup
|
5
|
+
module FileItem
|
6
|
+
def self.for(type, *args)
|
7
|
+
case type
|
8
|
+
when :cloud
|
9
|
+
Backup::FileItem::Cloud.new *args
|
10
|
+
when :local
|
11
|
+
Backup::FileItem::Local.new
|
12
|
+
|
13
|
+
else
|
14
|
+
puts_fail "Unknown '#{type}' type for FileItem"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/backup/jar.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
module Backup
|
2
|
+
class Jar
|
3
|
+
def initialize(file_item, root_path, local_path)
|
4
|
+
@root_path = root_path
|
5
|
+
@local_path = local_path
|
6
|
+
@timestamp = Backup::Timestamp.create
|
7
|
+
@file_item = file_item
|
8
|
+
end
|
9
|
+
|
10
|
+
def jar_hash
|
11
|
+
Digest::MD5.hexdigest(@local_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def save(increment = false)
|
15
|
+
unless increment
|
16
|
+
@local_files = hash_local_files
|
17
|
+
else
|
18
|
+
@local_files = {}
|
19
|
+
current_files = hash_local_files
|
20
|
+
|
21
|
+
last_timestamp = Jar.jar_versions(@root_path, jar_hash, true).last
|
22
|
+
|
23
|
+
if last_timestamp.nil?
|
24
|
+
puts_fail "First you must create a full backup for #{@local_path.dark_green}"
|
25
|
+
end
|
26
|
+
|
27
|
+
last_index = Jar.fetch_index_for(@root_path, jar_hash, last_timestamp)
|
28
|
+
|
29
|
+
current_files.keys.each do |file|
|
30
|
+
@local_files[file] = current_files[file]
|
31
|
+
|
32
|
+
#TODO: Cut to a new method {
|
33
|
+
current = current_files[file].dup
|
34
|
+
current.delete(:timestamp)
|
35
|
+
|
36
|
+
unless last_index[file].nil?
|
37
|
+
backup = last_index[file].dup
|
38
|
+
backup.delete(:timestamp)
|
39
|
+
|
40
|
+
if (current == backup) or
|
41
|
+
(!current[:checksum].nil? and current[:checksum] == backup[:checksum])
|
42
|
+
|
43
|
+
@local_files[file][:timestamp] = last_index[file][:timestamp]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
# }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@file_item.create_directory_once meta_jars_path, meta_jar_path, jar_data_path
|
51
|
+
@file_item.create_file_once(
|
52
|
+
"#{meta_jars_path}/#{jar_hash}",
|
53
|
+
@file_item.semantic_path(@local_path)
|
54
|
+
)
|
55
|
+
@file_item.create_file_once(
|
56
|
+
"#{meta_jar_path}/#{@timestamp}.yml",
|
57
|
+
@local_files.to_yaml
|
58
|
+
)
|
59
|
+
|
60
|
+
if @file_item.is_a? Backup::FileItem::Cloud
|
61
|
+
pbar = ProgressBar.new(
|
62
|
+
"Uploading",
|
63
|
+
@local_files.keys.count
|
64
|
+
)
|
65
|
+
else
|
66
|
+
pbar = ProgressBar.new(
|
67
|
+
"Copying",
|
68
|
+
@local_files.keys.count
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
pbar.bar_mark = '*'
|
73
|
+
|
74
|
+
@local_files.keys.each do |file|
|
75
|
+
unless Dir.exists?(file)
|
76
|
+
@file_item.create_file_once "#{jar_data_path}/#{@file_item.file_hash file}",
|
77
|
+
File.open(file)
|
78
|
+
pbar.inc
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
pbar.finish
|
83
|
+
end
|
84
|
+
|
85
|
+
def hash_local_files
|
86
|
+
files = {}
|
87
|
+
|
88
|
+
puts_verbose "Create index for #{@local_path.dark_green}"
|
89
|
+
|
90
|
+
if Dir.exists? @local_path
|
91
|
+
matches = Dir.glob(File.join(@local_path, "/**/*"), File::FNM_DOTMATCH)
|
92
|
+
|
93
|
+
matches = matches.select do |match|
|
94
|
+
match[/\/..$/].nil? and match[/\/.$/].nil?
|
95
|
+
end
|
96
|
+
|
97
|
+
matches << @local_path
|
98
|
+
|
99
|
+
matches.each do |match|
|
100
|
+
files.merge!(@file_item.stat(match, @timestamp))
|
101
|
+
end
|
102
|
+
else
|
103
|
+
files = @file_item.stat(@local_path, @timestamp)
|
104
|
+
end
|
105
|
+
|
106
|
+
files
|
107
|
+
end
|
108
|
+
|
109
|
+
class << self
|
110
|
+
def hash_to_path(file_item, root_path, hash)
|
111
|
+
file_item.read_file("#{root_path}/meta/jars/#{hash}").chomp
|
112
|
+
rescue Errno::ENOENT
|
113
|
+
""
|
114
|
+
end
|
115
|
+
|
116
|
+
def all(file_item, root_path)
|
117
|
+
hashes = file_item.dir("#{root_path}/meta/jars").map do |backup|
|
118
|
+
backup[/[0-9a-z]{32}$/]
|
119
|
+
end.compact.sort
|
120
|
+
|
121
|
+
result = {}
|
122
|
+
|
123
|
+
hashes.each do |hash|
|
124
|
+
jar_local_path = Jar.hash_to_path(file_item, root_path, hash)
|
125
|
+
result[jar_local_path] = hash unless jar_local_path.empty?
|
126
|
+
end
|
127
|
+
|
128
|
+
result
|
129
|
+
end
|
130
|
+
|
131
|
+
def jar_versions(file_item, root_path, jar, hash = false)
|
132
|
+
jar = jar.chop if jar =~ /\/$/
|
133
|
+
jar = Digest::MD5.hexdigest(jar) unless hash
|
134
|
+
|
135
|
+
meta_jar_path = "#{root_path}/meta/#{jar}"
|
136
|
+
|
137
|
+
file_item.dir(meta_jar_path, "*.yml").map do |file|
|
138
|
+
match = file.match(/^\/?([0-9]{12}).yml$/)
|
139
|
+
match[1] if match
|
140
|
+
end.compact.sort
|
141
|
+
end
|
142
|
+
|
143
|
+
def fetch_index_for(file_item, root_path, hash, timestamp)
|
144
|
+
index = file_item.read_file "#{root_path}/meta/#{hash}/#{timestamp}.yml"
|
145
|
+
YAML::load(index) unless index.nil?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def meta_jars_path
|
152
|
+
"#{@root_path}/meta/jars"
|
153
|
+
end
|
154
|
+
|
155
|
+
def meta_jar_path
|
156
|
+
"#{@root_path}/meta/#{jar_hash}"
|
157
|
+
end
|
158
|
+
|
159
|
+
def jar_data_path
|
160
|
+
"#{@root_path}/#{jar_hash}/#{@timestamp}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Backup
|
2
|
+
class Timestamp
|
3
|
+
def self.parse_timestamp(version, last = false)
|
4
|
+
version = version.gsub(".", "").gsub(" ", "").gsub(":", "")
|
5
|
+
|
6
|
+
puts_fail "Invalid date format: #{version}" unless version.match /[0-9]{6,}/
|
7
|
+
|
8
|
+
year, month, day, hour, min, sec =
|
9
|
+
version.split(/([0-9]{2})/).map do |date|
|
10
|
+
date.to_i unless date.empty?
|
11
|
+
end.compact
|
12
|
+
|
13
|
+
if last
|
14
|
+
hour = 23 if hour.nil?
|
15
|
+
min = 59 if min.nil?
|
16
|
+
sec = 59 if sec.nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
time = Time.new(year + 2000, month, day, hour, min, sec, 0)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.last_from(list, end_date, start_date = nil)
|
23
|
+
list.sort.reverse.find do |version|
|
24
|
+
version = Backup::Timestamp.parse_timestamp version
|
25
|
+
|
26
|
+
unless start_date.nil?
|
27
|
+
version >= start_date and version <= end_date
|
28
|
+
else
|
29
|
+
version <= end_date
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.create(time = nil)
|
35
|
+
time = time.nil? ? Time.now : time
|
36
|
+
|
37
|
+
time.utc.strftime "%y%m%d%H%M%S"
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.to_str(version)
|
41
|
+
to_s parse_timestamp(version)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.to_s(time)
|
45
|
+
time.strftime "%y.%m.%d %H:%M:%S" if time.is_a? Time
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/backup.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'backup/file_item'
|
2
|
+
require 'backup/timestamp'
|
3
|
+
require 'backup/jar'
|
4
|
+
require 'crypto'
|
5
|
+
|
6
|
+
module Backup
|
7
|
+
class Instance
|
8
|
+
attr_reader :root_path, :timestamp, :hostname, :file_item
|
9
|
+
|
10
|
+
def initialize(root_path, cloud = false, *args)
|
11
|
+
if cloud
|
12
|
+
@file_item = Backup::FileItem.for :cloud, *args
|
13
|
+
else
|
14
|
+
@file_item = Backup::FileItem.for :local
|
15
|
+
end
|
16
|
+
|
17
|
+
@hostname = Socket.gethostname
|
18
|
+
@root_path = "#{root_path}/#{@hostname}"
|
19
|
+
@timestamp = Backup::Timestamp.create
|
20
|
+
end
|
21
|
+
|
22
|
+
def hostname=(host)
|
23
|
+
@hostname = host
|
24
|
+
@root_path = "#{root_path}/#{@hostname}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def key=(path)
|
28
|
+
@key = open(path).read
|
29
|
+
end
|
30
|
+
|
31
|
+
def create!(local_path, increment = false)
|
32
|
+
jar = Jar.new(@file_item, @root_path, local_path)
|
33
|
+
jar.save(increment)
|
34
|
+
end
|
35
|
+
|
36
|
+
def jars
|
37
|
+
Jar.all(@file_item, @root_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def jar_versions(jar)
|
41
|
+
Jar.jar_versions(@file_item, @root_path, jar, !!jar[/^[0-9a-z]{32}$/])
|
42
|
+
end
|
43
|
+
|
44
|
+
def restore_jar_to(hash, timestamp, to)
|
45
|
+
files = Jar.fetch_index_for(@file_item, @root_path, hash, timestamp)
|
46
|
+
|
47
|
+
files.keys.sort.each do |file|
|
48
|
+
restore_file = File.join(to, file)
|
49
|
+
current_file = files[file]
|
50
|
+
|
51
|
+
if current_file[:checksum].nil?
|
52
|
+
try_create_dir restore_file
|
53
|
+
|
54
|
+
File.chmod current_file[:mode], restore_file
|
55
|
+
File.chown current_file[:uid], current_file[:gid], restore_file
|
56
|
+
|
57
|
+
file_ok = @file_item.stat(restore_file)[restore_file]
|
58
|
+
|
59
|
+
check_mode(restore_file, file_ok[:mode], current_file[:mode])
|
60
|
+
check_rights(
|
61
|
+
restore_file,
|
62
|
+
file_ok[:uid],
|
63
|
+
file_ok[:gid],
|
64
|
+
current_file[:uid],
|
65
|
+
current_file[:gid]
|
66
|
+
)
|
67
|
+
else
|
68
|
+
try_create_dir(File.dirname restore_file)
|
69
|
+
|
70
|
+
begin
|
71
|
+
File.open(restore_file, "w") do |f|
|
72
|
+
f.chmod current_file[:mode]
|
73
|
+
f.chown current_file[:uid], current_file[:gid]
|
74
|
+
|
75
|
+
remote_path = "#{@root_path}/#{hash}/#{current_file[:timestamp]}"
|
76
|
+
remote_path += "/#{@file_item.file_hash file}"
|
77
|
+
|
78
|
+
data = @file_item.read_file remote_path
|
79
|
+
f.puts data
|
80
|
+
end
|
81
|
+
|
82
|
+
file_ok = @file_item.stat(restore_file)[restore_file]
|
83
|
+
|
84
|
+
check_mode(restore_file, file_ok[:mode], current_file[:mode])
|
85
|
+
check_rights(
|
86
|
+
restore_file,
|
87
|
+
file_ok[:uid],
|
88
|
+
file_ok[:gid],
|
89
|
+
current_file[:uid],
|
90
|
+
current_file[:gid]
|
91
|
+
)
|
92
|
+
rescue Errno::EACCES
|
93
|
+
puts_fail "Permission denied for #{restore_file.dark_green}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.fetch_versions_of_backup(path)
|
102
|
+
Dir["#{path}/*"].map do |backup|
|
103
|
+
backup.match(/[0-9]{12}$/)[0] if backup.match(/[0-9]{12}$/)
|
104
|
+
end.compact.sort
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.aes(command, key, data)
|
108
|
+
aes = OpenSSL::Cipher::Cipher.new('aes-256-cbc').send(command)
|
109
|
+
aes.key = key
|
110
|
+
aes.update(data) << aes.final
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.encrypt_data(key, data)
|
114
|
+
Backup::aes(:encrypt, key, data) unless data.empty?
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.decrypt_data(key, data)
|
118
|
+
Backup::aes(:decrypt, key, data)
|
119
|
+
end
|
120
|
+
end
|
data/lib/crypto.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Crypto
|
5
|
+
|
6
|
+
def self.create_keys(priv = "rsa_key", pub = "#{priv}.pub", bits = 4096)
|
7
|
+
private_key = OpenSSL::PKey::RSA.new(bits)
|
8
|
+
File.open(priv, "w+") { |fp| fp << private_key.to_s }
|
9
|
+
File.open(pub, "w+") { |fp| fp << private_key.public_key.to_s }
|
10
|
+
private_key
|
11
|
+
end
|
12
|
+
|
13
|
+
class Key
|
14
|
+
def initialize(data)
|
15
|
+
@public = (data =~ /^-----BEGIN (RSA|DSA) PRIVATE KEY-----$/).nil?
|
16
|
+
@key = OpenSSL::PKey::RSA.new(data)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.from_file(filename)
|
20
|
+
self.new File.read( filename )
|
21
|
+
end
|
22
|
+
|
23
|
+
def encrypt(text)
|
24
|
+
@key.send("#{key_type}_encrypt", text)
|
25
|
+
end
|
26
|
+
|
27
|
+
def decrypt(text)
|
28
|
+
@key.send("#{key_type}_decrypt", text)
|
29
|
+
end
|
30
|
+
|
31
|
+
def private?
|
32
|
+
!@public
|
33
|
+
end
|
34
|
+
|
35
|
+
def public?
|
36
|
+
@public
|
37
|
+
end
|
38
|
+
|
39
|
+
def key_type
|
40
|
+
@public ? :public : :private
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/helpers.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
def puts_fail(msg)
|
2
|
+
STDERR.puts "#{"Error: ".red}#{msg}"
|
3
|
+
|
4
|
+
exit msg.length
|
5
|
+
end
|
6
|
+
|
7
|
+
def puts_verbose(msg)
|
8
|
+
puts msg if $PRINT_VERBOSE
|
9
|
+
end
|
10
|
+
|
11
|
+
def print_verbose(msg)
|
12
|
+
print msg if $PRINT_VERBOSE
|
13
|
+
end
|
14
|
+
|
15
|
+
def safe_require(&block)
|
16
|
+
yield
|
17
|
+
rescue Exception => e
|
18
|
+
puts_fail %Q{This script use these gems: fog, slop.
|
19
|
+
Make sure that you have them all.
|
20
|
+
If you don't have, you may install them: $ gem install fog slop ruby-progressbar
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def try_create_dir(dir)
|
25
|
+
begin
|
26
|
+
FileUtils.mkdir_p dir unless Dir.exists? dir
|
27
|
+
rescue Errno::EACCES
|
28
|
+
puts_fail "Permission denied for #{dir.dark_green}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def check_mode(file, first, second)
|
33
|
+
unless first == second
|
34
|
+
puts_fail "Permission wasn't changed for #{file.dark_green}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def check_rights(file, first_uid, first_gid, second_uid, second_gid)
|
39
|
+
unless first_uid == second_uid and first_gid == second_gid
|
40
|
+
puts_fail "Group and user wasn't change for #{file.dark_green}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class String
|
45
|
+
def red
|
46
|
+
colorize(self, "\e[1m\e[31m")
|
47
|
+
end
|
48
|
+
|
49
|
+
def green
|
50
|
+
colorize(self, "\e[1m\e[32m")
|
51
|
+
end
|
52
|
+
|
53
|
+
def dark_green
|
54
|
+
colorize(self, "\e[32m")
|
55
|
+
end
|
56
|
+
|
57
|
+
def yellow
|
58
|
+
colorize(self, "\e[1m\e[33m")
|
59
|
+
end
|
60
|
+
|
61
|
+
def blue
|
62
|
+
colorize(self, "\e[1m\e[34m")
|
63
|
+
end
|
64
|
+
|
65
|
+
def dark_blue
|
66
|
+
colorize(self, "\e[34m")
|
67
|
+
end
|
68
|
+
|
69
|
+
def pur
|
70
|
+
colorize(self, "\e[1m\e[35m")
|
71
|
+
end
|
72
|
+
|
73
|
+
def colorize(text, color_code)
|
74
|
+
if $COLORIZE
|
75
|
+
"#{color_code}#{text}\e[0m"
|
76
|
+
else
|
77
|
+
text
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path("../../lib/", __FILE__))
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'yaml'
|
5
|
+
require 'digest'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'openssl'
|
8
|
+
require 'socket'
|
9
|
+
require 'helpers'
|
10
|
+
require 'progressbar'
|
11
|
+
require 'test/unit'
|
12
|
+
|
13
|
+
require 'backup'
|
14
|
+
|
15
|
+
def puts_fail msg
|
16
|
+
raise msg
|
17
|
+
end
|
data/test/test_backup.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.expand_path("../helper.rb", __FILE__)
|
2
|
+
|
3
|
+
class TestBackup < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@backup = Backup::Instance.new(
|
6
|
+
File.expand_path("../fixtures/backups", __FILE__),
|
7
|
+
false
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_backup_attributes
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
class BackupFileItemTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@file_item = Backup::FileItem.for :local
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_semantic_path
|
9
|
+
assert_equal __FILE__, @file_item.semantic_path(__FILE__)
|
10
|
+
assert_equal File.dirname(__FILE__) + '/',
|
11
|
+
@file_item.semantic_path(File.dirname(__FILE__))
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_file_stat
|
15
|
+
file = @file_item.stat(
|
16
|
+
__FILE__,
|
17
|
+
Backup::Timestamp.create
|
18
|
+
)
|
19
|
+
key = file.keys.first
|
20
|
+
|
21
|
+
assert_not_nil file[key][:uid]
|
22
|
+
assert_not_nil file[key][:gid]
|
23
|
+
assert_not_nil file[key][:mode]
|
24
|
+
assert_not_nil file[key][:checksum]
|
25
|
+
assert_not_nil file[key][:timestamp]
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_directory_stat
|
29
|
+
file = @file_item.stat(
|
30
|
+
File.dirname(__FILE__),
|
31
|
+
Backup::Timestamp.create
|
32
|
+
)
|
33
|
+
key = file.keys.first
|
34
|
+
|
35
|
+
assert_not_nil file[key][:uid]
|
36
|
+
assert_not_nil file[key][:gid]
|
37
|
+
assert_not_nil file[key][:mode]
|
38
|
+
assert_nil file[key][:checksum]
|
39
|
+
assert_not_nil file[key][:timestamp]
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require File.expand_path("../helper", __FILE__)
|
2
|
+
|
3
|
+
class BackupTimestampTest < Test::Unit::TestCase
|
4
|
+
def test_parse_timestamp
|
5
|
+
assert_equal Backup::Timestamp.parse_timestamp("110102201130"),
|
6
|
+
Time.new(2011, 01, 02, 20, 11, 30, 0)
|
7
|
+
|
8
|
+
assert_equal Backup::Timestamp.parse_timestamp("11.01.02. 20:11:30"),
|
9
|
+
Time.new(2011, 01, 02, 20, 11, 30, 0)
|
10
|
+
|
11
|
+
assert_equal Backup::Timestamp.parse_timestamp("1101022011"),
|
12
|
+
Time.new(2011, 01, 02, 20, 11, 0, 0)
|
13
|
+
assert_equal Backup::Timestamp.parse_timestamp("11010220"),
|
14
|
+
Time.new(2011, 01, 02, 20, 0, 0, 0)
|
15
|
+
assert_equal Backup::Timestamp.parse_timestamp("110102"),
|
16
|
+
Time.new(2011, 01, 02, 0, 0, 0, 0)
|
17
|
+
|
18
|
+
assert_equal Backup::Timestamp.parse_timestamp("1101022011", true),
|
19
|
+
Time.new(2011, 01, 02, 20, 11, 59, 0)
|
20
|
+
assert_equal Backup::Timestamp.parse_timestamp("11010220", true),
|
21
|
+
Time.new(2011, 01, 02, 20, 59, 59, 0)
|
22
|
+
assert_equal Backup::Timestamp.parse_timestamp("110102", true),
|
23
|
+
Time.new(2011, 01, 02, 23, 59, 59, 0)
|
24
|
+
|
25
|
+
assert_not_equal Backup::Timestamp.parse_timestamp("110102201130"),
|
26
|
+
Time.new(2011, 01, 02, 20, 11, 31, 0)
|
27
|
+
|
28
|
+
assert_raise(RuntimeError) { Backup::Timestamp.parse_timestamp("11d10.,130") }
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_last_from
|
32
|
+
timestamps = ["110130090812", "110130103412", "110106121234"]
|
33
|
+
|
34
|
+
assert_equal("110130103412",
|
35
|
+
Backup::Timestamp.last_from(timestamps,
|
36
|
+
Backup::Timestamp.parse_timestamp("110130", true)))
|
37
|
+
assert_equal("110130090812",
|
38
|
+
Backup::Timestamp.last_from(timestamps,
|
39
|
+
Backup::Timestamp.parse_timestamp("11013009", true)))
|
40
|
+
assert_equal("110106121234",
|
41
|
+
Backup::Timestamp.last_from(timestamps,
|
42
|
+
Backup::Timestamp.parse_timestamp("110106", true)))
|
43
|
+
|
44
|
+
assert_equal("110130103412",
|
45
|
+
Backup::Timestamp.last_from(timestamps,
|
46
|
+
Backup::Timestamp.parse_timestamp("110130", true),
|
47
|
+
Backup::Timestamp.parse_timestamp("110130")))
|
48
|
+
assert_equal("110106121234",
|
49
|
+
Backup::Timestamp.last_from(timestamps,
|
50
|
+
Backup::Timestamp.parse_timestamp("110110", true),
|
51
|
+
Backup::Timestamp.parse_timestamp("110101")))
|
52
|
+
assert_equal("110130090812",
|
53
|
+
Backup::Timestamp.last_from(timestamps,
|
54
|
+
Backup::Timestamp.parse_timestamp("1101300908", true),
|
55
|
+
Backup::Timestamp.parse_timestamp("110130090811")))
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_create_timestamp
|
60
|
+
time = Time.new(2011, 01, 02, 23, 59, 59, 0)
|
61
|
+
|
62
|
+
assert_equal(Backup::Timestamp.create.length, 12)
|
63
|
+
assert_equal(Backup::Timestamp.create(time), "110102235959")
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_formatted_timestamp
|
67
|
+
time = Time.new(2011, 01, 02, 23, 59, 30, 0)
|
68
|
+
|
69
|
+
assert_equal Backup::Timestamp.to_s(time), "11.01.02 23:59:30"
|
70
|
+
assert_equal Backup::Timestamp.to_s(51), nil
|
71
|
+
assert_equal Backup::Timestamp.to_str("110102235930"), "11.01.02 23:59:30"
|
72
|
+
end
|
73
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: encbs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Timothy Klim
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-05-11 00:00:00 +04:00
|
14
|
+
default_executable: encbs
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: fog
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: slop
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: ruby-progressbar
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: bundler
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.0.0
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: jeweler
|
62
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ~>
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 1.6.0
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: rcov
|
73
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
type: :development
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: *id006
|
82
|
+
description: Simple backup system for pushing into cloud
|
83
|
+
email: klimtimothy@gmail.com
|
84
|
+
executables:
|
85
|
+
- encbs
|
86
|
+
extensions: []
|
87
|
+
|
88
|
+
extra_rdoc_files:
|
89
|
+
- LICENSE.txt
|
90
|
+
- README.rdoc
|
91
|
+
files:
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.rdoc
|
95
|
+
- Rakefile
|
96
|
+
- VERSION
|
97
|
+
- bin/encbs
|
98
|
+
- encbs.gemspec
|
99
|
+
- lib/backup.rb
|
100
|
+
- lib/backup/file_item.rb
|
101
|
+
- lib/backup/file_item/base.rb
|
102
|
+
- lib/backup/file_item/cloud.rb
|
103
|
+
- lib/backup/file_item/local.rb
|
104
|
+
- lib/backup/jar.rb
|
105
|
+
- lib/backup/timestamp.rb
|
106
|
+
- lib/crypto.rb
|
107
|
+
- lib/helpers.rb
|
108
|
+
- test/helper.rb
|
109
|
+
- test/test_backup.rb
|
110
|
+
- test/test_backup_file_item.rb
|
111
|
+
- test/test_backup_timestamp.rb
|
112
|
+
has_rdoc: true
|
113
|
+
homepage: http://github.com/TimothyKlim/encbs
|
114
|
+
licenses:
|
115
|
+
- MIT
|
116
|
+
post_install_message:
|
117
|
+
rdoc_options: []
|
118
|
+
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
hash: 4533740644498179754
|
127
|
+
segments:
|
128
|
+
- 0
|
129
|
+
version: "0"
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: "0"
|
136
|
+
requirements: []
|
137
|
+
|
138
|
+
rubyforge_project:
|
139
|
+
rubygems_version: 1.6.2
|
140
|
+
signing_key:
|
141
|
+
specification_version: 3
|
142
|
+
summary: Simple backup system for pushing into cloud
|
143
|
+
test_files: []
|
144
|
+
|