Fingertips-fingertips-backup 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ begin
11
+ require 'jeweler'
12
+ Jeweler::Tasks.new do |s|
13
+ s.name = "fingertips-backup"
14
+ s.description = "A simple tool to backup MySQL databases and files to an Amazon EBS instance through an EC2 instance."
15
+ s.summary = "A simple tool to backup a webserver."
16
+ s.homepage = "http://fingertips.github.com"
17
+
18
+ s.authors = ["Eloy Duran"]
19
+ s.email = "eloy@fngtps.com"
20
+
21
+ s.add_dependency 'Fingertips-executioner'
22
+ s.add_dependency 'aws-s3'
23
+ end
24
+ rescue LoadError
25
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 1
3
+ :patch: 0
4
+ :major: 0
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if config = ARGV.first
4
+ $:.unshift(File.expand_path('../../lib', __FILE__)) if $0 == __FILE__
5
+ require 'backup'
6
+ Fingertips::Backup.new(config).run!
7
+ else
8
+ puts "Usage: #{$0} /path/to/config.yml"
9
+ exit 1
10
+ end
@@ -0,0 +1,112 @@
1
+ require "yaml"
2
+
3
+ require "rubygems"
4
+ require "executioner"
5
+ require "aws/s3"
6
+
7
+ require "logger"
8
+ require "ec2"
9
+
10
+ module Fingertips
11
+ class Backup
12
+ include Executioner
13
+ Executioner::SEARCH_PATHS << '/usr/local/mysql/bin'
14
+ executable :mysql
15
+ executable :mysqldump
16
+ executable :rsync
17
+ executable :ssh, :switch_stdout_and_stderr => true
18
+
19
+ MYSQL_DUMP_DIR = '/tmp/mysql_backup_dumps'
20
+
21
+ attr_reader :config, :ec2, :logger, :s3
22
+ attr_accessor :ec2_instance_id
23
+
24
+ def initialize(config_file)
25
+ @logger = Executioner.logger = Fingertips::Logger.new
26
+ @config = YAML.load(File.read(config_file))
27
+ @ec2 = Fingertips::EC2.new(@config['ec2']['zone'], @config['ec2']['private_key_file'], @config['ec2']['certificate_file'], @config['java_home'])
28
+ @s3 = AWS::S3::Base.establish_connection!(:access_key_id => @config['s3']['access_key_id'], :secret_access_key => @config['s3']['secret_access_key'])
29
+ rescue Exception => e
30
+ failed(e)
31
+ end
32
+
33
+ def finished
34
+ @logger.debug "The backup finished."
35
+ publish_log!
36
+ end
37
+
38
+ def failed(exception)
39
+ @logger.debug "#{exception.message} #{exception.backtrace.join("\n")}"
40
+ @logger.debug "[!] The backup has failed."
41
+ publish_log!
42
+ raise exception
43
+ end
44
+
45
+ def write_feed!
46
+ @logger.write_feed(@config['log_feed'])
47
+ end
48
+
49
+ def publish_log!
50
+ write_feed!
51
+ AWS::S3::S3Object.store('backup_feed.xml', File.open(@config['log_feed']), @config['s3']['bucket'], :content_type => 'application/atom+xml', :access => :public_read)
52
+ end
53
+
54
+ def run!
55
+ begin
56
+ create_mysql_dump!
57
+ bring_backup_volume_online!
58
+ sync!
59
+ rescue Exception => e
60
+ failed(e)
61
+ ensure
62
+ take_backup_volume_offline! if ec2_instance_id
63
+ end
64
+ finished
65
+ end
66
+
67
+ def bring_backup_volume_online!
68
+ launch_ec2_instance!
69
+ attach_backup_volume!
70
+ mount_backup_volume!
71
+ end
72
+
73
+ def launch_ec2_instance!
74
+ @ec2_instance_id = @ec2.run_instance(@config['ec2']['ami'], @config['ec2']['keypair_name'])
75
+ sleep 5 until @ec2.running?(@ec2_instance_id)
76
+ end
77
+
78
+ def attach_backup_volume!
79
+ @ec2.attach_volume(@config['ec2']['ebs'], ec2_instance_id, "/dev/sdh")
80
+ sleep 2.5 until @ec2.attached?(@config['ec2']['ebs'])
81
+ end
82
+
83
+ def mount_backup_volume!
84
+ ssh "-o 'StrictHostKeyChecking=no' -i '#{@config['ec2']['keypair_file']}' root@#{ec2_host} 'mkdir /mnt/data-store && mount /dev/sdh /mnt/data-store'"
85
+ end
86
+
87
+ def take_backup_volume_offline!
88
+ @ec2.terminate_instance @ec2_instance_id
89
+ end
90
+
91
+ def ec2_host
92
+ @ec2_host ||= @ec2.host_of_instance(ec2_instance_id)
93
+ end
94
+
95
+ def mysql_databases
96
+ @mysql_databases ||= mysql('-u root --batch --skip-column-names -e "show databases"').strip.split("\n")
97
+ end
98
+
99
+ def create_mysql_dump!
100
+ FileUtils.rm_rf(MYSQL_DUMP_DIR)
101
+ FileUtils.mkdir_p(MYSQL_DUMP_DIR)
102
+
103
+ mysql_databases.each do |database|
104
+ mysqldump("-u root #{database} --add-drop-table > '#{File.join(MYSQL_DUMP_DIR, database)}.sql'")
105
+ end
106
+ end
107
+
108
+ def sync!
109
+ rsync "-avz -e \"ssh -i '#{@config['ec2']['keypair_file']}'\" '#{MYSQL_DUMP_DIR}' '#{@config['backup'].join("' '")}' root@#{ec2_host}:/mnt/data-store"
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,84 @@
1
+ require "rubygems"
2
+ require "executioner"
3
+
4
+ module Fingertips
5
+ class EC2
6
+ PRIVATE_KEY_FILE = '/Volumes/Fingertips Confidential/aws/fingertips/pk-6LN7EWTYKIDRU25OJYMTY6P75S43WA45.pem'
7
+ CERTIFICATE_FILE = '/Volumes/Fingertips Confidential/aws/fingertips/cert-6LN7EWTYKIDRU25OJYMTY6P75S43WA45.pem'
8
+
9
+ HOME = '/opt/ec2'
10
+ BIN = File.join(HOME, 'bin')
11
+
12
+ include Executioner
13
+ Executioner::SEARCH_PATHS << BIN
14
+
15
+ attr_reader :zone, :private_key_file, :certificate_file, :java_home
16
+
17
+ def initialize(zone, private_key_file, certificate_file, java_home)
18
+ @zone, @private_key_file, @certificate_file, @java_home = zone, private_key_file, certificate_file, java_home
19
+ end
20
+
21
+ def env
22
+ @env ||= {
23
+ 'JAVA_HOME' => @java_home,
24
+ 'EC2_HOME' => HOME,
25
+ 'EC2_PRIVATE_KEY' => @private_key_file,
26
+ 'EC2_CERT' => @certificate_file,
27
+ 'EC2_URL' => "https://#{@zone[0..-2]}.ec2.amazonaws.com"
28
+ }
29
+ end
30
+
31
+ # EC2
32
+
33
+ def run_instance(ami, keypair_name, options = {})
34
+ ec2_run_instances("#{ami} -z #{@zone} -k #{keypair_name}", :env => env)[1][1]
35
+ end
36
+
37
+ def describe_instance(instance_id)
38
+ ec2_describe_instances(instance_id, :env => env).detect { |line| line[1] == instance_id }
39
+ end
40
+
41
+ def terminate_instance(instance_id)
42
+ ec2_terminate_instances(instance_id, :env => env)[0][3]
43
+ end
44
+
45
+ def running?(instance_id)
46
+ describe_instance(instance_id)[5] == 'running'
47
+ end
48
+
49
+ def host_of_instance(instance_id)
50
+ describe_instance(instance_id)[3]
51
+ end
52
+
53
+ # EBS
54
+
55
+ def attach_volume(volume_id, instance_id, device)
56
+ ec2_attach_volume("#{volume_id} -i #{instance_id} -d #{device}", :env => env)
57
+ end
58
+
59
+ def describe_volume(volume_id)
60
+ ec2_describe_volumes(volume_id, :env => env).detect { |line| line[0] == 'ATTACHMENT' && line[1] == volume_id }
61
+ end
62
+
63
+ def attached?(volume_id)
64
+ describe_volume(volume_id)[4] == 'attached'
65
+ end
66
+
67
+ private
68
+
69
+ executable 'ec2-run-instances'
70
+ executable 'ec2-describe-instances'
71
+ executable 'ec2-terminate-instances'
72
+
73
+ executable 'ec2-attach-volume'
74
+ executable 'ec2-describe-volumes'
75
+
76
+ def parse(text)
77
+ text.strip.split("\n").map { |line| line.split("\t") }
78
+ end
79
+
80
+ def execute(command, options = {})
81
+ parse(super)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,48 @@
1
+ require "builder"
2
+ require "rss"
3
+
4
+ module Fingertips
5
+ class Logger
6
+ class << self
7
+ attr_accessor :print
8
+ end
9
+ self.print = true
10
+
11
+ attr_reader :logged
12
+
13
+ def initialize
14
+ @logged = []
15
+ end
16
+
17
+ def debug(message)
18
+ @logged << message
19
+ puts(message) if self.class.print
20
+ end
21
+
22
+ def feed
23
+ output = ''
24
+ xml = Builder::XmlMarkup.new(:target => output, :indent => 2)
25
+ xml.instruct!
26
+ xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
27
+ xml.id "http://github.com/Fingertips/fingertips-backup"
28
+ xml.link "rel" => "self", "href" => "http://github.com/Fingertips/fingertips-backup"
29
+ xml.updated Time.now.iso8601
30
+ xml.author { xml.name "Backup" }
31
+ xml.title "Backup feed"
32
+
33
+ xml.entry do
34
+ xml.id "http://github.com/Fingertips/fingertips-backup"
35
+ xml.updated Time.now.iso8601
36
+ xml.title @logged.last
37
+ xml.summary @logged.last
38
+ xml.content @logged.join("\n")
39
+ end
40
+ end
41
+ output
42
+ end
43
+
44
+ def write_feed(path)
45
+ File.open(path, 'w') { |f| f << feed }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,222 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "Fingertips::Backup, in general" do
4
+ before do
5
+ @config = YAML.load(fixture_read('config.yml'))
6
+ @backup = Fingertips::Backup.new(fixture('config.yml'))
7
+ end
8
+
9
+ it "should have instantiated a Logger and assigned it to Executioner" do
10
+ @backup.logger.should.be.instance_of Fingertips::Logger
11
+ Executioner.logger.should.be @backup.logger
12
+ end
13
+
14
+ it "should return the config" do
15
+ @backup.config.should == @config
16
+ end
17
+
18
+ it "should return a configured Fingertips::EC2 instance" do
19
+ @backup.ec2.should.be.instance_of Fingertips::EC2
20
+ @backup.ec2.zone.should == @config['ec2']['zone']
21
+ @backup.ec2.private_key_file.should == @config['ec2']['private_key_file']
22
+ @backup.ec2.certificate_file.should == @config['ec2']['certificate_file']
23
+ end
24
+
25
+ it "should return a configured AWS::S3 connection" do
26
+ @backup.s3.should.be.instance_of AWS::S3::Connection
27
+ @backup.s3.access_key_id.should == @config['s3']['access_key_id']
28
+ @backup.s3.secret_access_key.should == @config['s3']['secret_access_key']
29
+ end
30
+
31
+ it "should return a list of all MySQL databases" do
32
+ databases = @backup.mysql_databases
33
+ databases.should.include 'information_schema'
34
+ databases.should == `mysql -u root --batch --skip-column-names -e "show databases"`.strip.split("\n")
35
+ end
36
+
37
+ it "should return the host of the EC2 instance" do
38
+ @backup.ec2_instance_id = 'i-nonexistant'
39
+ @backup.ec2.expects(:host_of_instance).with('i-nonexistant').returns('instance.amazon.com')
40
+ @backup.ec2_host.should == 'instance.amazon.com'
41
+ end
42
+
43
+ it "should perform a full run and report that the backup finished" do
44
+ @backup.expects(:create_mysql_dump!)
45
+ @backup.expects(:bring_backup_volume_online!)
46
+ @backup.ec2_instance_id = 'i-nonexistant'
47
+ @backup.expects(:sync!)
48
+ @backup.expects(:take_backup_volume_offline!)
49
+ @backup.expects(:finished)
50
+
51
+ @backup.run!
52
+ end
53
+
54
+ it "should catch any type of exception that was raised during initialization and call #failed" do
55
+ Fingertips::Backup.any_instance.expects(:failed).with { |exception| exception.backtrace.to_s.include?('initialize') }
56
+ backup = Fingertips::Backup.new(nil)
57
+ end
58
+
59
+ it "should catch any type of exception that was raised during the run and terminate the EC2 instance if one was launched and call #failed" do
60
+ @backup.stubs(:finished)
61
+
62
+ @backup.ec2_instance_id = 'i-nonexistant'
63
+ @backup.stubs(:create_mysql_dump!).raises 'oh noes!'
64
+ @backup.expects(:failed).with { |exception| exception.message == 'oh noes!' }
65
+ @backup.expects(:take_backup_volume_offline!)
66
+ @backup.run!
67
+ end
68
+
69
+ it "should report that the backup failed and re-raise the exception" do
70
+ exception = nil
71
+ begin; raise 'oh noes!'; rescue Exception => e; exception = e; end
72
+
73
+ @backup.expects(:publish_log!)
74
+ lambda { @backup.failed(exception) }.should.raise exception.class
75
+ @backup.logger.logged.first.should.include 'oh noes!'
76
+ @backup.logger.logged.last.should == "[!] The backup has failed."
77
+ end
78
+
79
+ it "should report that the backup has finished" do
80
+ @backup.expects(:publish_log!)
81
+ @backup.finished
82
+ @backup.logger.logged.last.should == "The backup finished."
83
+ end
84
+
85
+ it "should write the feed of the current log" do
86
+ @backup.logger.expects(:write_feed).with(@config['log_feed'])
87
+ @backup.write_feed!
88
+ end
89
+
90
+ it "should push the log to S3" do
91
+ @backup.expects(:write_feed!)
92
+
93
+ file = stub('Feed file')
94
+ File.expects(:open).with(@config['log_feed']).returns(file)
95
+
96
+ AWS::S3::S3Object.expects(:store).with('backup_feed.xml', file, @config['s3']['bucket'], :content_type => 'application/atom+xml', :access => :public_read)
97
+
98
+ @backup.publish_log!
99
+ end
100
+ end
101
+
102
+ describe "Fingertips::Backup, concerning the MySQL backup" do
103
+ before do
104
+ @backup = Fingertips::Backup.new(fixture('config.yml'))
105
+ @backup.stubs(:mysql_databases).returns(%w{ information_schema })
106
+ end
107
+
108
+ after do
109
+ FileUtils.rm_rf(Fingertips::Backup::MYSQL_DUMP_DIR)
110
+ end
111
+
112
+ it "should first remove the tmp mysql dump dir" do
113
+ # have to use at_least_once because it's also called in the after filter
114
+ FileUtils.expects(:rm_rf).with(Fingertips::Backup::MYSQL_DUMP_DIR).at_least_once
115
+ @backup.create_mysql_dump!
116
+ end
117
+
118
+ it "should create the tmp mysql dump dir" do
119
+ @backup.create_mysql_dump!
120
+ File.should.exist Fingertips::Backup::MYSQL_DUMP_DIR
121
+ File.should.be.directory Fingertips::Backup::MYSQL_DUMP_DIR
122
+ end
123
+
124
+ it "should dump each database into its own file" do
125
+ @backup.create_mysql_dump!
126
+
127
+ actual = strip_comments(`mysqldump -u root information_schema --add-drop-table`)
128
+ dump = strip_comments(File.read(File.join(Fingertips::Backup::MYSQL_DUMP_DIR, 'information_schema.sql')))
129
+
130
+ actual.should == dump
131
+ end
132
+
133
+ private
134
+
135
+ def strip_comments(sql)
136
+ sql.gsub(/^--.*?$/, '').strip
137
+ end
138
+ end
139
+
140
+ describe "Fingertips::Backup, concerning the EBS volume" do
141
+ before do
142
+ @backup = Fingertips::Backup.new(fixture('config.yml'))
143
+ @config = @backup.config
144
+ @ec2 = @backup.ec2
145
+
146
+ @backup.stubs(:sleep)
147
+
148
+ @ec2.stubs(:run_instance).returns("i-nonexistant")
149
+ @ec2.stubs(:running?).returns(true)
150
+
151
+ @ec2.stubs(:attach_volume)
152
+ @ec2.stubs(:attached?).returns(true)
153
+ end
154
+
155
+ it "should run an EC2 instance and wait till it's online" do
156
+ @backup.stubs(:mount_backup_volume!)
157
+
158
+ @ec2.expects(:run_instance).with('ami-nonexistant', 'fingertips').returns("i-nonexistant")
159
+
160
+ @ec2.expects(:running?).with do |id|
161
+ # next time it's queried it will be running
162
+ def @ec2.running?(id)
163
+ true
164
+ end
165
+
166
+ id == "i-nonexistant"
167
+ end.returns(false)
168
+ @backup.expects(:sleep).with(5).once
169
+
170
+ @backup.launch_ec2_instance!
171
+ @backup.ec2_instance_id.should == "i-nonexistant"
172
+ end
173
+
174
+ it "should attach the existing EBS instance and wait till it's online" do
175
+ @backup.ec2_instance_id = 'i-nonexistant'
176
+ @ec2.expects(:attach_volume).with("vol-nonexistant", "i-nonexistant", "/dev/sdh")
177
+
178
+ @ec2.expects(:attached?).with do |id|
179
+ # next time it's queried it will be attached
180
+ def @ec2.attached?(id)
181
+ true
182
+ end
183
+
184
+ id == "vol-nonexistant"
185
+ end.returns(false)
186
+ @backup.expects(:sleep).with(2.5).once
187
+
188
+ @backup.attach_backup_volume!
189
+ end
190
+
191
+ it "should mount the attached EBS volume on the running instance" do
192
+ @backup.stubs(:ec2_host).returns('instance.amazon.com')
193
+ @backup.expects(:ssh).with("-o 'StrictHostKeyChecking=no' -i '#{@config['ec2']['keypair_file']}' root@instance.amazon.com 'mkdir /mnt/data-store && mount /dev/sdh /mnt/data-store'")
194
+ @backup.mount_backup_volume!
195
+ end
196
+
197
+ it "should run all steps to bring the backup volume online" do
198
+ @backup.expects(:launch_ec2_instance!)
199
+ @backup.expects(:attach_backup_volume!)
200
+ @backup.expects(:mount_backup_volume!)
201
+
202
+ @backup.bring_backup_volume_online!
203
+ end
204
+
205
+ it "should take the backup volume offline by terminating the EC2 instance" do
206
+ @backup.ec2_instance_id = 'i-nonexistant'
207
+ @backup.ec2.expects(:terminate_instance).with('i-nonexistant')
208
+ @backup.take_backup_volume_offline!
209
+ end
210
+ end
211
+
212
+ describe "Fingertips::Backup, concerning syncing" do
213
+ before do
214
+ @backup = Fingertips::Backup.new(fixture('config.yml'))
215
+ @backup.stubs(:ec2_host).returns('instance.amazon.com')
216
+ end
217
+
218
+ it "should sync all configured paths and the mysql dump dir to the backup volume" do
219
+ @backup.expects(:rsync).with("-avz -e \"ssh -i '#{@backup.config['ec2']['keypair_file']}'\" '#{Fingertips::Backup::MYSQL_DUMP_DIR}' '/var/www/apps' '/root' root@instance.amazon.com:/mnt/data-store")
220
+ @backup.sync!
221
+ end
222
+ end
@@ -0,0 +1,80 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "Fingertips::EC2, in general" do
4
+ before do
5
+ @ami = 'ami-nonexistant'
6
+ @instance = Fingertips::EC2.new('eu-west-1a', '/path/to/private_key_file', '/path/to/certificate_file', '/Library/Java/Home')
7
+ end
8
+
9
+ it "should return the right env variables to be able to use the Amazon CLI tools" do
10
+ @instance.env.should == {
11
+ 'JAVA_HOME' => '/Library/Java/Home',
12
+ 'EC2_HOME' => '/opt/ec2',
13
+ 'EC2_PRIVATE_KEY' => '/path/to/private_key_file',
14
+ 'EC2_CERT' => '/path/to/certificate_file',
15
+ 'EC2_URL' => 'https://eu-west-1.ec2.amazonaws.com'
16
+ }
17
+ end
18
+
19
+ it "should return an array of lines splitted at tabs" do
20
+ @instance.send(:parse, " line1item1\tline1item2\nline2item1\tline2item2 ").should ==
21
+ [['line1item1', 'line1item2'], ['line2item1', 'line2item2']]
22
+ end
23
+
24
+ it "should override #execute so it returns the response parsed" do
25
+ @instance.send(:execute, 'ls').should == @instance.send(:parse, `ls`)
26
+ end
27
+ end
28
+
29
+ describe "Fingertips::EC2, concerning the pre-defined commands" do
30
+ before do
31
+ @instance = Fingertips::EC2.new('eu-west-1a', '/path/to/private_key_file', '/path/to/certificate_file', '/Library/Java/Home')
32
+ @options = { :switch_stdout_and_stderr => false, :env => @instance.env }
33
+ end
34
+
35
+ it "should run an instance of the given AMI with the given options and return the instance ID" do
36
+ expect_call('run-instances', "ami-nonexistant -z eu-west-1a -k fingertips")
37
+ @instance.run_instance('ami-nonexistant', 'fingertips').should == 'i-0992a760'
38
+ end
39
+
40
+ it "should return the status of an instance" do
41
+ expect_call('describe-instances', 'i-nonexistant')
42
+ @instance.describe_instance("i-nonexistant").should == @instance.send(:parse, fixture_read('describe-instances'))[1]
43
+ end
44
+
45
+ it "should terminate an instance" do
46
+ expect_call('terminate-instances', 'i-nonexistant')
47
+ @instance.terminate_instance('i-nonexistant').should == 'shutting-down'
48
+ end
49
+
50
+ it "should return if an instance is running" do
51
+ expect_call('describe-instances', 'i-nonexistant')
52
+ @instance.running?('i-nonexistant').should.be true
53
+ end
54
+
55
+ it "should return the public host address of an instance" do
56
+ expect_call('describe-instances', 'i-nonexistant')
57
+ @instance.host_of_instance('i-nonexistant').should == 'ec2-174-129-88-205.compute-1.amazonaws.com'
58
+ end
59
+
60
+ it "should attach an EBS volume to an EC2 instance" do
61
+ expect_call('attach-volume', 'vol-nonexistant -i i-nonexistant -d /dev/sdh')
62
+ @instance.attach_volume('vol-nonexistant', 'i-nonexistant', '/dev/sdh')
63
+ end
64
+
65
+ it "should return the status of a volume" do
66
+ expect_call('describe-volumes', 'vol-nonexistant')
67
+ @instance.describe_volume('vol-nonexistant').should == @instance.send(:parse, fixture_read('describe-volumes'))[1]
68
+ end
69
+
70
+ it "should return if a volume is attached" do
71
+ expect_call('describe-volumes', 'vol-nonexistant')
72
+ @instance.attached?('vol-nonexistant').should.be true
73
+ end
74
+
75
+ private
76
+
77
+ def expect_call(name, args)
78
+ @instance.expects(:execute).with("/opt/ec2/bin/ec2-#{name} #{args}", @options).returns(@instance.send(:parse, fixture_read(name)))
79
+ end
80
+ end
@@ -0,0 +1 @@
1
+ ATTACHMENT vol-21e70e48 i-ad2f19c4 /dev/sdh attaching 2009-07-09T13:46:45+0000
@@ -0,0 +1,17 @@
1
+ java_home: /Library/Java/Home
2
+ log_feed: /var/www/backup_feed/backup_feed.xml
3
+ s3:
4
+ bucket: backup_feed
5
+ access_key_id: ANID
6
+ secret_access_key: SOSECRET
7
+ ec2:
8
+ zone: eu-west-1a
9
+ ebs: vol-nonexistant
10
+ ami: ami-nonexistant
11
+ keypair_name: fingertips
12
+ keypair_file: /Volumes/Fingertips Confidential/aws/fingertips/keys/fingertips
13
+ private_key_file: /Volumes/Fingertips Confidential/aws/fingertips/pk-6LN7EWTYKIDRU25OJYMTY6P75S43WA45.pem
14
+ certificate_file: /Volumes/Fingertips Confidential/aws/fingertips/cert-6LN7EWTYKIDRU25OJYMTY6P75S43WA45.pem
15
+ backup:
16
+ - /var/www/apps
17
+ - /root
@@ -0,0 +1,2 @@
1
+ RESERVATION r-bfda9cd6 895951156444 default
2
+ INSTANCE i-nonexistant ami-0d729464 ec2-174-129-88-205.compute-1.amazonaws.com domU-12-31-39-00-84-B7.compute-1.internal running fingertips 0 m1.small 2009-07-08T13:06:22+0000 us-east-1b aki-a71cf9ce ari-a51cf9cc monitoring-disabled
@@ -0,0 +1,2 @@
1
+ VOLUME vol-nonexistant 1 us-east-1d in-use 2009-07-09T13:41:40+0000
2
+ ATTACHMENT vol-nonexistant i-ad2f19c4 /dev/sdh attached 2009-07-09T13:46:45+0000
@@ -0,0 +1,2 @@
1
+ RESERVATION r-ffd09796 895951156444 default
2
+ INSTANCE i-0992a760 ami-0d729464 pending fingertips 0 m1.small 2009-07-07T14:52:22+0000 us-east-1b aki-a71cf9cari-a51cf9cc monitoring-disabled
@@ -0,0 +1 @@
1
+ INSTANCE i-7b576012 running shutting-down
@@ -0,0 +1,32 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "Fingertips::Logger" do
4
+ TMP_FEED = '/tmp/backup_feed'
5
+
6
+ before do
7
+ @logger = Fingertips::Logger.new
8
+ @logger.debug "foo"
9
+ @logger.debug "bar"
10
+ @logger.debug "baz"
11
+ end
12
+
13
+ after do
14
+ FileUtils.rm_rf TMP_FEED
15
+ end
16
+
17
+ it "should store any debug messages" do
18
+ @logger.logged.should == %w{ foo bar baz }
19
+ end
20
+
21
+ it "should create a feed" do
22
+ @logger.feed.should.include "foo\nbar\nbaz"
23
+ end
24
+
25
+ it "should write the feed to a given path" do
26
+ now = Time.now
27
+ Time.stubs(:now).returns(now)
28
+
29
+ @logger.write_feed(TMP_FEED)
30
+ File.read(TMP_FEED).should == @logger.feed
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ require "rubygems"
2
+ require "test/spec"
3
+ require "mocha"
4
+
5
+ $:.unshift File.expand_path('../../lib', __FILE__)
6
+ require "backup"
7
+
8
+ Fingertips::Logger.print = false
9
+
10
+ FIXTURES = File.expand_path('../fixtures', __FILE__)
11
+
12
+ class Test::Unit::TestCase
13
+ def fixture(fixture)
14
+ File.join(FIXTURES, fixture)
15
+ end
16
+
17
+ def fixture_read(fixture)
18
+ File.read(self.fixture(fixture))
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Fingertips-fingertips-backup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Eloy Duran
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-18 00:00:00 -07:00
13
+ default_executable: fingertips-backup
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: Fingertips-executioner
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: aws-s3
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: A simple tool to backup MySQL databases and files to an Amazon EBS instance through an EC2 instance.
36
+ email: eloy@fngtps.com
37
+ executables:
38
+ - fingertips-backup
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - Rakefile
45
+ - VERSION.yml
46
+ - bin/fingertips-backup
47
+ - lib/backup.rb
48
+ - lib/ec2.rb
49
+ - lib/logger.rb
50
+ - test/backup_test.rb
51
+ - test/ec2_test.rb
52
+ - test/fixtures/attach-volume
53
+ - test/fixtures/config.yml
54
+ - test/fixtures/describe-instances
55
+ - test/fixtures/describe-volumes
56
+ - test/fixtures/run-instances
57
+ - test/fixtures/terminate-instances
58
+ - test/logger_test.rb
59
+ - test/test_helper.rb
60
+ has_rdoc: true
61
+ homepage: http://fingertips.github.com
62
+ licenses:
63
+ post_install_message:
64
+ rdoc_options:
65
+ - --charset=UTF-8
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ version:
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.3.5
84
+ signing_key:
85
+ specification_version: 2
86
+ summary: A simple tool to backup a webserver.
87
+ test_files:
88
+ - test/backup_test.rb
89
+ - test/ec2_test.rb
90
+ - test/logger_test.rb
91
+ - test/test_helper.rb