s3-backup 0.6.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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Ben Koski
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.
@@ -0,0 +1,63 @@
1
+ = s3-backup
2
+
3
+ Easy tar-based backups to S3, with backup rotation, cleanup helpers, and pre-/post-backup hooks.
4
+
5
+ == Install
6
+
7
+ sudo gem install s3-backup
8
+
9
+ == Easy Example
10
+
11
+ b = S3Backup.new('vps-backups') # Init with the name of bucket to push to; keys handled by AWSCredentials
12
+
13
+ b.files << '/usr/local/important.data'
14
+ b.files << '/usr/local/more/important.data'
15
+ b.files << '/usr/local/secrets'
16
+
17
+ b.run
18
+
19
+ This will:
20
+
21
+ 1. Create a .tar.gz file including everything in <tt>files</tt>
22
+ 2. Push tar to S3
23
+ 3. Delete old backups (keeps 5 by default, but you can change <tt>copies_to_keep</tt>)
24
+ 4. Remove scratch files
25
+
26
+ == Hooks
27
+
28
+ S3Backup also includes a <tt>before_backup</tt> and <tt>after_backup</tt> hook to take care of preparing dumpfiles
29
+ and restarting services. These are just blocks of code.
30
+
31
+ Also useful, <tt>files_to_cleanup</tt> contains a list of files to cleanup post-backup. You
32
+ can add your scratch files to this list and they'll be deleted after each run, even if an error is encountered.
33
+
34
+ <tt>tar_excludes</tt> is an array of patterns for tar to exclude -- for example, "*.log"
35
+
36
+ For example:
37
+
38
+ b = S3Backup.new('vps-backups')
39
+
40
+ b.before_backup do
41
+ `mysqldump -h locahost important_data > /tmp/important_data.sql`
42
+ raise "mysqdump failed" if !$?.success?
43
+
44
+ b.files_to_cleanup << '/tmp/important_data.sql'
45
+ b.files << '/tmp/important_data.sql'
46
+ end
47
+
48
+ b.run
49
+
50
+ You'll find additional opts in the S3Backup docs.
51
+
52
+ == Suggested deployment
53
+
54
+ Create a ruby script, and add to your crontab. Don't forget to 2>&1 the output and set a <tt>MAILTO</tt> to get error notices.
55
+
56
+ == Notes
57
+
58
+ 1. This uses AWSCredentials[http://github.com/bkoski/aws_credentials] to manage AWS keys.
59
+ 2. This doesn't work on Windows.
60
+
61
+ == Copyright
62
+
63
+ Copyright (c) 2010 Ben Koski. See LICENSE for details.
@@ -0,0 +1,61 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "s3-backup"
8
+ gem.summary = %Q{Easy tar-based backups to S3, with backup rotation, cleanup helpers, and pre-/post-backup hooks.}
9
+ gem.description = %Q{Easy tar-based backups to S3, with backup rotation, cleanup helpers, and pre-/post-backup hooks.}
10
+ gem.email = "gems@benkoski.com"
11
+ gem.homepage = "http://github.com/bkoski/s3-backup"
12
+ gem.authors = ["Ben Koski"]
13
+
14
+ gem.files += ["lib/s3-backup/base.rb"]
15
+
16
+ gem.add_dependency "aws_credentials", ">= 0.6.0"
17
+ gem.add_dependency "right_aws", "~> 2.0.0"
18
+
19
+ gem.add_development_dependency "shoulda", ">= 0"
20
+ gem.add_development_dependency "mocha", ">= 0"
21
+
22
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
23
+ end
24
+ Jeweler::GemcutterTasks.new
25
+ rescue LoadError
26
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
27
+ end
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ begin
37
+ require 'rcov/rcovtask'
38
+ Rcov::RcovTask.new do |test|
39
+ test.libs << 'test'
40
+ test.pattern = 'test/**/test_*.rb'
41
+ test.verbose = true
42
+ end
43
+ rescue LoadError
44
+ task :rcov do
45
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
46
+ end
47
+ end
48
+
49
+ task :test => :check_dependencies
50
+
51
+ task :default => :test
52
+
53
+ require 'hanna/rdoctask'
54
+ Rake::RDocTask.new do |rdoc|
55
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
56
+
57
+ rdoc.rdoc_dir = 'rdoc'
58
+ rdoc.title = "s3-backup #{version}"
59
+ rdoc.rdoc_files.include('README*')
60
+ rdoc.rdoc_files.include('lib/**/*.rb')
61
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.6.0
@@ -0,0 +1,16 @@
1
+ require 'right_aws'
2
+ require 'aws_credentials'
3
+ require 'fileutils'
4
+
5
+ require File.join(File.dirname(__FILE__), 's3-backup', 'base')
6
+
7
+ # To suppress "warning: peer certificate won't be verified in this SSL session" errors,
8
+ # borrowed from http://www.5dollarwhitebox.org/drupal/node/64
9
+ class Net::HTTP
10
+ alias_method :old_initialize, :initialize
11
+ def initialize(*args)
12
+ old_initialize(*args)
13
+ @ssl_context = OpenSSL::SSL::SSLContext.new
14
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
15
+ end
16
+ end
@@ -0,0 +1,118 @@
1
+ class S3Backup
2
+
3
+ # Array of files or paths to back up
4
+ attr_accessor :files
5
+
6
+ # Array of files that will be deleted post-backup, regardless of success
7
+ attr_accessor :files_to_cleanup
8
+
9
+ # Array of exclude patterns, these are passed to tar as <tt>--exclude</tt> flags
10
+ attr_accessor :tar_excludes
11
+
12
+ # Number of backups to keep on S3. Defaults to 5.
13
+ attr_accessor :copies_to_keep
14
+
15
+ # Prefix for tarball, defaults to "backup".
16
+ attr_accessor :backup_name
17
+
18
+ # Name of bucket to push to, set on initialize.
19
+ attr_reader :bucket_name
20
+
21
+ # Initialize with the name of S3 bucket to push to
22
+ def initialize(bucket_name)
23
+ @files = []
24
+ @files_to_cleanup = []
25
+ @tar_excludes = []
26
+ @copies_to_keep = 5
27
+ @bucket_name = bucket_name
28
+ @backup_name = 'backup'
29
+
30
+ @s3 = RightAws::S3.new(AWSCredentials.access_key, AWSCredentials.secret_access_key, :logger => Logger.new(nil))
31
+ end
32
+
33
+ # Called before backup runs. Useful for dumping a database, or creating files
34
+ # prior to tarball create. As you create tmpfiles, you can push onto files_to_cleanup
35
+ # to ensure post-backup cleanup.
36
+ def before_backup &block
37
+ @before_backup = block
38
+ end
39
+
40
+ # Called after backup runs. Useful for restarting services.
41
+ def after_backup &block
42
+ @after_backup = block
43
+ end
44
+
45
+ # Runs the backup: creates tarball, pushes to s3, and rotates old backups.
46
+ def run
47
+ begin
48
+ @before_backup.call unless @before_backup.nil?
49
+
50
+ create_tarball
51
+ push_to_s3
52
+ rotate_remote_backups
53
+
54
+ @after_backup.call unless @after_backup.nil?
55
+ ensure
56
+ cleanup_files
57
+ end
58
+ end
59
+
60
+ private
61
+ # Name of the tarball created locally
62
+ def tarball_name
63
+ @tarbarll_name ||= "/tmp/#{backup_name}-#{Time.now.to_i}.tar.gz"
64
+ @tarbarll_name
65
+ end
66
+
67
+ # Name of the file passed to tar -I containing files to back up
68
+ def include_file_name
69
+ tarball_name.gsub('.tar.gz','-includes.txt')
70
+ end
71
+
72
+ def create_tarball
73
+ raise ArgumentError, "files to backup is empty!" if files.empty?
74
+
75
+ write_include_file
76
+ files_to_cleanup << include_file_name
77
+
78
+ excludes = tar_excludes.collect { |e| "--exclude #{e}" }
79
+ run_tar(%{#{excludes.join(" ")} --preserve -I #{include_file_name} -czf #{tarball_name}})
80
+
81
+ files_to_cleanup << tarball_name
82
+ end
83
+
84
+ def write_include_file
85
+ File.open(include_file_name, 'w') { |f| f.write(files.join("\n")) }
86
+ end
87
+
88
+ def run_tar params
89
+ output = `tar #{params} 2>&1`
90
+ raise "tar create failed with #{output}" if !$?.success?
91
+ end
92
+
93
+ def s3_bucket
94
+ raise ArgumentError, "bucket_name must be set to run a backup!" if @bucket_name.nil?
95
+ if @s3_bucket.nil?
96
+ @s3_bucket = @s3.bucket(bucket_name)
97
+ raise ArgumentError, "bucket #{bucket_name} not found!" if @s3_bucket.nil?
98
+ end
99
+
100
+ @s3_bucket
101
+ end
102
+
103
+ def push_to_s3
104
+ s3_bucket.put(File.basename(tarball_name), File.open(tarball_name))
105
+ end
106
+
107
+ def rotate_remote_backups
108
+ all_backups = s3_bucket.keys(:prefix => backup_name).sort_by { |k| k.name }.reverse
109
+ if all_backups.length > copies_to_keep
110
+ all_backups[copies_to_keep, all_backups.length - copies_to_keep].each { |k| k.delete }
111
+ end
112
+ end
113
+
114
+ def cleanup_files
115
+ files_to_cleanup.each { |f| FileUtils.rm_rf(f) }
116
+ end
117
+
118
+ end
@@ -0,0 +1,64 @@
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
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{s3-backup}
8
+ s.version = "0.6.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ben Koski"]
12
+ s.date = %q{2010-10-17}
13
+ s.description = %q{Easy tar-based backups to S3, with backup rotation, cleanup helpers, and pre-/post-backup hooks.}
14
+ s.email = %q{gems@benkoski.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/s3-backup.rb",
27
+ "lib/s3-backup/base.rb",
28
+ "s3-backup.gemspec",
29
+ "test/base_test.rb",
30
+ "test/helper.rb"
31
+ ]
32
+ s.homepage = %q{http://github.com/bkoski/s3-backup}
33
+ s.rdoc_options = ["--charset=UTF-8"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = %q{1.3.7}
36
+ s.summary = %q{Easy tar-based backups to S3, with backup rotation, cleanup helpers, and pre-/post-backup hooks.}
37
+ s.test_files = [
38
+ "test/base_test.rb",
39
+ "test/helper.rb"
40
+ ]
41
+
42
+ if s.respond_to? :specification_version then
43
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
44
+ s.specification_version = 3
45
+
46
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
47
+ s.add_runtime_dependency(%q<aws_credentials>, [">= 0.6.0"])
48
+ s.add_runtime_dependency(%q<right_aws>, ["~> 2.0.0"])
49
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
50
+ s.add_development_dependency(%q<mocha>, [">= 0"])
51
+ else
52
+ s.add_dependency(%q<aws_credentials>, [">= 0.6.0"])
53
+ s.add_dependency(%q<right_aws>, ["~> 2.0.0"])
54
+ s.add_dependency(%q<shoulda>, [">= 0"])
55
+ s.add_dependency(%q<mocha>, [">= 0"])
56
+ end
57
+ else
58
+ s.add_dependency(%q<aws_credentials>, [">= 0.6.0"])
59
+ s.add_dependency(%q<right_aws>, ["~> 2.0.0"])
60
+ s.add_dependency(%q<shoulda>, [">= 0"])
61
+ s.add_dependency(%q<mocha>, [">= 0"])
62
+ end
63
+ end
64
+
@@ -0,0 +1,164 @@
1
+ require 'helper'
2
+
3
+ class BaseTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @b = S3Backup.new('test')
7
+ @b.files = ['/tmp/test1.txt']
8
+ end
9
+
10
+ context "callbacks" do
11
+ setup do
12
+ stub_io_methods
13
+ end
14
+
15
+ should "call before_backup block before backup is run" do
16
+ s = sequence('backup')
17
+
18
+ before_mock = mock()
19
+ before_mock.expects(:test_cmd).in_sequence(s)
20
+ @b.before_backup { before_mock.test_cmd }
21
+
22
+ @b.expects(:create_tarball).in_sequence(s)
23
+
24
+ @b.run
25
+ end
26
+
27
+ should "call after_backup block after backup is run" do
28
+ s = sequence('backup')
29
+
30
+ @b.expects(:push_to_s3).in_sequence(s)
31
+
32
+ after_mock = mock()
33
+ after_mock.expects(:test_cmd).in_sequence(s)
34
+ @b.after_backup { after_mock.test_cmd }
35
+
36
+ @b.run
37
+ end
38
+ end
39
+
40
+ context "cleanup" do
41
+ setup do
42
+ stub_io_methods
43
+ end
44
+
45
+ should "remove everything specified in files_to_delete on success" do
46
+ @b.files_to_cleanup << '/tmp/testtmpfile.txt'
47
+ FileUtils.expects(:rm_rf).with('/tmp/testtmpfile.txt')
48
+ @b.run
49
+ end
50
+
51
+ should "remove everything specified in files_to_delete even if an exception was raised" do
52
+ @b.files_to_cleanup << '/tmp/testtmpfile.txt'
53
+ FileUtils.expects(:rm_rf).with('/tmp/testtmpfile.txt')
54
+ @b.stubs(:create_tarball).raises('test error')
55
+ @b.run rescue nil
56
+ end
57
+
58
+ should "remove local tarfile" do
59
+ FileUtils.expects(:rm_rf).with(@b.tarball_name)
60
+ @b.run
61
+ end
62
+
63
+ should "remove local includes file" do
64
+ FileUtils.expects(:rm_rf).with(@b.include_file_name)
65
+ @b.run
66
+ end
67
+ end
68
+
69
+ context "tar file" do
70
+
71
+ setup do
72
+ stub_io_methods(:run_tar, :rotate_remote_backups)
73
+
74
+ @tarball_mock = mock()
75
+ File.stubs(:open).with(@b.tarball_name).returns(@tarball_mock)
76
+
77
+ @include_file_mock = mock()
78
+ @include_file_mock.stubs(:write)
79
+ File.stubs(:open).with(@b.include_file_name, 'w').yields(@include_file_mock)
80
+ end
81
+
82
+ context "naming" do
83
+ should "default to backup" do
84
+ @b.expects(:run_tar).with(regexp_matches(/backup-\d+.tar.gz$/))
85
+ @b.run
86
+ end
87
+
88
+ should "include backup_name if specified" do
89
+ @b = S3Backup.new('test')
90
+
91
+ @b.files = ['/tmp/test1.txt']
92
+ @b.backup_name = 'app_data'
93
+
94
+ File.stubs(:open).with(@b.include_file_name, 'w').yields(@include_file_mock)
95
+ File.stubs(:open).with(anything).returns(mock())
96
+
97
+ @b.expects(:run_tar).with(regexp_matches(/app_data-\d+.tar.gz$/))
98
+ @b.run
99
+ end
100
+ end
101
+
102
+ should "include all files named in files" do
103
+ @include_file_mock.expects(:write).with(@b.files.join("\n"))
104
+ @b.expects(:run_tar).with(regexp_matches(/-I #{@b.include_file_name}/))
105
+ @b.run
106
+ end
107
+
108
+ should "exclude tar_excludes" do
109
+ @b.tar_excludes = ['*.log','*.txt']
110
+ @b.expects(:run_tar).with(regexp_matches(/--exclude \*.log --exclude \*.txt/))
111
+ @b.run
112
+ end
113
+
114
+ should "be run with --perserve" do
115
+ @b.expects(:run_tar).with(regexp_matches(/--preserve /))
116
+ @b.run
117
+ end
118
+
119
+ should "be pushed to s3" do
120
+ @s3_bucket.expects(:put).with(File.basename(@b.tarball_name), @tarball_mock)
121
+ @b.run
122
+ end
123
+ end
124
+
125
+ context "rotation" do
126
+ setup do
127
+ stub_io_methods(:run_tar, :write_include_file, :push_to_s3)
128
+ end
129
+
130
+ should "not remove anything if total count is < copies_to_keep" do
131
+ mock_keys = [mock(),mock()]
132
+ mock_keys.each_with_index do |k,i|
133
+ k.stubs(:name).returns(i.to_s)
134
+ k.expects(:delete).never()
135
+ end
136
+
137
+ @s3_bucket.stubs(:keys).with(:prefix => @b.backup_name).returns(mock_keys)
138
+ @b.run
139
+ end
140
+
141
+ should "remove oldest files if total count is > copies_to_keep" do
142
+ mock_keys = [mock(:name => 'file-299'),mock(:name => 'file-300'),mock(:name => 'file-301'),mock(:name => 'file-302'),mock(:name => 'file-303')]
143
+ mock_keys[0,@b.copies_to_keep].each { |k| k.expects(:delete).never() }
144
+
145
+ extra_keys = [mock(:name => 'file-100'),mock(:name => 'file-102')]
146
+ extra_keys.each { |k| k.expects(:delete) }
147
+ mock_keys += extra_keys
148
+
149
+ @s3_bucket.stubs(:keys).with(:prefix => @b.backup_name).returns(mock_keys)
150
+ @b.run
151
+ end
152
+ end
153
+
154
+ private
155
+ def stub_io_methods *methods
156
+ methods = [:run_tar, :write_include_file, :push_to_s3, :rotate_remote_backups] if methods.empty?
157
+ methods.each { |io| S3Backup.any_instance.stubs(io) }
158
+ @s3_bucket = mock()
159
+ @s3_bucket.stubs(:put)
160
+ S3Backup.any_instance.stubs(:s3_bucket).returns(@s3_bucket)
161
+ FileUtils.stubs(:rm_rf)
162
+ end
163
+
164
+ end
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+ require 'ruby-debug'
6
+
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+ require 's3-backup'
10
+
11
+ class S3Backup
12
+ # These methods become relevant in tests
13
+ public :tarball_name, :include_file_name
14
+ end
15
+
16
+ class Test::Unit::TestCase
17
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: s3-backup
3
+ version: !ruby/object:Gem::Version
4
+ hash: 7
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 6
9
+ - 0
10
+ version: 0.6.0
11
+ platform: ruby
12
+ authors:
13
+ - Ben Koski
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-17 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: aws_credentials
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 0
32
+ - 6
33
+ - 0
34
+ version: 0.6.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: right_aws
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 15
46
+ segments:
47
+ - 2
48
+ - 0
49
+ - 0
50
+ version: 2.0.0
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: shoulda
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 3
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ type: :development
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: mocha
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ type: :development
80
+ version_requirements: *id004
81
+ description: Easy tar-based backups to S3, with backup rotation, cleanup helpers, and pre-/post-backup hooks.
82
+ email: gems@benkoski.com
83
+ executables: []
84
+
85
+ extensions: []
86
+
87
+ extra_rdoc_files:
88
+ - LICENSE
89
+ - README.rdoc
90
+ files:
91
+ - .document
92
+ - .gitignore
93
+ - LICENSE
94
+ - README.rdoc
95
+ - Rakefile
96
+ - VERSION
97
+ - lib/s3-backup.rb
98
+ - lib/s3-backup/base.rb
99
+ - s3-backup.gemspec
100
+ - test/base_test.rb
101
+ - test/helper.rb
102
+ has_rdoc: true
103
+ homepage: http://github.com/bkoski/s3-backup
104
+ licenses: []
105
+
106
+ post_install_message:
107
+ rdoc_options:
108
+ - --charset=UTF-8
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ hash: 3
117
+ segments:
118
+ - 0
119
+ version: "0"
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ hash: 3
126
+ segments:
127
+ - 0
128
+ version: "0"
129
+ requirements: []
130
+
131
+ rubyforge_project:
132
+ rubygems_version: 1.3.7
133
+ signing_key:
134
+ specification_version: 3
135
+ summary: Easy tar-based backups to S3, with backup rotation, cleanup helpers, and pre-/post-backup hooks.
136
+ test_files:
137
+ - test/base_test.rb
138
+ - test/helper.rb