cfbackup 0.7.1 → 0.8.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/.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
|