cfbackup 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -2
- data/CHANGELOG.markdown +14 -0
- data/README.markdown +9 -6
- data/Rakefile +21 -20
- data/VERSION.yml +3 -2
- data/bin/cfbackup +6 -0
- data/cfbackup.gemspec +23 -10
- data/conf/cfconfig.yml +3 -0
- data/example_scripts/piped.sh +7 -5
- data/example_scripts/temp_directory.sh +8 -7
- data/lib/cfbackup.rb +126 -28
- data/lib/optcfbackup.rb +35 -19
- data/temp/README +1 -1
- data/test/cfbackup_test.rb +47 -2
- data/test/cfconfig.yml +3 -0
- data/test/test_helper.rb +2 -0
- metadata +35 -5
data/.gitignore
CHANGED
data/CHANGELOG.markdown
CHANGED
@@ -1,6 +1,20 @@
|
|
1
1
|
CFBackup ChangeLog
|
2
2
|
==================
|
3
3
|
|
4
|
+
0.8.0 2010-01-20
|
5
|
+
-----------------
|
6
|
+
* Code documented
|
7
|
+
* Gem deployed on gemcutter
|
8
|
+
* Cloudfiles API dependency updated to 1.4.4
|
9
|
+
* Basic exception handling and retry mechanisms
|
10
|
+
* New --max_retries option
|
11
|
+
* New --ignore_errors option
|
12
|
+
* File push errors can now be ignored
|
13
|
+
* New --error_log option
|
14
|
+
* File push errors can be written to a log for later processing
|
15
|
+
|
16
|
+
This release sponsored by Shaun Brazier.
|
17
|
+
|
4
18
|
0.7.1 2009-05-19
|
5
19
|
-----------------
|
6
20
|
* Version bump and re-release of 0.6.1 because the published gem by GitHub was marked as 0.7.0.
|
data/README.markdown
CHANGED
@@ -16,18 +16,18 @@ Features
|
|
16
16
|
Requirements
|
17
17
|
--------------
|
18
18
|
|
19
|
-
*
|
19
|
+
* cloudfiles >= 1.4.4
|
20
20
|
|
21
21
|
Notes:
|
22
22
|
* If you install CFBackup as a gem, all of the dependencies _should_ automatically be installed for you.
|
23
|
-
* Ubuntu Users: The Ubuntu rubygems package will
|
24
|
-
need to update it or create a symlink to access cfbackup from anywhere. See the wiki for more information.
|
23
|
+
* Ubuntu Users: The Ubuntu rubygems package will install executables outside your normal PATH. You will
|
24
|
+
need to update it or create a symlink to access cfbackup from anywhere. See the wiki for more information.
|
25
25
|
|
26
26
|
Install
|
27
27
|
-----------
|
28
28
|
|
29
|
-
* gem sources -a http://
|
30
|
-
* sudo gem install
|
29
|
+
* gem sources -a http://gemcutter.org
|
30
|
+
* sudo gem install cfbackup
|
31
31
|
|
32
32
|
Configuration
|
33
33
|
-----------
|
@@ -41,7 +41,7 @@ CFBackup will look in the following places (in order) for the configuration file
|
|
41
41
|
|
42
42
|
The configuration file can be overridden at any time with the --config_file option
|
43
43
|
|
44
|
-
|
44
|
+
Usage
|
45
45
|
-----------
|
46
46
|
|
47
47
|
Usage: cfbackup.rb --action push|pull|delete options --container CONTAINER
|
@@ -54,6 +54,9 @@ Configuration
|
|
54
54
|
--version Show current version
|
55
55
|
--config_file PATH Use specified config file, rather than the default
|
56
56
|
--local_net Use unmetered connection in DFW1 (only applicable to Slicehost or Mosso Cloud Server customers)
|
57
|
+
--max_retries COUNT Change the number of times to retry an operation before giving up
|
58
|
+
--ignore_errors Ignore file operation errors (push only) and continue processing other files
|
59
|
+
--error_log FILEPATH Create an error log at the given filepath containing a listing of failed push operations
|
57
60
|
|
58
61
|
The wiki has usage examples and some sample automation scripts can be found in the example_scripts directory.
|
59
62
|
|
data/Rakefile
CHANGED
@@ -2,28 +2,29 @@ require 'rake'
|
|
2
2
|
|
3
3
|
$LOAD_PATH.unshift('lib')
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "cfbackup"
|
8
|
+
gem.summary = "A simple ruby program intended to serve as a useful tool for automated backups to Mosso Cloud Files."
|
9
|
+
gem.description = "A simple ruby program intended to serve as a useful tool for automated backups to Mosso Cloud Files."
|
10
|
+
gem.email = "jon@jonsview.com"
|
11
|
+
gem.homepage = "http://github.com/jmstacey/cfbackup"
|
12
|
+
gem.authors = ["Jon Stacey"]
|
13
|
+
|
14
|
+
# Dependencies
|
15
|
+
gem.add_dependency "cloudfiles", ">=1.4.4"
|
16
|
+
gem.add_dependency "gemcutter", ">= 0.1.0"
|
17
|
+
|
18
|
+
# Development Dependencies
|
19
|
+
gem.add_development_dependency "shoulda"
|
20
|
+
gem.add_development_dependency "mocha"
|
21
|
+
|
22
|
+
# Include Files
|
23
|
+
gem.files.include %w(lib/optcfbackup.rb)
|
25
24
|
end
|
26
25
|
|
26
|
+
Jeweler::GemcutterTasks.new
|
27
|
+
|
27
28
|
require 'rake/testtask'
|
28
29
|
Rake::TestTask.new(:test) do |test|
|
29
30
|
test.libs << 'lib' << 'test'
|
data/VERSION.yml
CHANGED
data/bin/cfbackup
CHANGED
data/cfbackup.gemspec
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
1
6
|
Gem::Specification.new do |s|
|
2
7
|
s.name = %q{cfbackup}
|
3
|
-
s.version = "0.
|
8
|
+
s.version = "0.8.0"
|
4
9
|
|
5
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
6
11
|
s.authors = ["Jon Stacey"]
|
7
|
-
s.date = %q{
|
12
|
+
s.date = %q{2010-01-20}
|
8
13
|
s.default_executable = %q{cfbackup}
|
9
14
|
s.description = %q{A simple ruby program intended to serve as a useful tool for automated backups to Mosso Cloud Files.}
|
10
15
|
s.email = %q{jon@jonsview.com}
|
@@ -28,7 +33,6 @@ Gem::Specification.new do |s|
|
|
28
33
|
"example_scripts/temp_directory.sh",
|
29
34
|
"lib/cfbackup.rb",
|
30
35
|
"lib/optcfbackup.rb",
|
31
|
-
"lib/optcfbackup.rb",
|
32
36
|
"temp/README",
|
33
37
|
"test/cfbackup_test.rb",
|
34
38
|
"test/cfconfig.yml",
|
@@ -40,11 +44,10 @@ Gem::Specification.new do |s|
|
|
40
44
|
"test/data/folder_2/file2.txt",
|
41
45
|
"test/test_helper.rb"
|
42
46
|
]
|
43
|
-
s.has_rdoc = true
|
44
47
|
s.homepage = %q{http://github.com/jmstacey/cfbackup}
|
45
48
|
s.rdoc_options = ["--charset=UTF-8"]
|
46
49
|
s.require_paths = ["lib"]
|
47
|
-
s.rubygems_version = %q{1.
|
50
|
+
s.rubygems_version = %q{1.3.5}
|
48
51
|
s.summary = %q{A simple ruby program intended to serve as a useful tool for automated backups to Mosso Cloud Files.}
|
49
52
|
s.test_files = [
|
50
53
|
"test/cfbackup_test.rb",
|
@@ -53,14 +56,24 @@ Gem::Specification.new do |s|
|
|
53
56
|
|
54
57
|
if s.respond_to? :specification_version then
|
55
58
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
56
|
-
s.specification_version =
|
59
|
+
s.specification_version = 3
|
57
60
|
|
58
|
-
if
|
59
|
-
s.add_runtime_dependency(%q<
|
61
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
62
|
+
s.add_runtime_dependency(%q<cloudfiles>, [">= 1.4.4"])
|
63
|
+
s.add_runtime_dependency(%q<gemcutter>, [">= 0.1.0"])
|
64
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
65
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
60
66
|
else
|
61
|
-
s.add_dependency(%q<
|
67
|
+
s.add_dependency(%q<cloudfiles>, [">= 1.4.4"])
|
68
|
+
s.add_dependency(%q<gemcutter>, [">= 0.1.0"])
|
69
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
70
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
62
71
|
end
|
63
72
|
else
|
64
|
-
s.add_dependency(%q<
|
73
|
+
s.add_dependency(%q<cloudfiles>, [">= 1.4.4"])
|
74
|
+
s.add_dependency(%q<gemcutter>, [">= 0.1.0"])
|
75
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
76
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
65
77
|
end
|
66
78
|
end
|
79
|
+
|
data/conf/cfconfig.yml
CHANGED
data/example_scripts/piped.sh
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
#!/bin/sh
|
2
2
|
|
3
|
-
#
|
4
|
-
# container, bypassing the intermediate temp directory step
|
3
|
+
# piped.sh
|
5
4
|
#
|
6
|
-
# This
|
7
|
-
#
|
8
|
-
#
|
5
|
+
# This script demonstrates how CFBackup could be used to peform
|
6
|
+
# automated backups. In this example, data is piped directly
|
7
|
+
# to a Cloud Files container, bypassing the need for a temporary
|
8
|
+
# holding directory.
|
9
|
+
#
|
10
|
+
# Modify it to suit your needs.
|
9
11
|
|
10
12
|
CONTAINER=backups
|
11
13
|
NOW=$(date +_%b_%d_%y)
|
@@ -1,13 +1,14 @@
|
|
1
1
|
#!/bin/sh
|
2
2
|
|
3
|
-
#
|
4
|
-
# holding directory. Then, the entire directory is uploaded to
|
5
|
-
# Cloud Files. Finally, the temp directory is cleared in preparation
|
6
|
-
# for the next backup.
|
3
|
+
# temp_directory.sh
|
7
4
|
#
|
8
|
-
# This
|
9
|
-
#
|
10
|
-
#
|
5
|
+
# This script demonstrates how CFBackup could be used to peform
|
6
|
+
# automated backups. In this example, backups are first created
|
7
|
+
# and placed in a temporary holding directory. Then, the entire
|
8
|
+
# directory is uploaded to Cloud Files. Finally, the temp directory
|
9
|
+
# is cleared in preparation for the next scheduled backup.
|
10
|
+
#
|
11
|
+
# Modify it to suit your needs.
|
11
12
|
|
12
13
|
cd ~/cfbackup/temp
|
13
14
|
CONTAINER=backups
|
data/lib/cfbackup.rb
CHANGED
@@ -22,6 +22,11 @@ require 'yaml'
|
|
22
22
|
|
23
23
|
class CFBackup
|
24
24
|
|
25
|
+
# Implementation of initialize
|
26
|
+
#
|
27
|
+
# Prepares CFBackup object by calling options processor,
|
28
|
+
# loading configuration files, and preparing the connection
|
29
|
+
# to Mosso Cloud Files.
|
25
30
|
def initialize(args)
|
26
31
|
@opts = OptCFBackup.new(args)
|
27
32
|
|
@@ -47,9 +52,14 @@ class CFBackup
|
|
47
52
|
show_error('Error: Unable to locate config file.') unless (@conf != nil)
|
48
53
|
|
49
54
|
prep_connection
|
55
|
+
prep_error_log_file unless !@opts.options.error_log
|
50
56
|
|
51
57
|
end # initialize()
|
52
58
|
|
59
|
+
# Run CFBackup.
|
60
|
+
#
|
61
|
+
# This method will call the appropriate method based on
|
62
|
+
# the action given when CFBackup was called.
|
53
63
|
def run
|
54
64
|
|
55
65
|
show_error() unless (@opts.options.container != "")
|
@@ -74,18 +84,38 @@ class CFBackup
|
|
74
84
|
|
75
85
|
private
|
76
86
|
|
87
|
+
# Prepare the connection to Mosso Cloud Files.
|
88
|
+
#
|
89
|
+
# Will attempt connectiong via the local network if
|
90
|
+
# --local_net option was specified.
|
77
91
|
def prep_connection
|
78
92
|
# Establish connection
|
79
93
|
show_verbose "Establishing connection...", false
|
80
|
-
@cf = CloudFiles::Connection.new(@conf["username"], @conf["api_key"]);
|
81
|
-
show_verbose " done."
|
82
94
|
|
83
|
-
|
84
|
-
|
85
|
-
@cf
|
95
|
+
retry_count = 1
|
96
|
+
begin
|
97
|
+
@cf = CloudFiles::Connection.new(@conf["username"], @conf["api_key"], true, @opts.options.local_net)
|
98
|
+
rescue AuthenticationException => e
|
99
|
+
puts "Error: #{e.message}. Check your cfconfig.yml file."
|
100
|
+
Process.exit
|
101
|
+
rescue ConnectionException => e
|
102
|
+
if retry_count <= @opts.options.max_retries
|
103
|
+
puts "Error: #{e.message}. Retrying (#{retry_count}/#{@opts.options.max_retries.to_s}) in 15 seconds..."
|
104
|
+
retry_count = retry_count + 1
|
105
|
+
sleep 15
|
106
|
+
retry
|
107
|
+
else
|
108
|
+
puts "Error: #{e.message}. Giving up!"
|
109
|
+
Process.exit
|
110
|
+
end
|
86
111
|
end
|
87
112
|
end # prep_connection()
|
88
|
-
|
113
|
+
|
114
|
+
# Prepare the Cloud Files Container.
|
115
|
+
#
|
116
|
+
# Confirms the existence of the specified container
|
117
|
+
# and attempts to create it if possible. If container creation
|
118
|
+
# is disabled, an error will be thrown.
|
89
119
|
def prep_container(create_container = true)
|
90
120
|
# Check for the container. If it doesn't exist, create it if allowed
|
91
121
|
if !@cf.container_exists?(@opts.options.container)
|
@@ -101,14 +131,50 @@ class CFBackup
|
|
101
131
|
@container = @cf.container(@opts.options.container)
|
102
132
|
end # prep_cnnection()
|
103
133
|
|
134
|
+
# Push piped data to the Cloud Files container.
|
135
|
+
#
|
136
|
+
# Pushes data piped from STDIN directly to the Cloud Files container.
|
104
137
|
def push_piped_data
|
105
138
|
prep_container
|
106
139
|
|
107
|
-
puts "
|
140
|
+
puts "Note: Rackspace enforces a 5GB maximum filesize."
|
108
141
|
object = @container.create_object(@opts.options.remote_path, true)
|
109
142
|
object.write
|
110
143
|
end # push_piped_data()
|
111
144
|
|
145
|
+
# Push single file to the Cloud Files container.
|
146
|
+
def push_file(file)
|
147
|
+
if @opts.options.remote_path.to_s == ''
|
148
|
+
remote_path = file
|
149
|
+
else
|
150
|
+
remote_path = File.join(@opts.options.remote_path, file)
|
151
|
+
end
|
152
|
+
|
153
|
+
retry_count = 1
|
154
|
+
begin
|
155
|
+
object = @container.create_object(remote_path, true)
|
156
|
+
object.load_from_filename(file)
|
157
|
+
rescue Exception => e
|
158
|
+
if retry_count <= @opts.options.max_retries
|
159
|
+
puts "Error: #{e.message}. Retrying (#{retry_count}/#{@opts.options.max_retries.to_s})..."
|
160
|
+
retry_count = retry_count + 1
|
161
|
+
retry
|
162
|
+
else
|
163
|
+
write_error_to_log(file, e) unless !@opts.options.error_log
|
164
|
+
unless @opts.options.ignore_errors
|
165
|
+
puts "Error: #{e.message}. Giving up!"
|
166
|
+
Process.exit
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Push files to the Cloud Files container.
|
173
|
+
#
|
174
|
+
# Deterimes what files to upload and then sends them to the
|
175
|
+
# Cloud Files container one at a time. If the push is recursive
|
176
|
+
# pseudo directories will be used to mimic the filesystem layout
|
177
|
+
# in the Cloud Files container.
|
112
178
|
def push_files
|
113
179
|
prep_container
|
114
180
|
|
@@ -138,16 +204,7 @@ class CFBackup
|
|
138
204
|
end
|
139
205
|
|
140
206
|
show_verbose "(#{counter}/#{files.length}) Pushing file #{file}...", false
|
141
|
-
|
142
|
-
if @opts.options.remote_path.to_s == ''
|
143
|
-
remote_path = file
|
144
|
-
else
|
145
|
-
remote_path = File.join(@opts.options.remote_path, file)
|
146
|
-
end
|
147
|
-
|
148
|
-
object = @container.create_object(remote_path, true)
|
149
|
-
object.load_from_filename(file)
|
150
|
-
|
207
|
+
push_file file
|
151
208
|
show_verbose " done."
|
152
209
|
counter += 1
|
153
210
|
end # files.each
|
@@ -156,6 +213,12 @@ class CFBackup
|
|
156
213
|
|
157
214
|
end # push_files()
|
158
215
|
|
216
|
+
# Pull files from Cloud Files container to local filesystem.
|
217
|
+
#
|
218
|
+
# This method will pull the given file or directory from
|
219
|
+
# the Cloud Files container to the local filesystem. If recursion
|
220
|
+
# is enabled, the pseudo directory structure will also be duplicated
|
221
|
+
# on the local system.
|
159
222
|
def pull_files
|
160
223
|
prep_container(false)
|
161
224
|
|
@@ -218,6 +281,10 @@ class CFBackup
|
|
218
281
|
end
|
219
282
|
end # pull_files()
|
220
283
|
|
284
|
+
# Delete a given file from a Cloud Files container.
|
285
|
+
#
|
286
|
+
# This method will delete a single file or directory
|
287
|
+
# from the Cloud Files container.
|
221
288
|
def delete_files
|
222
289
|
prep_container(false)
|
223
290
|
|
@@ -244,9 +311,15 @@ class CFBackup
|
|
244
311
|
|
245
312
|
end # delete_files()
|
246
313
|
|
314
|
+
################################################
|
315
|
+
# Helper methods #
|
316
|
+
################################################
|
247
317
|
|
248
|
-
#
|
249
|
-
|
318
|
+
# Determines if the given object is a file.
|
319
|
+
#
|
320
|
+
# If the object is a file, true is returned. Otherwise,
|
321
|
+
# if the object is a directory false is returned and a warning is
|
322
|
+
# emitted if the recursive option was not specified.
|
250
323
|
def object_file?
|
251
324
|
|
252
325
|
file = false
|
@@ -266,6 +339,10 @@ class CFBackup
|
|
266
339
|
return file
|
267
340
|
end
|
268
341
|
|
342
|
+
# Get an array of objects to process from the container.
|
343
|
+
#
|
344
|
+
# Queries the Cloud Files container to compile and return an array
|
345
|
+
# of objects that need to be processed.
|
269
346
|
def get_objects_array
|
270
347
|
|
271
348
|
file = object_file?
|
@@ -284,23 +361,44 @@ class CFBackup
|
|
284
361
|
return objects
|
285
362
|
end
|
286
363
|
|
287
|
-
#
|
364
|
+
# Show given message if verbose output is turned on
|
365
|
+
#
|
366
|
+
# Used to display or hide messages based on the users verbosity
|
367
|
+
# preference.
|
288
368
|
def show_verbose(message, line_break = true)
|
289
|
-
|
290
|
-
|
291
|
-
puts message
|
292
|
-
else
|
293
|
-
print message
|
294
|
-
end
|
295
|
-
$stdout.flush
|
369
|
+
if @opts.options.verbose
|
370
|
+
line_break ? puts(message) : print(message)
|
296
371
|
end
|
372
|
+
$stdout.flush
|
297
373
|
end # show_verbose()
|
298
374
|
|
299
|
-
# Show error message, banner and exit
|
375
|
+
# Show error message, banner, and exit program.
|
376
|
+
#
|
377
|
+
# This is considered a critical error and the application
|
378
|
+
# terminated after printing the given error message and the
|
379
|
+
# usage banner for reference.
|
300
380
|
def show_error(message = '')
|
301
381
|
puts message
|
302
382
|
puts @opts.banner
|
303
383
|
exit
|
304
384
|
end # show_error()
|
305
385
|
|
386
|
+
# Prepare error log file for writing
|
387
|
+
#
|
388
|
+
# The log file is created if necessary and the current date and time are
|
389
|
+
# written to indicate a new set of operations. The log is never overwritten
|
390
|
+
# or truncated except by user.
|
391
|
+
def prep_error_log_file
|
392
|
+
header = "\n\nFile operations initiated #{Time.now}\n------------------"
|
393
|
+
File.open(@opts.options.error_log, 'a+') { |f| f.write(header) }
|
394
|
+
end
|
395
|
+
|
396
|
+
# Append given error message to the error log
|
397
|
+
#
|
398
|
+
# Log entries will be in the format "filepath:exception message"
|
399
|
+
def write_error_to_log(message, exception)
|
400
|
+
output = "#{message}:#{exception.message}"
|
401
|
+
File.open(@opts.options.error_log, 'a') {|f| f.write(output) }
|
402
|
+
end
|
403
|
+
|
306
404
|
end # class CFBackup
|
data/lib/optcfbackup.rb
CHANGED
@@ -1,30 +1,35 @@
|
|
1
|
+
# Handle option parsing for use in CFBackup.
|
2
|
+
|
1
3
|
require 'optparse'
|
2
4
|
require 'ostruct'
|
3
5
|
|
6
|
+
# Option parser class for CFBackup
|
4
7
|
class OptCFBackup
|
5
8
|
|
6
|
-
# Options structure
|
7
|
-
attr_reader :
|
8
|
-
|
9
|
-
# Ussage message
|
10
|
-
attr_reader :banner
|
9
|
+
attr_reader :options # Options structure
|
10
|
+
attr_reader :banner # Ussage message
|
11
11
|
|
12
|
+
# Implementation of initialize
|
13
|
+
#
|
12
14
|
# Initializes object with command line arguments passed
|
13
15
|
def initialize(args)
|
14
16
|
|
15
17
|
@banner = "Usage: cfbackup.rb --action push|pull|delete options --container CONTAINER"
|
16
18
|
|
17
19
|
@options = OpenStruct.new
|
18
|
-
self.options.config
|
19
|
-
self.options.action
|
20
|
-
self.options.pipe_data
|
21
|
-
self.options.show_ver
|
22
|
-
self.options.recursive
|
23
|
-
self.options.local_net
|
24
|
-
self.options.container
|
25
|
-
self.options.local_path
|
26
|
-
self.options.remote_path
|
27
|
-
self.options.verbose
|
20
|
+
self.options.config = ["#{ENV['HOME']}/.cfconfig.yml", './cfconfig.yml', '/etc/cfconfig.yml']
|
21
|
+
self.options.action = ''
|
22
|
+
self.options.pipe_data = false
|
23
|
+
self.options.show_ver = false
|
24
|
+
self.options.recursive = false
|
25
|
+
self.options.local_net = false
|
26
|
+
self.options.container = ''
|
27
|
+
self.options.local_path = ''
|
28
|
+
self.options.remote_path = ''
|
29
|
+
self.options.verbose = false
|
30
|
+
self.options.max_retries = 3
|
31
|
+
self.options.ignore_errors = false
|
32
|
+
self.options.error_log = false
|
28
33
|
|
29
34
|
opts = OptionParser.new do |opts|
|
30
35
|
opts.banner = self.banner
|
@@ -67,20 +72,31 @@ class OptCFBackup
|
|
67
72
|
self.options.local_net = local_net
|
68
73
|
end
|
69
74
|
|
75
|
+
opts.on("--max_retries COUNT", "Change the number of times to retry an operation before giving up.") do |config|
|
76
|
+
self.options.max_retries = max_retries
|
77
|
+
end
|
78
|
+
|
79
|
+
opts.on("--ignore_errors", "Ignore file operation errors (push only) and continue processing other files.") do |ignore_errors|
|
80
|
+
self.options.ignore_errors = ignore_errors
|
81
|
+
end
|
82
|
+
|
83
|
+
opts.on("--error_log FILEPATH", "Create an error log at the given filepath containing a listing of failed push operations.") do |error_log|
|
84
|
+
self.options.error_log = error_log
|
85
|
+
end
|
86
|
+
|
70
87
|
end
|
71
88
|
|
72
|
-
opts.parse!(args)
|
89
|
+
opts.parse!(args) # Parse arguments
|
73
90
|
|
74
91
|
end # initialize()
|
75
92
|
|
76
93
|
private
|
77
94
|
|
95
|
+
# Remove trailing slash from the remote path if present.
|
78
96
|
def clean_remote_path
|
79
97
|
if self.options.remote_path[0,1] == "/"
|
80
98
|
self.options.remote_path.slice!(0)
|
81
99
|
end
|
82
|
-
|
83
|
-
# self.options.remote_path = self.options.remote_path + "/" unless (self.options.remote_path[-1,1] == "/")
|
84
|
-
end
|
100
|
+
end # clean_remote_path()
|
85
101
|
|
86
102
|
end # class OptCFBackup
|
data/temp/README
CHANGED
data/test/cfbackup_test.rb
CHANGED
@@ -1,8 +1,47 @@
|
|
1
|
+
# Contains CFBackup unit tests.
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
5
|
+
# CFBackup test class
|
3
6
|
class CfbackupTest < Test::Unit::TestCase
|
4
|
-
TEST_DIR = 'test/tmp'
|
7
|
+
TEST_DIR = 'test/tmp' # Test directory
|
8
|
+
|
9
|
+
# Test --error_log option
|
10
|
+
context "A backup with the --error_log option enabled" do
|
11
|
+
setup do
|
12
|
+
mock_ARGV = ['--action', 'push', '--local_path', 'test/data/data.txt', '--container', 'test', '--config_file', 'test/cfconfig.yml', '-v', '--error_log', 'error.log']
|
13
|
+
backup = CFBackup.new(mock_ARGV)
|
14
|
+
end
|
15
|
+
|
16
|
+
should "result in a log file being created" do
|
17
|
+
assert_equal true, File.exists?('error.log')
|
18
|
+
end
|
19
|
+
|
20
|
+
teardown do
|
21
|
+
File.delete('error.log')
|
22
|
+
end
|
23
|
+
end
|
5
24
|
|
25
|
+
# Test connections
|
26
|
+
context "A connection" do
|
27
|
+
|
28
|
+
context "that fails with a ConnectionException and the default number of retries" do
|
29
|
+
should "should be retried 3 times and then exit" do
|
30
|
+
CloudFiles::Connection.expects(:new).times(4).raises(ConnectionException)
|
31
|
+
# We expect 4 calls (1 initial + 3 retries)
|
32
|
+
|
33
|
+
assert_raises(SystemExit) do
|
34
|
+
mock_ARGV = ['--action', 'push', '--local_path', 'test/data/data.txt', '--container', 'test', '--config_file', 'test/cfconfig.yml', '-v']
|
35
|
+
CFBackup.new(mock_ARGV)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end # context "that fails with a ConnectionException..."
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
# Test uploading files.
|
43
|
+
# First a single file is uploaded, then a recursive directory
|
44
|
+
# push is performed.
|
6
45
|
context "A backup" do
|
7
46
|
|
8
47
|
context "with a single file push" do
|
@@ -26,9 +65,12 @@ class CfbackupTest < Test::Unit::TestCase
|
|
26
65
|
assert @backup.run
|
27
66
|
end
|
28
67
|
end
|
29
|
-
|
68
|
+
|
30
69
|
end
|
31
70
|
|
71
|
+
# Test restoring files.
|
72
|
+
# First attempts pulling a single file, then attempts a recursive
|
73
|
+
# directory pull. Cleans up afterwards.
|
32
74
|
context "A restore" do
|
33
75
|
|
34
76
|
context "with a single file pull" do
|
@@ -78,6 +120,9 @@ class CfbackupTest < Test::Unit::TestCase
|
|
78
120
|
|
79
121
|
end
|
80
122
|
|
123
|
+
# Test deleting remote objects.
|
124
|
+
# First test deleting a single remote object representing a file,
|
125
|
+
# then delete a pseudo directory recursively.
|
81
126
|
context "A deletion" do
|
82
127
|
|
83
128
|
context "of a single file" do
|
data/test/cfconfig.yml
CHANGED
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cfbackup
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Stacey
|
@@ -9,18 +9,48 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-01-20 00:00:00 -06:00
|
13
13
|
default_executable: cfbackup
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
16
|
+
name: cloudfiles
|
17
17
|
type: :runtime
|
18
18
|
version_requirement:
|
19
19
|
version_requirements: !ruby/object:Gem::Requirement
|
20
20
|
requirements:
|
21
21
|
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 1.
|
23
|
+
version: 1.4.4
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: gemcutter
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.0
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: shoulda
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: mocha
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
24
54
|
version:
|
25
55
|
description: A simple ruby program intended to serve as a useful tool for automated backups to Mosso Cloud Files.
|
26
56
|
email: jon@jonsview.com
|
@@ -82,7 +112,7 @@ requirements: []
|
|
82
112
|
rubyforge_project:
|
83
113
|
rubygems_version: 1.3.5
|
84
114
|
signing_key:
|
85
|
-
specification_version:
|
115
|
+
specification_version: 3
|
86
116
|
summary: A simple ruby program intended to serve as a useful tool for automated backups to Mosso Cloud Files.
|
87
117
|
test_files:
|
88
118
|
- test/cfbackup_test.rb
|