ey_cloud_server 1.4.5 → 1.4.26
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/bin/binary_log_purge +10 -2
- data/bin/ey-snapshots +7 -1
- data/bin/eybackup +13 -1
- data/lib/ey-flex.rb +18 -7
- data/lib/ey-flex/big-brother.rb +2 -2
- data/lib/ey-flex/bucket_minder.rb +46 -160
- data/lib/ey-flex/ec2.rb +17 -0
- data/lib/ey-flex/snapshot_minder.rb +93 -171
- data/lib/ey_backup.rb +84 -48
- data/lib/ey_backup/backend.rb +34 -0
- data/lib/ey_backup/backup_set.rb +70 -63
- data/lib/ey_backup/base.rb +0 -5
- data/lib/ey_backup/cli.rb +26 -6
- data/lib/ey_backup/database.rb +48 -0
- data/lib/ey_backup/dumper.rb +15 -31
- data/lib/ey_backup/engine.rb +7 -17
- data/lib/ey_backup/engines/mysql_engine.rb +24 -16
- data/lib/ey_backup/engines/postgresql_engine.rb +26 -20
- data/lib/ey_backup/loader.rb +13 -33
- data/lib/ey_backup/processors/gpg_encryptor.rb +3 -20
- data/lib/ey_backup/processors/gzipper.rb +0 -29
- data/lib/ey_backup/processors/splitter.rb +22 -34
- data/lib/ey_backup/spawner.rb +7 -13
- data/lib/ey_cloud_server.rb +1 -1
- data/lib/{ey-flex → ey_cloud_server}/version.rb +1 -1
- data/spec/big-brother_spec.rb +12 -0
- data/spec/bucket_minder_spec.rb +113 -0
- data/spec/config-example.yml +11 -0
- data/spec/ey_api_spec.rb +63 -0
- data/spec/ey_backup/backend_spec.rb +12 -0
- data/spec/ey_backup/backup_spec.rb +54 -0
- data/spec/ey_backup/cli_spec.rb +35 -0
- data/spec/ey_backup/mysql_backups_spec.rb +208 -0
- data/spec/ey_backup/postgres_backups_spec.rb +106 -0
- data/spec/ey_backup/spec_helper.rb +5 -0
- data/spec/fakefs_hax.rb +50 -0
- data/spec/gpg.public +0 -0
- data/spec/gpg.sekrit +0 -0
- data/spec/helpers.rb +270 -0
- data/spec/snapshot_minder_spec.rb +68 -0
- data/spec/spec_helper.rb +31 -0
- metadata +286 -53
data/lib/ey_backup.rb
CHANGED
@@ -6,74 +6,108 @@ require 'open4'
|
|
6
6
|
require 'zlib'
|
7
7
|
require 'ostruct'
|
8
8
|
require 'fileutils'
|
9
|
+
require 'fog'
|
9
10
|
|
10
11
|
module EY
|
11
12
|
module Backup
|
12
|
-
|
13
|
-
|
13
|
+
class << self
|
14
|
+
attr_accessor :logger
|
15
|
+
attr_accessor :tmp_dir
|
16
|
+
end
|
14
17
|
|
15
|
-
def run(argv = ARGV)
|
18
|
+
def self.run(argv = ARGV)
|
16
19
|
options = CLI.run(argv)
|
17
|
-
|
18
|
-
dispatch(options)
|
20
|
+
Runner.new(options).run
|
19
21
|
end
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
class Runner
|
24
|
+
def initialize(options)
|
25
|
+
@options = options
|
26
|
+
end
|
24
27
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
else
|
29
|
-
#list a single database's backups
|
30
|
-
BackupSet.list(options[:db])
|
28
|
+
def run
|
29
|
+
setup
|
30
|
+
dispatch
|
31
31
|
end
|
32
|
-
end
|
33
32
|
|
34
|
-
|
35
|
-
Loader.run(options)
|
36
|
-
end
|
33
|
+
# COMMANDS
|
37
34
|
|
38
|
-
|
39
|
-
|
40
|
-
|
35
|
+
def new_backup
|
36
|
+
Dumper.run(databases, @options[:split_size])
|
37
|
+
end
|
41
38
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
setup_minder(options)
|
46
|
-
setup_engine(options)
|
47
|
-
end
|
39
|
+
def list
|
40
|
+
BackupSet.list(databases)
|
41
|
+
end
|
48
42
|
|
49
|
-
|
50
|
-
|
51
|
-
|
43
|
+
def restore
|
44
|
+
databases.each do |database|
|
45
|
+
Loader.run(database, @options[:index])
|
46
|
+
end
|
47
|
+
end
|
52
48
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
@logger ||= Logger.new
|
49
|
+
def download
|
50
|
+
databases.each do |database|
|
51
|
+
Loader.download(database, @options[:index])
|
52
|
+
end
|
58
53
|
end
|
59
|
-
end
|
60
54
|
|
61
|
-
|
62
|
-
@bucket = "ey-backup-#{Digest::SHA1.hexdigest(options[:aws_secret_id])[0..11]}"
|
63
|
-
@bucket_minder = EY::BucketMinder.new(options.merge(:bucket => @bucket))
|
64
|
-
end
|
55
|
+
# END COMMANDS
|
65
56
|
|
66
|
-
|
67
|
-
|
68
|
-
|
57
|
+
def databases
|
58
|
+
@databases ||= database_names.map do |name|
|
59
|
+
Database.new(@engine, EY::Backup.tmp_dir, @options[:keep], @backend, @options[:environment], name)
|
60
|
+
end
|
61
|
+
end
|
69
62
|
|
70
|
-
|
71
|
-
|
72
|
-
|
63
|
+
def database_names
|
64
|
+
if @options[:db].nil? || @options[:db].empty?
|
65
|
+
@options[:databases]
|
66
|
+
else
|
67
|
+
[@options[:db]]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def setup
|
72
|
+
setup_logger
|
73
|
+
setup_tmp_dir
|
74
|
+
setup_backend
|
75
|
+
setup_engine
|
76
|
+
end
|
77
|
+
|
78
|
+
def setup_logger
|
79
|
+
if @options[:quiet]
|
80
|
+
EY::Backup.logger ||= Logger.quiet
|
81
|
+
else
|
82
|
+
EY::Backup.logger ||= Logger.new
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def setup_tmp_dir
|
87
|
+
if @options[:tmp_dir]
|
88
|
+
EY::Backup.tmp_dir = @options[:tmp_dir]
|
89
|
+
else
|
90
|
+
EY::Backup.tmp_dir = "/mnt/tmp"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def setup_backend
|
95
|
+
@backend = Backend.new(@options[:aws_secret_id], @options[:aws_secret_key], @options[:region], @options[:backup_bucket])
|
96
|
+
end
|
73
97
|
|
74
|
-
|
75
|
-
|
98
|
+
def setup_engine
|
99
|
+
engine_class = Engine.lookup(@options[:engine])
|
100
|
+
if ! @options.key?(:dbhost) or @options[:dbhost] == nil or @options[:dbhost] == ""
|
101
|
+
@options[:dbhost] = 'localhost'
|
102
|
+
end
|
103
|
+
@engine = engine_class.new(@options[:dbuser], @options[:dbpass], @options[:dbhost], @options[:key_id])
|
104
|
+
end
|
105
|
+
|
106
|
+
def dispatch
|
107
|
+
send(@options[:command])
|
108
|
+
end
|
76
109
|
end
|
110
|
+
|
77
111
|
end
|
78
112
|
end
|
79
113
|
|
@@ -84,9 +118,11 @@ require lib_dir + '/logger'
|
|
84
118
|
require lib_dir + '/base'
|
85
119
|
require lib_dir + '/backup_set'
|
86
120
|
require lib_dir + '/cli'
|
121
|
+
require lib_dir + '/database'
|
87
122
|
require lib_dir + '/dumper'
|
88
123
|
require lib_dir + '/engine'
|
89
124
|
require lib_dir + '/loader'
|
125
|
+
require lib_dir + '/backend'
|
90
126
|
|
91
127
|
require lib_dir + '/engines/mysql_engine'
|
92
128
|
require lib_dir + '/engines/postgresql_engine'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'fog'
|
2
|
+
|
3
|
+
module EY
|
4
|
+
module Backup
|
5
|
+
class Backend
|
6
|
+
include EY::Backup::Logging
|
7
|
+
extend EY::Backup::Logging
|
8
|
+
|
9
|
+
def initialize(secret_id, secret_key, region, bucket_name)
|
10
|
+
@bucket_minder = EY::BucketMinder.new(secret_id, secret_key, bucket_name, region)
|
11
|
+
@s3 = Fog::Storage.new(:provider => 'AWS', :aws_access_key_id => secret_id, :aws_secret_access_key => secret_key, :region => region)
|
12
|
+
end
|
13
|
+
attr_reader :bucket_minder
|
14
|
+
|
15
|
+
def start_upload(filenames, environment_name, database_name)
|
16
|
+
filenames.each do |filename|
|
17
|
+
begin
|
18
|
+
object_name = "#{environment_name}.#{database_name}/#{File.basename(filename)}"
|
19
|
+
info "Starting upload: #{filename}"
|
20
|
+
@s3.put_object(@bucket_minder.bucket_name, object_name, File.open(filename,'r'))
|
21
|
+
info "Successful upload: #{filename}"
|
22
|
+
rescue => e
|
23
|
+
retries ||= 5
|
24
|
+
retries -= 1
|
25
|
+
raise e if retries == 0
|
26
|
+
warn "retrying upload of #{filename}. Got: #{e.inspect}"
|
27
|
+
retry
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/ey_backup/backup_set.rb
CHANGED
@@ -1,65 +1,56 @@
|
|
1
1
|
module EY
|
2
2
|
module Backup
|
3
|
-
class BackupSet
|
3
|
+
class BackupSet
|
4
4
|
include EY::Backup::Logging
|
5
5
|
extend EY::Backup::Logging
|
6
6
|
|
7
|
-
attr_accessor :files
|
8
|
-
|
9
7
|
MULTIPART_EXTENSION = /\.part\d+/
|
10
8
|
|
11
|
-
def self.
|
12
|
-
|
9
|
+
def self.from(database, basename, file)
|
10
|
+
new(database, basename, file, nil)
|
13
11
|
end
|
14
12
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
def initialize(database, basename, filename, keys)
|
14
|
+
@database = database
|
15
|
+
@basename = basename
|
16
|
+
@filename = filename
|
17
|
+
@keys = keys
|
19
18
|
end
|
19
|
+
attr_reader :basename
|
20
20
|
|
21
|
-
def
|
22
|
-
|
23
|
-
backups_for(database)[0...(-keep)].each do |backup|
|
24
|
-
say "deleting: #{backup.name}"
|
25
|
-
backup.delete!
|
26
|
-
end
|
21
|
+
def database_name
|
22
|
+
@database.name
|
27
23
|
end
|
28
24
|
|
29
|
-
def
|
30
|
-
|
25
|
+
def cleanup
|
26
|
+
say "Cleanup for #{database_name}"
|
27
|
+
@database.backups[0...(-@database.keep)].each do |backup|
|
28
|
+
say "Deleting: #{backup.basename}"
|
29
|
+
backup.delete!
|
30
|
+
end
|
31
31
|
end
|
32
32
|
|
33
|
-
def self.
|
34
|
-
|
35
|
-
|
33
|
+
def self.list(databases)
|
34
|
+
names = databases.map do |db|
|
35
|
+
db.name
|
36
|
+
end
|
37
|
+
info "Listing database backups for #{names.join(', ')}"
|
36
38
|
|
37
|
-
|
38
|
-
info "Listing database backups for #{database}"
|
39
|
+
backups = databases.map{ |db| db.backups }.flatten
|
39
40
|
|
40
41
|
info "#{backups.size} backup(s) found"
|
41
42
|
|
42
|
-
backups.each_with_index do |
|
43
|
-
info "#{i}:#{
|
43
|
+
backups.each_with_index do |backup_set, i|
|
44
|
+
info "#{i}:#{backup_set.database_name} #{backup_set.normalized_name}"
|
44
45
|
end
|
45
46
|
|
46
47
|
backups
|
47
48
|
end
|
48
49
|
|
49
|
-
def self.backups_for(database_name)
|
50
|
-
s3_objects = EY::Backup.bucket_minder.list(:prefix => "#{EY::Backup.config.environment}.#{database_name}", :print => false)
|
51
|
-
parse(s3_objects).sort
|
52
|
-
end
|
53
|
-
|
54
|
-
def self.upload(database, file)
|
55
|
-
EY::Backup.bucket_minder.name = "#{EY::Backup.config.environment}.#{database}/#{File.basename(file)}"
|
56
|
-
EY::Backup.bucket_minder.upload_object(file, false)
|
57
|
-
end
|
58
|
-
|
59
50
|
def self.download(database, index)
|
60
|
-
fatal "You didn't specify a database name: e.g. 1:rails_production" if database.
|
51
|
+
fatal "You didn't specify a database name: e.g. 1:rails_production" if database.nil? || index.empty?
|
61
52
|
|
62
|
-
backup =
|
53
|
+
backup = database.backups[index.to_i]
|
63
54
|
|
64
55
|
fatal "No backup found for database #{database}: requested index: #{index}" unless backup
|
65
56
|
|
@@ -68,60 +59,76 @@ module EY
|
|
68
59
|
backup
|
69
60
|
end
|
70
61
|
|
71
|
-
def initialize(*)
|
72
|
-
super
|
73
|
-
|
74
|
-
@files = []
|
75
|
-
end
|
76
|
-
|
77
62
|
def parts
|
78
|
-
|
63
|
+
@keys.size
|
79
64
|
end
|
80
65
|
|
81
66
|
def normalized_name
|
82
|
-
normalize(
|
67
|
+
normalize(@basename)
|
83
68
|
end
|
84
69
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
70
|
+
def load!
|
71
|
+
@database.engine.load(@database.name, @filename)
|
72
|
+
end
|
73
|
+
|
74
|
+
def upload!
|
75
|
+
@database.start_upload(@remote_filenames)
|
91
76
|
end
|
92
77
|
|
93
78
|
def download!
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
File.
|
98
|
-
|
99
|
-
|
79
|
+
@remote_filenames = []
|
80
|
+
@keys.each do |key|
|
81
|
+
info "Downloading #{key} to #{@database.base_path}"
|
82
|
+
remote_filename = File.join(@database.base_path, normalize(key))
|
83
|
+
File.open(remote_filename, 'wb') do |f|
|
84
|
+
@database.bucket_minder.stream(key) do |chunk, remaining_size, total_size|
|
85
|
+
f.write(chunk)
|
100
86
|
end
|
101
87
|
end
|
102
88
|
|
103
|
-
|
89
|
+
@remote_filenames << remote_filename
|
104
90
|
end
|
105
91
|
end
|
106
92
|
|
107
93
|
def delete!
|
108
|
-
keys.each do |key|
|
109
|
-
|
94
|
+
@keys.each do |key|
|
95
|
+
@database.bucket_minder.remove_object(key)
|
110
96
|
end
|
111
97
|
end
|
112
98
|
|
113
|
-
def
|
114
|
-
|
115
|
-
FileUtils.rm(
|
99
|
+
def remove_split_files!
|
100
|
+
(@remote_filenames - [@filename]).each do |filename|
|
101
|
+
FileUtils.rm(filename)
|
116
102
|
end
|
117
103
|
end
|
118
104
|
|
105
|
+
def remove_joined_file!
|
106
|
+
FileUtils.rm(@filename)
|
107
|
+
end
|
108
|
+
|
109
|
+
def rm!
|
110
|
+
remove_split_files!
|
111
|
+
remove_joined_file!
|
112
|
+
end
|
113
|
+
|
114
|
+
def split!(split_size)
|
115
|
+
@remote_filenames = Splitter.dump(@filename, split_size)
|
116
|
+
end
|
117
|
+
|
118
|
+
def join!
|
119
|
+
@filename = Splitter.load(@remote_filenames)
|
120
|
+
end
|
121
|
+
|
119
122
|
def normalize(o)
|
120
123
|
o.gsub(/^.*?\//, '')
|
121
124
|
end
|
122
125
|
|
126
|
+
def timestamp
|
127
|
+
@basename[/(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2})/, 1]
|
128
|
+
end
|
129
|
+
|
123
130
|
def <=>(o)
|
124
|
-
self.
|
131
|
+
self.timestamp <=> o.timestamp
|
125
132
|
end
|
126
133
|
end
|
127
134
|
end
|
data/lib/ey_backup/base.rb
CHANGED
data/lib/ey_backup/cli.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module EY
|
2
2
|
module Backup
|
3
|
-
module CLI
|
3
|
+
module CLI #rename to option parser and config loader
|
4
4
|
extend self
|
5
5
|
|
6
6
|
def run(argv)
|
@@ -27,11 +27,14 @@ module EY
|
|
27
27
|
opts.version = EY::CloudServer::VERSION
|
28
28
|
|
29
29
|
opts.banner = "Usage: eybackup [-flag] [argument]"
|
30
|
-
opts.define_head "eybackup:
|
30
|
+
opts.define_head "eybackup: manage dump (mysqldump/pg_dump) style backups of your database."
|
31
31
|
opts.separator '*'*80
|
32
32
|
|
33
33
|
opts.on("-l", "--list-backup DATABASE", "List mysql backups for DATABASE") do |db|
|
34
|
-
|
34
|
+
if db == "all"
|
35
|
+
db = nil
|
36
|
+
end
|
37
|
+
options[:db] = db
|
35
38
|
options[:command] = :list
|
36
39
|
end
|
37
40
|
|
@@ -43,18 +46,26 @@ module EY
|
|
43
46
|
options[:config] = config
|
44
47
|
end
|
45
48
|
|
46
|
-
opts.on("-
|
49
|
+
opts.on("-t", "--tmp_dir TMPDIR", "Use the given directory for temporary storage.") do |tmp_dir|
|
50
|
+
options[:tmp_dir] = tmp_dir
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on("-d", "--download BACKUP_INDEX", "download the backup specified by index. Run eybackup -l to get the index.") do |index_and_db|
|
47
54
|
options[:command] = :download
|
55
|
+
db, index = split_index(index_and_db)
|
48
56
|
options[:index] = index
|
57
|
+
options[:db] = db
|
49
58
|
end
|
50
59
|
|
51
60
|
opts.on("-e", "--engine DATABASE_ENGINE", "The database engine. ex: mysql, postgres.") do |engine|
|
52
61
|
options[:engine] = engine
|
53
62
|
end
|
54
63
|
|
55
|
-
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 |
|
64
|
+
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_and_db|
|
56
65
|
options[:command] = :restore
|
66
|
+
db, index = split_index(index_and_db)
|
57
67
|
options[:index] = index
|
68
|
+
options[:db] = db
|
58
69
|
end
|
59
70
|
|
60
71
|
opts.on("-k", "--key KEYID", "Public key ID to use for the backup operation") do |key_id|
|
@@ -75,9 +86,18 @@ module EY
|
|
75
86
|
options
|
76
87
|
end
|
77
88
|
|
89
|
+
def split_index(index)
|
90
|
+
index.split(':').reverse
|
91
|
+
end
|
92
|
+
|
78
93
|
def config_for(filename)
|
79
94
|
if File.exist?(filename)
|
80
|
-
config
|
95
|
+
# Make the loaded config have symbols rather than strings
|
96
|
+
config_orig = YAML::load(File.read(filename))
|
97
|
+
config = {}
|
98
|
+
config_orig.each do |key, val|
|
99
|
+
config[key.to_sym] = val
|
100
|
+
end
|
81
101
|
config[:environment] ||= config[:env]
|
82
102
|
config
|
83
103
|
else
|