ey-backup 0.0.3
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/LICENSE +20 -0
- data/README +3 -0
- data/Rakefile +59 -0
- data/TODO +4 -0
- data/bin/eybackup +60 -0
- data/lib/ey-backup.rb +0 -0
- data/lib/ey-backup/mysql_backup.rb +122 -0
- data/spec/ey-backup_spec.rb +7 -0
- data/spec/spec_helper.rb +2 -0
- metadata +63 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Engine Yard Inc.
|
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.
|
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rubygems/specification'
|
4
|
+
require 'date'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
|
7
|
+
GEM = "ey-backup"
|
8
|
+
GEM_VERSION = "0.0.3"
|
9
|
+
AUTHOR = "Ezra Zygmuntowicz"
|
10
|
+
EMAIL = "ezra@engineyard.com"
|
11
|
+
HOMEPAGE = "http://example.com"
|
12
|
+
SUMMARY = "A gem that provides s3 backups for ey-solo slices..."
|
13
|
+
|
14
|
+
spec = Gem::Specification.new do |s|
|
15
|
+
s.name = GEM
|
16
|
+
s.version = GEM_VERSION
|
17
|
+
s.platform = Gem::Platform::RUBY
|
18
|
+
s.has_rdoc = true
|
19
|
+
s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
|
20
|
+
s.summary = SUMMARY
|
21
|
+
s.description = s.summary
|
22
|
+
s.author = AUTHOR
|
23
|
+
s.email = EMAIL
|
24
|
+
s.homepage = HOMEPAGE
|
25
|
+
s.bindir = "bin"
|
26
|
+
s.executables = %w( eybackup )
|
27
|
+
|
28
|
+
# Uncomment this to add a dependency
|
29
|
+
# s.add_dependency "foo"
|
30
|
+
|
31
|
+
s.require_path = 'lib'
|
32
|
+
s.autorequire = GEM
|
33
|
+
s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
|
34
|
+
end
|
35
|
+
|
36
|
+
task :default => :spec
|
37
|
+
|
38
|
+
desc "Run specs"
|
39
|
+
Spec::Rake::SpecTask.new do |t|
|
40
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
41
|
+
t.spec_opts = %w(-fs --color)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
46
|
+
pkg.gem_spec = spec
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "install the gem locally"
|
50
|
+
task :install => [:package] do
|
51
|
+
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "create a gemspec file"
|
55
|
+
task :make_spec do
|
56
|
+
File.open("#{GEM}.gemspec", "w") do |file|
|
57
|
+
file.puts spec.to_ruby
|
58
|
+
end
|
59
|
+
end
|
data/TODO
ADDED
data/bin/eybackup
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'yaml'
|
4
|
+
require "optparse"
|
5
|
+
require 'ey-backup/mysql_backup'
|
6
|
+
|
7
|
+
options = {:config => '/etc/.mysql.backups.yml',
|
8
|
+
:command => :new_backup}
|
9
|
+
|
10
|
+
# Build a parser for the command line arguments
|
11
|
+
opts = OptionParser.new do |opts|
|
12
|
+
opts.version = "0.0.1"
|
13
|
+
|
14
|
+
opts.banner = "Usage: eybackup [-flag] [argument]"
|
15
|
+
opts.define_head "eybackup: backing up your shit since way back when..."
|
16
|
+
opts.separator '*'*80
|
17
|
+
|
18
|
+
opts.on("-l", "--list-backup DATABASE", "List mysql backups for DATABASE") do |db|
|
19
|
+
options[:db] = (db || 'all')
|
20
|
+
options[:command] = :list
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("-n", "--new-backup", "Create new mysql backup") do
|
24
|
+
options[:command] = :new_backup
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("-c", "--config CONFIG", "Use config file.") do |config|
|
28
|
+
options[:config] = config
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("-d", "--download BACKUP_INDEX", "download the backup specified by index. Run eybackup -l to get the index.") do |index|
|
32
|
+
options[:command] = :download
|
33
|
+
options[:index] = index
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("-r", "--restore BACKUP_INDEX", "Download and apply the backup specified by index WARNING! will overwrite the current db with the backup. Run eybackup -l to get the index.") do |index|
|
37
|
+
options[:command] = :restore
|
38
|
+
options[:index] = index
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.parse!
|
44
|
+
|
45
|
+
eyb = nil
|
46
|
+
if File.exist?(options[:config])
|
47
|
+
eyb = EyBackup::MysqlBackup.new(YAML::load(IO.read(options[:config])))
|
48
|
+
end
|
49
|
+
|
50
|
+
case options[:command]
|
51
|
+
when :list
|
52
|
+
eyb.list options[:db], true
|
53
|
+
when :new_backup
|
54
|
+
eyb.new_backup
|
55
|
+
when :download
|
56
|
+
eyb.download(options[:index])
|
57
|
+
when :restore
|
58
|
+
eyb.restore(options[:index])
|
59
|
+
end
|
60
|
+
eyb.cleanup
|
data/lib/ey-backup.rb
ADDED
File without changes
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'aws/s3'
|
2
|
+
require 'date'
|
3
|
+
require 'digest'
|
4
|
+
require 'net/http'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module AWS::S3
|
8
|
+
class S3Object
|
9
|
+
def <=>(other)
|
10
|
+
DateTime.parse(self.about['last-modified']) <=> DateTime.parse(other.about['last-modified'])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module EyBackup
|
16
|
+
def self.get_from_ec2(thing="/")
|
17
|
+
base_url = "http://169.254.169.254/latest/meta-data" + thing
|
18
|
+
url = URI.parse(base_url)
|
19
|
+
req = Net::HTTP::Get.new(url.path)
|
20
|
+
res = Net::HTTP.start(url.host, url.port) {|http|
|
21
|
+
http.request(req)
|
22
|
+
}
|
23
|
+
res.body
|
24
|
+
end
|
25
|
+
class MysqlBackup
|
26
|
+
def initialize(opts={})
|
27
|
+
AWS::S3::Base.establish_connection!(
|
28
|
+
:access_key_id => opts[:aws_secret_id],
|
29
|
+
:secret_access_key => opts[:aws_secret_key]
|
30
|
+
)
|
31
|
+
@dbuser = opts[:dbuser]
|
32
|
+
@dbpass = opts[:dbpass]
|
33
|
+
@databases = opts[:databases]
|
34
|
+
@keep = opts[:keep]
|
35
|
+
@bucket = "ey-backup-#{Digest::SHA1.hexdigest(opts[:aws_secret_id])[0..11]}"
|
36
|
+
@tmpname = "#{Time.now.strftime("%Y-%m-%dT%H:%M:%S").gsub(/:/, '-')}.sql.gz"
|
37
|
+
@id = EyBackup.get_from_ec2('/instance-id')
|
38
|
+
FileUtils.mkdir_p '/mnt/tmp'
|
39
|
+
begin
|
40
|
+
AWS::S3::Bucket.create @bucket
|
41
|
+
rescue AWS::S3::BucketAlreadyExists
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def new_backup
|
46
|
+
@databases.each do |db|
|
47
|
+
backup_database(db)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def backup_database(database)
|
52
|
+
mysqlcmd = "mysqldump -u #{@dbuser} -p'#{@dbpass}' #{database} | gzip - > /mnt/tmp/#{database}.#{@tmpname}"
|
53
|
+
if system(mysqlcmd)
|
54
|
+
AWS::S3::S3Object.store(
|
55
|
+
"/#{@id}.#{database}/#{database}.#{@tmpname}",
|
56
|
+
open("/mnt/tmp/#{database}.#{@tmpname}"),
|
57
|
+
@bucket,
|
58
|
+
:access => :private
|
59
|
+
)
|
60
|
+
FileUtils.rm "/mnt/tmp/#{database}.#{@tmpname}"
|
61
|
+
puts "successful backup: #{database}.#{@tmpname}"
|
62
|
+
else
|
63
|
+
raise "Unable to dump database#{database} wtf?"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def download(index)
|
68
|
+
idx, db = index.split(":")
|
69
|
+
obj = list(db)[idx.to_i]
|
70
|
+
puts "downloading: #{normalize_name(obj)}"
|
71
|
+
File.open(normalize_name(obj), 'wb') do |f|
|
72
|
+
print "."
|
73
|
+
obj.value {|chunk| f.write chunk }
|
74
|
+
end
|
75
|
+
puts
|
76
|
+
puts "finished"
|
77
|
+
normalize_name(obj)
|
78
|
+
end
|
79
|
+
|
80
|
+
def restore(index)
|
81
|
+
name = download(index)
|
82
|
+
db = name.split('.').first
|
83
|
+
cmd = "gunzip -c #{name} | mysql -u #{@dbuser} -p'#{@dbpass}' #{db}"
|
84
|
+
if system(cmd)
|
85
|
+
puts "successfully restored backup: #{name}"
|
86
|
+
else
|
87
|
+
puts "FAIL"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def cleanup
|
92
|
+
list('all',false)[0...-(@keep*@databases.size)].each{|o|
|
93
|
+
puts "deleting: #{o.key}"
|
94
|
+
o.delete
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
def normalize_name(obj)
|
99
|
+
obj.key.gsub(/^.*?\//, '')
|
100
|
+
end
|
101
|
+
|
102
|
+
def list(database='all', printer = false)
|
103
|
+
puts "listing #{database} database" if printer
|
104
|
+
backups = []
|
105
|
+
if database == 'all'
|
106
|
+
@databases.each do |db|
|
107
|
+
backups << AWS::S3::Bucket.objects(@bucket, :prefix => "#{@id}.#{db}")
|
108
|
+
end
|
109
|
+
backups = backups.flatten.sort
|
110
|
+
else
|
111
|
+
backups = AWS::S3::Bucket.objects(@bucket, :prefix => "#{@id}.#{database}").sort
|
112
|
+
end
|
113
|
+
if printer
|
114
|
+
backups.each_with_index do |b,i|
|
115
|
+
puts "#{i}:#{database} #{normalize_name(b)}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
backups
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ey-backup
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ezra Zygmuntowicz
|
8
|
+
autorequire: ey-backup
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-19 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A gem that provides s3 backups for ey-solo slices...
|
17
|
+
email: ezra@engineyard.com
|
18
|
+
executables:
|
19
|
+
- eybackup
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
- LICENSE
|
25
|
+
- TODO
|
26
|
+
files:
|
27
|
+
- LICENSE
|
28
|
+
- README
|
29
|
+
- Rakefile
|
30
|
+
- TODO
|
31
|
+
- lib/ey-backup
|
32
|
+
- lib/ey-backup/mysql_backup.rb
|
33
|
+
- lib/ey-backup.rb
|
34
|
+
- spec/ey-backup_spec.rb
|
35
|
+
- spec/spec_helper.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://example.com
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
requirements: []
|
56
|
+
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 1.3.1
|
59
|
+
signing_key:
|
60
|
+
specification_version: 2
|
61
|
+
summary: A gem that provides s3 backups for ey-solo slices...
|
62
|
+
test_files: []
|
63
|
+
|