Fingertips-fingertips-backup 0.1.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,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