ey_cloud_server 1.4.58 → 1.4.60
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.
- checksums.yaml +8 -8
- data/bin/ey-snapshots +2 -1
- data/bin/eybackup +4 -2
- data/bin/eyrestore +37 -8
- data/lib/ey-flex/bucket_minder.rb +11 -1
- data/lib/ey-flex.rb +1 -1
- data/lib/ey_backup/backend.rb +2 -3
- data/lib/ey_backup/backup_set.rb +16 -16
- data/lib/ey_backup/cli.rb +31 -8
- data/lib/ey_backup/dumper.rb +20 -10
- data/lib/ey_backup/engine.rb +16 -3
- data/lib/ey_backup/engines/mysql_engine.rb +15 -4
- data/lib/ey_backup/engines/postgresql_engine.rb +13 -4
- data/lib/ey_backup/loader.rb +3 -1
- data/lib/ey_backup/logger.rb +42 -10
- data/lib/ey_backup/processors/splitter.rb +1 -1
- data/lib/ey_backup/spawner.rb +2 -5
- data/lib/ey_backup.rb +13 -2
- data/lib/ey_cloud_server/version.rb +1 -1
- data/spec/config.yml +1 -0
- data/spec/ey_backup/mysql_backups_spec.rb +18 -0
- data/spec/helpers.rb +29 -7
- data/spec/log/ey_flex_postgresql_db_wanting.sizes +1 -0
- metadata +6 -18
checksums.yaml
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
!binary "U0hBMQ==":
|
|
3
3
|
metadata.gz: !binary |-
|
|
4
|
-
|
|
4
|
+
MjkxYjFlMDg5ODQwOGNmMmVjZmQyM2VlNDhlNzQ3ZjUwNmY3MzJiYQ==
|
|
5
5
|
data.tar.gz: !binary |-
|
|
6
|
-
|
|
6
|
+
ZWEzZmJhMjQ5Y2I2YmFkZWQ1NDVkYWM1OTM5YmIyYzEyNDhmMmI0Zg==
|
|
7
7
|
SHA512:
|
|
8
8
|
metadata.gz: !binary |-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
OWI2N2I3ZWJjNjlhN2EwZDBlZjlhMWJkZjRjYzc1MWZhNGVkZDViZDVjYWEz
|
|
10
|
+
NWUxMGI3NzcyM2EyNmVmZDAzYzQ1MGVkZmY2ZDQyNTBlMGYwZjM3OThmMDE0
|
|
11
|
+
YWU5ODA3MDc2NTc4NDYxZTQzYjU3Mjg1MTU4YTM3ZTkwNzNiOWI=
|
|
12
12
|
data.tar.gz: !binary |-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
ZTBjZjEzNTUzZGI5NTVlNjc1ZWYwZTliYjNhOWYyNjIyYzkyNzc4MjA4YWI2
|
|
14
|
+
MjJiZDg3ZTI5MTEzZTNlZGUzYTViNTc3Mjc1NDYyYjNlNzAwMjBlMDg5ZGYx
|
|
15
|
+
ZGY4ZTEwNjM5NTc2NjZlMTk5NWYwNDUzOWYwNWFmY2VlMjNjZWY=
|
data/bin/ey-snapshots
CHANGED
data/bin/eybackup
CHANGED
|
@@ -12,7 +12,9 @@ require File.dirname(__FILE__) + '/../lib/ey-flex'
|
|
|
12
12
|
begin
|
|
13
13
|
EY::Backup.run(ARGV)
|
|
14
14
|
rescue => e
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
puts e.message
|
|
16
|
+
puts "******* Trace *******"
|
|
17
|
+
puts e.inspect
|
|
18
|
+
EY.notify_backup_error(e) unless STDOUT.isatty
|
|
17
19
|
end
|
|
18
20
|
|
data/bin/eyrestore
CHANGED
|
@@ -7,7 +7,7 @@ require 'timeout'
|
|
|
7
7
|
|
|
8
8
|
def opt_parse(argv)
|
|
9
9
|
options = {}
|
|
10
|
-
optparse = OptionParser.new do |opts|
|
|
10
|
+
@optparse = OptionParser.new do |opts|
|
|
11
11
|
opts.banner = "Usage: #{__FILE__} --env <environment name> --database <database_name> --action <action> [options]"
|
|
12
12
|
opts.separator ''
|
|
13
13
|
opts.separator "Wrapper for eybackup used for listing, downloading and restoring database backups. The main purpose for this tool is to"
|
|
@@ -20,7 +20,7 @@ def opt_parse(argv)
|
|
|
20
20
|
opts.separator 'Common Options:'
|
|
21
21
|
|
|
22
22
|
opts.on('-e', '--env environment_name', "Specifies the source environment name for the backups you will be working with.") { |env| options[:env] = env }
|
|
23
|
-
opts.on('-d', '--database database_name', "Name of the source database to
|
|
23
|
+
opts.on('-d', '--database database_name', "Name of the source database to list backups for.") { |dbname| options[:databases] = [dbname] }
|
|
24
24
|
actions=['list', 'restore', 'download']
|
|
25
25
|
opts.on('-a', '--action action_name', "The action you want to perform using the backup #{actions}.") do |action|
|
|
26
26
|
unless actions.include?(action)
|
|
@@ -65,7 +65,7 @@ def opt_parse(argv)
|
|
|
65
65
|
opts.separator " sudo -i #{__FILE__} --env production --database todo --action restore --index last"
|
|
66
66
|
opts.separator ''
|
|
67
67
|
end
|
|
68
|
-
optparse.parse!
|
|
68
|
+
@optparse.parse!
|
|
69
69
|
options
|
|
70
70
|
end
|
|
71
71
|
|
|
@@ -83,6 +83,7 @@ end
|
|
|
83
83
|
|
|
84
84
|
def extra_validations(options)
|
|
85
85
|
mandatory = [:databases, :env, :action]
|
|
86
|
+
mandatory << :index if %w(download restore).include? options[:action]
|
|
86
87
|
missing = mandatory.select{ |param| options[param].nil? }
|
|
87
88
|
raise OptionParser::MissingArgument, missing.join(',') unless missing.empty?
|
|
88
89
|
end
|
|
@@ -92,7 +93,9 @@ def list_backups(engine, db, path)
|
|
|
92
93
|
debug("Listing backups with command: #{command}")
|
|
93
94
|
res = %x{#{command}}
|
|
94
95
|
last = res.chomp.split("\n").last
|
|
95
|
-
|
|
96
|
+
if last.match(/0 backup\(s\) found/)
|
|
97
|
+
abort "No Backups Found for that environment and database.\n Tips:\n - double check the source environment name is correct\n - you may have a legacy bucket (grep backup_bucket /etc/.*.backups.yml) in the source environment, pass to eyrestore with --backup_bucket"
|
|
98
|
+
end
|
|
96
99
|
res
|
|
97
100
|
end
|
|
98
101
|
|
|
@@ -104,6 +107,14 @@ rescue Timeout::Error
|
|
|
104
107
|
'' # return nil if timeout
|
|
105
108
|
end
|
|
106
109
|
|
|
110
|
+
def backup_idx(listing, index)
|
|
111
|
+
if index == 'last'
|
|
112
|
+
last_backup_idx(listing)
|
|
113
|
+
else
|
|
114
|
+
index
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
107
118
|
def last_backup_idx(listing)
|
|
108
119
|
last = listing.chomp.split("\n").last
|
|
109
120
|
if last.split.first.match(/\d+:\w+/)
|
|
@@ -132,7 +143,15 @@ config.merge!(eyrestore) unless eyrestore.nil?
|
|
|
132
143
|
|
|
133
144
|
# override config from command line options
|
|
134
145
|
config.merge!(options)
|
|
135
|
-
|
|
146
|
+
indexdb = config[:index].split(':')[1] if not config[:index].nil? and config[:index].include? ':'
|
|
147
|
+
config[:databases] = [indexdb] if config[:databases].nil? and not indexdb.nil? and indexdb.match(/^\w+$/)
|
|
148
|
+
begin
|
|
149
|
+
extra_validations(config)
|
|
150
|
+
rescue OptionParser::MissingArgument => e
|
|
151
|
+
puts e.message
|
|
152
|
+
puts @optparse
|
|
153
|
+
exit
|
|
154
|
+
end
|
|
136
155
|
debug("Config Options: '#{config}'")
|
|
137
156
|
|
|
138
157
|
# create a temporary configuration file from ~/.eyrestore.config.yml
|
|
@@ -156,8 +175,10 @@ end
|
|
|
156
175
|
|
|
157
176
|
if ['download','restore'].include?(config[:action])
|
|
158
177
|
config[:index] = gets_timeout("Enter the backup index to use for #{config[:action]} (e.g. 9:#{db}) ", 30) unless config[:index]
|
|
159
|
-
config[:index] =
|
|
160
|
-
|
|
178
|
+
config[:index] = backup_idx(list_backups(engine, db, temp), config[:index])
|
|
179
|
+
# config[:index] = last_backup_idx(list_backups(engine, db, temp)) if config[:index] == 'last'
|
|
180
|
+
config[:index] = config[:index] + ":#{config[:databases].first}" if config[:index].match(/^\d+$/) and config[:databases] and config[:databases].size == 1
|
|
181
|
+
abort "Invalid backup index '#{config[:index]}'; proper format is <number>:<dbname>." unless config[:index].match(/^\d+:\w+$/)
|
|
161
182
|
|
|
162
183
|
action = config[:action] == 'restore' ? '-r' : '-d'
|
|
163
184
|
debug("Set action flag for eybackup to '#{action}' based on action of '#{config[:action]}'")
|
|
@@ -174,7 +195,15 @@ if ['download','restore'].include?(config[:action])
|
|
|
174
195
|
|
|
175
196
|
res=%x{#{command}}
|
|
176
197
|
puts res
|
|
177
|
-
|
|
198
|
+
|
|
199
|
+
last_line = res.split("\n").last
|
|
200
|
+
if last_line.nil?
|
|
201
|
+
# No-op, eybackup messaging is adequate.
|
|
202
|
+
elsif last_line.match(/^Filename/) and config[:action] == 'restore' and not last_line.match(/.gpz$/)
|
|
203
|
+
puts "Restore complete!"
|
|
204
|
+
else
|
|
205
|
+
puts "Download complete!"
|
|
206
|
+
end
|
|
178
207
|
end
|
|
179
208
|
|
|
180
209
|
# cleanup temporary config file
|
|
@@ -16,6 +16,10 @@ module EY
|
|
|
16
16
|
@bucket ||= @s3.directories.get(@bucket_name)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
def file
|
|
20
|
+
bucket
|
|
21
|
+
end
|
|
22
|
+
|
|
19
23
|
def files
|
|
20
24
|
bucket.files
|
|
21
25
|
end
|
|
@@ -74,7 +78,13 @@ module EY
|
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
def put(filename, contents)
|
|
77
|
-
files.create(
|
|
81
|
+
files.create(
|
|
82
|
+
:key => filename,
|
|
83
|
+
:body => contents,
|
|
84
|
+
:public => false,
|
|
85
|
+
:multipart_chunk_size => 100*1024*1024, # 100MB
|
|
86
|
+
'x-amz-server-side-encryption' => 'AES256'
|
|
87
|
+
)
|
|
78
88
|
end
|
|
79
89
|
end
|
|
80
90
|
end
|
data/lib/ey-flex.rb
CHANGED
data/lib/ey_backup/backend.rb
CHANGED
|
@@ -8,7 +8,6 @@ module EY
|
|
|
8
8
|
|
|
9
9
|
def initialize(secret_id, secret_key, region, bucket_name)
|
|
10
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
11
|
end
|
|
13
12
|
attr_reader :bucket_minder
|
|
14
13
|
|
|
@@ -17,13 +16,13 @@ module EY
|
|
|
17
16
|
begin
|
|
18
17
|
object_name = File.join("#{environment_name}.#{database_name}", "#{File.basename(filename)}")
|
|
19
18
|
info "Starting upload: #{filename}"
|
|
20
|
-
@
|
|
19
|
+
@bucket_minder.put(object_name, File.open(filename, 'r'))
|
|
21
20
|
info "Successful upload: #{filename}"
|
|
22
21
|
rescue => e
|
|
23
22
|
retries ||= 5
|
|
24
23
|
retries -= 1
|
|
25
24
|
# remove partial or failed uploads
|
|
26
|
-
@
|
|
25
|
+
@bucket_minder.remove_object(object_name)
|
|
27
26
|
raise e if retries == 0
|
|
28
27
|
warn "retrying upload of #{filename}. Got: #{e.inspect}"
|
|
29
28
|
retry
|
data/lib/ey_backup/backup_set.rb
CHANGED
|
@@ -31,26 +31,26 @@ module EY
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def self.list(databases)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
puts
|
|
34
|
+
all_backups = []
|
|
35
|
+
databases.each do |db|
|
|
36
|
+
backups = db.backups
|
|
37
|
+
all_backups << backups
|
|
38
|
+
puts "Listing database backups for #{db.name}"
|
|
39
|
+
puts "#{backups.size} backup(s) found"
|
|
40
|
+
|
|
41
|
+
backups.each_with_index do |backup_set, i|
|
|
42
|
+
puts "#{i}:#{backup_set.database_name} #{backup_set.normalized_name}"
|
|
43
|
+
end
|
|
44
|
+
puts # just some extra whitespace
|
|
45
45
|
end
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
|
|
47
|
+
all_backups.flatten
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def self.download(database, index)
|
|
51
|
-
fatal "You didn't specify a database name: e.g. 1:rails_production" if database.nil? || index
|
|
51
|
+
fatal "You didn't specify a database name: e.g. 1:rails_production" if database.nil? || index < 0
|
|
52
52
|
|
|
53
|
-
backup = database.backups[index
|
|
53
|
+
backup = database.backups[index]
|
|
54
54
|
|
|
55
55
|
fatal "No backup found for database #{database}: requested index: #{index}" unless backup
|
|
56
56
|
|
|
@@ -78,9 +78,9 @@ module EY
|
|
|
78
78
|
def download!
|
|
79
79
|
@remote_filenames = []
|
|
80
80
|
@keys.each do |key|
|
|
81
|
-
info "Downloading #{key} to #{@database.base_path}"
|
|
82
81
|
remote_filename = File.join(@database.base_path, normalize(key))
|
|
83
82
|
puts "Filename: #{remote_filename}"
|
|
83
|
+
info "Downloading #{key} to #{@database.base_path}"
|
|
84
84
|
File.open(remote_filename, 'wb') do |f|
|
|
85
85
|
@database.bucket_minder.stream(key) do |chunk, remaining_size, total_size|
|
|
86
86
|
f.write(chunk)
|
data/lib/ey_backup/cli.rb
CHANGED
|
@@ -34,14 +34,16 @@ module EY
|
|
|
34
34
|
# Build a parser for the command line arguments
|
|
35
35
|
options = {}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
optparse = OptionParser.new do |opts|
|
|
38
38
|
opts.version = EY::CloudServer::VERSION
|
|
39
39
|
|
|
40
40
|
opts.banner = "\nUsage: eybackup [-flag] [argument]"
|
|
41
|
-
opts.define_head " eybackup: manage
|
|
41
|
+
opts.define_head " eybackup: manage logical (mysqldump/pg_dump) style backups of your database.",
|
|
42
|
+
" When backing up multiple databases, each database is backed up separately."
|
|
42
43
|
opts.separator '*'*80
|
|
43
44
|
|
|
44
|
-
opts.on("-l", "--list-backup
|
|
45
|
+
opts.on("-l", "--list-backup DATABASE_NAME", "List backups for DATABASE_NAME; ",
|
|
46
|
+
" accepts 'all' to list all databases in the config (/etc/.*.backups.yml)") do |db|
|
|
45
47
|
if db == "all"
|
|
46
48
|
db = nil
|
|
47
49
|
end
|
|
@@ -52,7 +54,9 @@ module EY
|
|
|
52
54
|
opts.on("-e", "--engine DATABASE_ENGINE", "The database engine. ex: mysql, postgresql.") do |engine|
|
|
53
55
|
options[:engine] = engine
|
|
54
56
|
end
|
|
55
|
-
opts.on("-n", "--new-backup", "Create a new backup (default)"
|
|
57
|
+
opts.on("-n", "--new-backup [DATABASE_NAME]", "Create a new backup (default).",
|
|
58
|
+
" Backs up each database in '/etc/.*.backups.yml' if DATABASE_NAME not set.") do |db|
|
|
59
|
+
options[:db] = db.split(',') unless db.nil? or db == ''
|
|
56
60
|
options[:command] = :new_backup
|
|
57
61
|
end
|
|
58
62
|
|
|
@@ -89,9 +93,11 @@ module EY
|
|
|
89
93
|
' Run `eybackup -l #{db_name}` to get the index.',
|
|
90
94
|
' BACKUP_INDEX uses the format #{index_number}:#{db_name}') do |index_and_db|
|
|
91
95
|
options[:command] = :download
|
|
92
|
-
|
|
96
|
+
index, db = split_index(index_and_db)
|
|
97
|
+
index = index.to_i
|
|
93
98
|
options[:index] = index
|
|
94
99
|
options[:db] = db
|
|
100
|
+
raise OptionParser::InvalidArgument, "Index '#{index_and_db}' is not a valid format (hint: <number>:<dbname>)!" if index.nil? or not index.is_a? Numeric
|
|
95
101
|
end
|
|
96
102
|
|
|
97
103
|
opts.on("-r", "--restore BACKUP_INDEX", "Download and apply the backup specified by index.",
|
|
@@ -99,9 +105,11 @@ module EY
|
|
|
99
105
|
' Run `eybackup -l #{db_name}` to get the index.',
|
|
100
106
|
' BACKUP_INDEX uses the format #{index_number}:#{db_name}') do |index_and_db|
|
|
101
107
|
options[:command] = :restore
|
|
102
|
-
|
|
108
|
+
index, db = split_index(index_and_db)
|
|
109
|
+
index = index.to_i
|
|
103
110
|
options[:index] = index
|
|
104
111
|
options[:db] = db
|
|
112
|
+
raise OptionParser::InvalidArgument, "Index '#{index_and_db}' is not a valid format (hint: <number>:<dbname>)!" if index.nil? or not index.is_a? Numeric
|
|
105
113
|
end
|
|
106
114
|
|
|
107
115
|
options[:force] = false
|
|
@@ -109,16 +117,31 @@ module EY
|
|
|
109
117
|
" For use with automated restore operations (e.g. Staging).") do
|
|
110
118
|
options[:force] = true
|
|
111
119
|
end
|
|
120
|
+
|
|
121
|
+
options[:allow_concurrent] = false
|
|
122
|
+
opts.on("--allow_concurrent", "Allow eybackup process to run concurrently with other eybackup processes.") do
|
|
123
|
+
options[:allow_concurrent] = true
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
options[:skip_analyze] = false
|
|
127
|
+
opts.on("--skip_analyze", "Skip automatic analyze during PostgreSQL Restore operations.") do
|
|
128
|
+
options[:skip_analyze] = true
|
|
129
|
+
end
|
|
112
130
|
|
|
113
131
|
end
|
|
114
132
|
|
|
115
|
-
|
|
133
|
+
begin
|
|
134
|
+
optparse.parse!(argv)
|
|
135
|
+
rescue => e
|
|
136
|
+
puts optparse
|
|
137
|
+
raise
|
|
138
|
+
end
|
|
116
139
|
|
|
117
140
|
options
|
|
118
141
|
end
|
|
119
142
|
|
|
120
143
|
def split_index(index)
|
|
121
|
-
index.split(':')
|
|
144
|
+
index.split(':')
|
|
122
145
|
end
|
|
123
146
|
|
|
124
147
|
def config_for(filename)
|
data/lib/ey_backup/dumper.rb
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
class String
|
|
2
|
+
def truncate(limit = 1)
|
|
3
|
+
self.match(%r{^(.{0,#{limit}})})[1]
|
|
4
|
+
end
|
|
5
|
+
end
|
|
6
|
+
|
|
1
7
|
module EY
|
|
2
8
|
module Backup
|
|
3
9
|
class Dumper < Base
|
|
@@ -26,15 +32,19 @@ module EY
|
|
|
26
32
|
|
|
27
33
|
alert_level=exceptions.empty? ? 'OKAY' : 'FAILURE'
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
# we don't provide alert here unless its a failure
|
|
36
|
+
unless alert_level == 'OKAY'
|
|
37
|
+
message.gsub!("\n", '\n')
|
|
38
|
+
message = Shellwords.escape(message).truncate(255)
|
|
39
|
+
full_txt= "Severity: #{alert_level}\n" \
|
|
40
|
+
+ "Time: #{Time.now.to_i}\n" \
|
|
41
|
+
+ "Type: process-dbbackup summary\n" \
|
|
42
|
+
+ "Plugin: exec\n"
|
|
43
|
+
full_txt = Shellwords.escape(full_txt)
|
|
44
|
+
full_txt += "raw_message:\\ \\'#{message}\\'"
|
|
45
|
+
alert_command = %Q(echo #{full_txt} | /engineyard/bin/ey-alert.rb 2>&1)
|
|
46
|
+
system(alert_command)
|
|
47
|
+
end
|
|
38
48
|
end
|
|
39
49
|
|
|
40
50
|
def initialize(database)
|
|
@@ -48,7 +58,7 @@ module EY
|
|
|
48
58
|
backup_set.split!(split_size)
|
|
49
59
|
backup_set.upload!
|
|
50
60
|
|
|
51
|
-
okay(
|
|
61
|
+
okay(@database.name, @database.backup_size)
|
|
52
62
|
backup_set.cleanup
|
|
53
63
|
backup_set.rm!
|
|
54
64
|
@database.backup_size
|
data/lib/ey_backup/engine.rb
CHANGED
|
@@ -3,7 +3,7 @@ module EY
|
|
|
3
3
|
class Engine < Base
|
|
4
4
|
include Spawner
|
|
5
5
|
|
|
6
|
-
attr_reader :username, :password, :host, :key_id, :force
|
|
6
|
+
attr_reader :username, :password, :host, :key_id, :force, :allow_concurrent, :skip_analyze
|
|
7
7
|
|
|
8
8
|
def self.label
|
|
9
9
|
@label
|
|
@@ -26,8 +26,21 @@ module EY
|
|
|
26
26
|
EY::Backup.logger.fatal("Unknown database engine: #{label}")
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def initialize(username, password, host, key_id, force)
|
|
30
|
-
@username, @password, @host, @key_id, @force = username, password, host, key_id, force
|
|
29
|
+
def initialize(username, password, host, key_id, force, allow_concurrent, skip_analyze)
|
|
30
|
+
@username, @password, @host, @key_id, @force, @allow_concurrent, @skip_analyze = username, password, host, key_id, force, allow_concurrent, skip_analyze
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def block_concurrent(db = nil)
|
|
34
|
+
if backup_running?
|
|
35
|
+
message = "Unable to backup #{db}: already a backup in progress. Use --allow_concurrent to enable concurrent backup runs."
|
|
36
|
+
error(message, db) unless File.exists?('/etc/engineyard/skip_concurrent_alerts') # this skips the dashboard alert
|
|
37
|
+
abort message
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def backup_running?
|
|
42
|
+
verbose %x{ps aux | grep [e]ybackup | grep rubygems}
|
|
43
|
+
%x{ps -ef | grep [e]ybackup | grep rubygems | wc -l}.to_i > 1
|
|
31
44
|
end
|
|
32
45
|
|
|
33
46
|
def gpg?
|
|
@@ -6,7 +6,7 @@ module EY
|
|
|
6
6
|
def dump(database_name, basename)
|
|
7
7
|
file = basename + '.sql'
|
|
8
8
|
|
|
9
|
-
command = "mysqldump #{username_option} #{host_option} #{single_transaction_option(database_name)} #{routines_option} #{master_data_option} #{databases_option(database_name)}
|
|
9
|
+
command = "( #{server_id} mysqldump #{username_option} #{host_option} #{single_transaction_option(database_name)} #{routines_option} #{master_data_option} #{databases_option(database_name)} ) "
|
|
10
10
|
|
|
11
11
|
if gpg?
|
|
12
12
|
command << " | " << GPGEncryptor.command_for(key_id)
|
|
@@ -17,6 +17,8 @@ module EY
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
command << " > #{file}"
|
|
20
|
+
|
|
21
|
+
block_concurrent(database_name) unless allow_concurrent
|
|
20
22
|
|
|
21
23
|
run(command, database_name)
|
|
22
24
|
|
|
@@ -27,14 +29,16 @@ module EY
|
|
|
27
29
|
command = "cat #{file}"
|
|
28
30
|
|
|
29
31
|
if file =~ /.gpz$/ # GPG?
|
|
30
|
-
abort "
|
|
32
|
+
abort "\nCannot restore a GPG backup directly; decrypt the file (#{file}) using your key and then load using the mysql client.
|
|
33
|
+
To decrypt a backup: https://support.cloud.engineyard.com/hc/en-us/articles/205413948-Use-PGP-Encrypted-Database-Backups-with-Engine-Yard-Cloud#restore
|
|
34
|
+
Once decrypted, restore with: `gunzip -f < <filename> | mysql #{username_option} #{host_option} #{database_name}`\n\n"
|
|
31
35
|
else
|
|
32
36
|
command << " | " << GZipper.gunzip
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
cycle_database(database_name)
|
|
36
40
|
|
|
37
|
-
command << " | mysql #{username_option} #{host_option} #{database_name}
|
|
41
|
+
command << " | mysql #{username_option} #{host_option} #{database_name} "
|
|
38
42
|
|
|
39
43
|
run(command, database_name)
|
|
40
44
|
end
|
|
@@ -52,12 +56,19 @@ module EY
|
|
|
52
56
|
"--routines"
|
|
53
57
|
end
|
|
54
58
|
|
|
59
|
+
def server_id
|
|
60
|
+
if log_bin_on?
|
|
61
|
+
stdout = %x{mysql #{username_option} #{host_option} -BN -e"select @@global.server_id"}
|
|
62
|
+
"echo '-- Server_id: #{stdout.strip}' && "
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
55
66
|
def master_data_option
|
|
56
67
|
"--master-data=2" if log_bin_on?
|
|
57
68
|
end
|
|
58
69
|
|
|
59
70
|
def databases_option(db)
|
|
60
|
-
"
|
|
71
|
+
"#{db}"
|
|
61
72
|
end
|
|
62
73
|
|
|
63
74
|
def host_option
|
|
@@ -6,7 +6,7 @@ module EY
|
|
|
6
6
|
def dump(database_name, basename)
|
|
7
7
|
file = basename + '.dump'
|
|
8
8
|
|
|
9
|
-
command = "PGPASSWORD='#{password}' pg_dump -h #{host} --create --format=c -Upostgres #{database_name}
|
|
9
|
+
command = "PGPASSWORD='#{password}' pg_dump -h #{host} --create --format=c -Upostgres #{database_name} "
|
|
10
10
|
|
|
11
11
|
if gpg?
|
|
12
12
|
command << " | " << GPGEncryptor.command_for(key_id)
|
|
@@ -15,6 +15,7 @@ module EY
|
|
|
15
15
|
|
|
16
16
|
command << " > #{file}"
|
|
17
17
|
|
|
18
|
+
block_concurrent(database_name) unless allow_concurrent
|
|
18
19
|
run(command, database_name)
|
|
19
20
|
|
|
20
21
|
file
|
|
@@ -22,16 +23,24 @@ module EY
|
|
|
22
23
|
|
|
23
24
|
def load(database_name, file)
|
|
24
25
|
if file =~ /.gpz$/ # GPG?
|
|
25
|
-
abort "
|
|
26
|
+
abort "\nCannot restore a GPG backup directly; decrypt the file (#{file}) using your key and then load with pg_restore.
|
|
27
|
+
To decrypt a backup: https://support.cloud.engineyard.com/hc/en-us/articles/205413948-Use-PGP-Encrypted-Database-Backups-with-Engine-Yard-Cloud#restore
|
|
28
|
+
Once decrypted, restore with: `pg_restore -h #{host} --format=c --clean -Upostgres -d #{database_name} <filename>`\n\n"
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
cycle_database(database_name)
|
|
29
32
|
|
|
30
33
|
command = "cat #{file}"
|
|
31
34
|
|
|
32
|
-
command << " | PGPASSWORD='#{password}' pg_restore -h #{host} --format=c -Upostgres -d #{database_name}
|
|
35
|
+
command << " | PGPASSWORD='#{password}' pg_restore -h #{host} --format=c -Upostgres -d #{database_name}"
|
|
33
36
|
|
|
34
37
|
run(command, database_name)
|
|
38
|
+
|
|
39
|
+
# Analyze database unless disabled
|
|
40
|
+
unless skip_analyze
|
|
41
|
+
verbose "Analyzing database '#{database_name}', use --skip-analyze to skip this step."
|
|
42
|
+
%x{PGPASSWORD='#{password}' vacuumdb -h #{host} -Upostgres -d #{database_name} --analyze-only}
|
|
43
|
+
end
|
|
35
44
|
end
|
|
36
45
|
|
|
37
46
|
def check_connections(database_name)
|
|
@@ -40,7 +49,7 @@ module EY
|
|
|
40
49
|
if active_connections.to_i > 0
|
|
41
50
|
res = ''
|
|
42
51
|
unless force
|
|
43
|
-
puts "There are currently #{
|
|
52
|
+
puts "There are currently #{active_connections} connections on database: '#{database_name}'; can I kill these to continue (Y/n):"
|
|
44
53
|
Timeout::timeout(30){
|
|
45
54
|
res = gets.strip
|
|
46
55
|
}
|
data/lib/ey_backup/loader.rb
CHANGED
data/lib/ey_backup/logger.rb
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
class String
|
|
2
|
+
def truncate(limit = 1)
|
|
3
|
+
self.match(%r{^(.{0,#{limit}})})[1]
|
|
4
|
+
end
|
|
5
|
+
end
|
|
6
|
+
|
|
1
7
|
module EY
|
|
2
8
|
module Backup
|
|
3
9
|
class Logger
|
|
4
10
|
extend Forwardable
|
|
5
11
|
require 'shellwords'
|
|
12
|
+
require 'fileutils'
|
|
6
13
|
|
|
7
14
|
attr_accessor :stdout, :stderr
|
|
8
15
|
|
|
@@ -22,21 +29,22 @@ module EY
|
|
|
22
29
|
|
|
23
30
|
def push_dashboard_alert(message, alert_level, db = nil)
|
|
24
31
|
message.gsub!("\n", '\n')
|
|
32
|
+
message = Shellwords.escape(message).truncate(255)
|
|
25
33
|
type="process-dbbackup"
|
|
26
34
|
type = type + " #{db}" unless db.nil?
|
|
27
35
|
full_txt= "Severity: #{alert_level}\n" \
|
|
28
36
|
+ "Time: #{Time.now.to_i}\n" \
|
|
29
37
|
+ "Type: #{type}\n" \
|
|
30
|
-
+ "Plugin: exec\n"
|
|
31
|
-
+ "raw_message: '#{message}'"
|
|
38
|
+
+ "Plugin: exec\n"
|
|
32
39
|
full_txt = Shellwords.escape(full_txt)
|
|
40
|
+
full_txt += "raw_message:\\ \\'#{message}\\'"
|
|
33
41
|
alert_command = %Q(echo #{full_txt} | /engineyard/bin/ey-alert.rb 2>&1)
|
|
34
42
|
verbose "Sending dashboard alert"
|
|
35
43
|
system(alert_command)
|
|
36
44
|
end
|
|
37
45
|
|
|
38
46
|
def info(msg)
|
|
39
|
-
puts "#{Time.now} #{msg}"
|
|
47
|
+
stdout.puts "#{Time.now} #{msg}"
|
|
40
48
|
end
|
|
41
49
|
|
|
42
50
|
def verbose(msg)
|
|
@@ -46,24 +54,48 @@ module EY
|
|
|
46
54
|
def set_verbose()
|
|
47
55
|
@verbose = true
|
|
48
56
|
end
|
|
57
|
+
|
|
58
|
+
def set_log_path=(path)
|
|
59
|
+
@status_path = path
|
|
60
|
+
FileUtils.mkdir_p(@status_path)
|
|
61
|
+
end
|
|
49
62
|
|
|
50
63
|
def say(msg, newline = true)
|
|
51
64
|
newline ? info(msg) : stdout.print(msg)
|
|
52
65
|
end
|
|
53
66
|
|
|
54
|
-
def okay(
|
|
55
|
-
|
|
56
|
-
|
|
67
|
+
def okay(db, size)
|
|
68
|
+
filepath = File.join(@status_path, "#{db}.sizes")
|
|
69
|
+
%x{LOG=$(tail -n 100 #{filepath}); echo "$LOG" > #{filepath}} if File.exists?(filepath)
|
|
70
|
+
%x{echo "#{Time.now()} #{size}" >> #{filepath}}
|
|
71
|
+
msg = "Backup successful for '#{db}' after previous failure."
|
|
72
|
+
push_dashboard_alert(msg, 'OKAY', db) if clear_alert?(db)
|
|
57
73
|
end
|
|
58
74
|
|
|
59
75
|
def warn(msg, db = nil)
|
|
60
|
-
|
|
76
|
+
stdout.puts("#{Time.now} WARNING: #{msg}")
|
|
77
|
+
set_alert(msg, db) unless db.nil?
|
|
61
78
|
push_dashboard_alert(msg, "WARNING", db)
|
|
62
79
|
end
|
|
63
80
|
|
|
64
81
|
def error(msg, db = nil)
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
stdout.puts("#{Time.now} ERROR: #{msg}")
|
|
83
|
+
set_alert(msg, db) unless db.nil?
|
|
84
|
+
push_dashboard_alert("#{msg} Details at /var/log/eybackup.log.", "FAILURE", db)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def set_alert(msg, db)
|
|
88
|
+
fullPath = File.join(@status_path, "#{db}.alert")
|
|
89
|
+
File.open(fullPath, 'a') { |file| file.puts "#{Time.now}: #{msg}"}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def clear_alert?(db)
|
|
93
|
+
begin
|
|
94
|
+
File.delete(File.join(@status_path, "#{db}.alert"))
|
|
95
|
+
true
|
|
96
|
+
rescue Errno::ENOENT
|
|
97
|
+
false
|
|
98
|
+
end
|
|
67
99
|
end
|
|
68
100
|
|
|
69
101
|
def exception(type, msg)
|
|
@@ -71,7 +103,7 @@ module EY
|
|
|
71
103
|
end
|
|
72
104
|
|
|
73
105
|
def debug(msg)
|
|
74
|
-
|
|
106
|
+
stdout.puts("#{Time.now} DEBUG: #{msg}")
|
|
75
107
|
end
|
|
76
108
|
|
|
77
109
|
end
|
data/lib/ey_backup/spawner.rb
CHANGED
|
@@ -42,14 +42,11 @@ module EY
|
|
|
42
42
|
pid, stdin, stdout, stderr = Open4.popen4("bash -o pipefail -c #{escaped_command}")
|
|
43
43
|
pid, status = Process::waitpid2(pid)
|
|
44
44
|
|
|
45
|
-
verbose "stdout: #{stdout.read}"
|
|
46
|
-
verbose "stderr: #{stderr.read}"
|
|
47
45
|
verbose "status: #{status}"
|
|
48
46
|
|
|
49
47
|
if ! status.success?
|
|
50
|
-
dumperr = File.exists?("/tmp/eybackup.#{pid}.dumperr") ? File.read("/tmp/eybackup.#{pid}.dumperr") : status
|
|
51
|
-
err_msg = "
|
|
52
|
-
verbose "#{db} backup failed: #{err_msg}"
|
|
48
|
+
dumperr = File.exists?("/tmp/eybackup.#{pid}.dumperr") ? File.read("/tmp/eybackup.#{pid}.dumperr") : "#{status}: #{stderr.read.chomp}: #{stdout.read.chomp}"
|
|
49
|
+
err_msg = "#{db} backup failed! The error returned was: #{dumperr}"
|
|
53
50
|
error(err_msg, db)
|
|
54
51
|
end
|
|
55
52
|
|
data/lib/ey_backup.rb
CHANGED
|
@@ -44,6 +44,7 @@ module EY
|
|
|
44
44
|
class << self
|
|
45
45
|
attr_accessor :logger
|
|
46
46
|
attr_accessor :tmp_dir
|
|
47
|
+
attr_accessor :log_dir
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
def self.run(argv = ARGV)
|
|
@@ -96,11 +97,12 @@ module EY
|
|
|
96
97
|
if @options[:db].nil? || @options[:db].empty?
|
|
97
98
|
@options[:databases]
|
|
98
99
|
else
|
|
99
|
-
[@options[:db]]
|
|
100
|
+
[@options[:db]].flatten
|
|
100
101
|
end
|
|
101
102
|
end
|
|
102
103
|
|
|
103
104
|
def setup
|
|
105
|
+
setup_log_dir
|
|
104
106
|
setup_logger
|
|
105
107
|
setup_tmp_dir
|
|
106
108
|
setup_backend
|
|
@@ -117,6 +119,7 @@ module EY
|
|
|
117
119
|
EY::Backup.logger.set_verbose
|
|
118
120
|
end
|
|
119
121
|
end
|
|
122
|
+
EY::Backup.logger.set_log_path=EY::Backup.log_dir
|
|
120
123
|
end
|
|
121
124
|
|
|
122
125
|
def setup_tmp_dir
|
|
@@ -126,6 +129,14 @@ module EY
|
|
|
126
129
|
EY::Backup.tmp_dir = "/mnt/tmp"
|
|
127
130
|
end
|
|
128
131
|
end
|
|
132
|
+
|
|
133
|
+
def setup_log_dir
|
|
134
|
+
if @options[:log_dir]
|
|
135
|
+
EY::Backup.log_dir = @options[:log_dir]
|
|
136
|
+
else
|
|
137
|
+
EY::Backup.log_dir = "/var/log/engineyard/eybackup"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
129
140
|
|
|
130
141
|
def setup_backend
|
|
131
142
|
@backend = Backend.new(@options[:aws_secret_id], @options[:aws_secret_key], @options[:region], @options[:backup_bucket])
|
|
@@ -136,7 +147,7 @@ module EY
|
|
|
136
147
|
if ! @options.key?(:dbhost) or @options[:dbhost] == nil or @options[:dbhost] == ""
|
|
137
148
|
@options[:dbhost] = 'localhost'
|
|
138
149
|
end
|
|
139
|
-
@engine = engine_class.new(@options[:dbuser], @options[:dbpass], @options[:dbhost], @options[:key_id], @options[:force])
|
|
150
|
+
@engine = engine_class.new(@options[:dbuser], @options[:dbpass], @options[:dbhost], @options[:key_id], @options[:force], @options[:allow_concurrent], @options[:skip_analyze])
|
|
140
151
|
end
|
|
141
152
|
|
|
142
153
|
def dispatch
|
data/spec/config.yml
CHANGED
|
@@ -125,6 +125,24 @@ describe "MySQL Backups" do
|
|
|
125
125
|
run_sql("SELECT * FROM `bar`;", @db_name).should be_true
|
|
126
126
|
FileUtils.rm(file)
|
|
127
127
|
end
|
|
128
|
+
|
|
129
|
+
it 'detects a backup failure due to an invalid view definition and reports it to stdout' do
|
|
130
|
+
|
|
131
|
+
run_sql("CREATE TABLE `foo` (`id` int(11) NOT NULL auto_increment, PRIMARY KEY(`id`));", @db_name).should be_truthy
|
|
132
|
+
run_sql("CREATE TABLE `bar` (`id` int(11) NOT NULL auto_increment, PRIMARY KEY(`id`));", @db_name).should be_truthy
|
|
133
|
+
run_sql("CREATE view `vw_foobar` as select `foo`.`id` as `foo_id`, `bar`.`id` as `bar_id` from `foo` inner join `bar` on `foo`.`id` = `bar`.`id`;", @db_name).should be_truthy
|
|
134
|
+
run_sql("DROP TABLE `bar`;", @db_name).should be_truthy
|
|
135
|
+
|
|
136
|
+
reset_logger
|
|
137
|
+
|
|
138
|
+
EY::Backup.run(["-c", backup_config_file])
|
|
139
|
+
stdout.should match(/mysqldump: Couldn't execute 'SHOW FIELDS FROM `vw_foobar`'/)
|
|
140
|
+
|
|
141
|
+
files = Dir["#{EY::Backup.tmp_dir}/*#{@db_name}*"]
|
|
142
|
+
|
|
143
|
+
files.size.should == 1
|
|
144
|
+
FileUtils.rm(files.first)
|
|
145
|
+
end
|
|
128
146
|
end
|
|
129
147
|
|
|
130
148
|
describe "MySQL Backups" do
|
data/spec/helpers.rb
CHANGED
|
@@ -18,19 +18,29 @@ module Helpers
|
|
|
18
18
|
FakeWeb.register_uri(:post, "http://example.org/api/environments", :body => JSON.generate({
|
|
19
19
|
@mock_environment_name => {:id => 1}
|
|
20
20
|
}))
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
|
|
22
|
+
create_or_clean_dir(tmp_dir)
|
|
23
|
+
create_or_clean_dir(log_dir)
|
|
24
|
+
|
|
25
|
+
# FileUtils.mkdir_p(tmp_dir)
|
|
26
|
+
# FileUtils.mkdir_p(log_dir)
|
|
27
|
+
# Dir.glob("#{tmp_dir}/*").each do |f|
|
|
28
|
+
# FileUtils.rm(f)
|
|
29
|
+
# end
|
|
30
|
+
stub_configs
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def create_or_clean_dir(dir)
|
|
34
|
+
FileUtils.mkdir_p(dir)
|
|
35
|
+
Dir.glob("#{dir}/*").each do |f|
|
|
24
36
|
FileUtils.rm(f)
|
|
25
37
|
end
|
|
26
|
-
|
|
27
|
-
stub_configs
|
|
28
38
|
end
|
|
29
39
|
|
|
30
40
|
def stub_configs
|
|
31
41
|
#print "in stub_configs: #{YAML::dump(@database_config)}, #{YAML::dump(spec_config)}\n"
|
|
32
42
|
config = @database_config || spec_config
|
|
33
|
-
config = config.merge({ :tmp_dir => tmp_dir })
|
|
43
|
+
config = config.merge({ :tmp_dir => tmp_dir, :log_dir => log_dir })
|
|
34
44
|
# print "in stub_configs2: #{YAML::dump(config)}\n"
|
|
35
45
|
File.open(backup_config_file, "w") do |f|
|
|
36
46
|
f.puts YAML.dump(config )
|
|
@@ -55,7 +65,7 @@ module Helpers
|
|
|
55
65
|
return File.read(PUBLIC_KEY_PATH), File.read(PRIVATE_KEY_PATH)
|
|
56
66
|
end
|
|
57
67
|
|
|
58
|
-
def run_after
|
|
68
|
+
def run_after(skip_file_remove = false)
|
|
59
69
|
FileUtils.rm_f(backup_config_file)
|
|
60
70
|
filenames = Dir.glob("#{tmp_dir}/*")
|
|
61
71
|
if filenames.any?
|
|
@@ -183,6 +193,14 @@ module Helpers
|
|
|
183
193
|
spec_config['tmp_dir'] || File.dirname("./tmp/")
|
|
184
194
|
end
|
|
185
195
|
end
|
|
196
|
+
|
|
197
|
+
def log_dir
|
|
198
|
+
if spec_config['log_dir'].nil?
|
|
199
|
+
File.dirname(__FILE__) + "/log/"
|
|
200
|
+
else
|
|
201
|
+
spec_config['log_dir'] || File.dirname("./log/")
|
|
202
|
+
end
|
|
203
|
+
end
|
|
186
204
|
|
|
187
205
|
def mysql_user
|
|
188
206
|
spec_config['mysql_user'] || "root"
|
|
@@ -280,6 +298,10 @@ module Helpers
|
|
|
280
298
|
def stdout
|
|
281
299
|
EY::Backup.logger.stdout.string
|
|
282
300
|
end
|
|
301
|
+
|
|
302
|
+
def stderr
|
|
303
|
+
EY::Backup.logger.stderr.string
|
|
304
|
+
end
|
|
283
305
|
|
|
284
306
|
load_spec_config
|
|
285
307
|
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2017-05-17 02:10:11 -0400 1.7 KB
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ey_cloud_server
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.60
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- EngineYard
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-
|
|
11
|
+
date: 2017-05-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|
|
@@ -78,34 +78,20 @@ dependencies:
|
|
|
78
78
|
- - <
|
|
79
79
|
- !ruby/object:Gem::Version
|
|
80
80
|
version: '3.0'
|
|
81
|
-
- !ruby/object:Gem::Dependency
|
|
82
|
-
name: aws-s3
|
|
83
|
-
requirement: !ruby/object:Gem::Requirement
|
|
84
|
-
requirements:
|
|
85
|
-
- - ! '>='
|
|
86
|
-
- !ruby/object:Gem::Version
|
|
87
|
-
version: 0.6.3
|
|
88
|
-
type: :runtime
|
|
89
|
-
prerelease: false
|
|
90
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
91
|
-
requirements:
|
|
92
|
-
- - ! '>='
|
|
93
|
-
- !ruby/object:Gem::Version
|
|
94
|
-
version: 0.6.3
|
|
95
81
|
- !ruby/object:Gem::Dependency
|
|
96
82
|
name: fog-aws
|
|
97
83
|
requirement: !ruby/object:Gem::Requirement
|
|
98
84
|
requirements:
|
|
99
85
|
- - ! '>='
|
|
100
86
|
- !ruby/object:Gem::Version
|
|
101
|
-
version:
|
|
87
|
+
version: 1.2.1
|
|
102
88
|
type: :runtime
|
|
103
89
|
prerelease: false
|
|
104
90
|
version_requirements: !ruby/object:Gem::Requirement
|
|
105
91
|
requirements:
|
|
106
92
|
- - ! '>='
|
|
107
93
|
- !ruby/object:Gem::Version
|
|
108
|
-
version:
|
|
94
|
+
version: 1.2.1
|
|
109
95
|
- !ruby/object:Gem::Dependency
|
|
110
96
|
name: ey_enzyme
|
|
111
97
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -293,6 +279,7 @@ files:
|
|
|
293
279
|
- spec/gpg.public
|
|
294
280
|
- spec/gpg.sekrit
|
|
295
281
|
- spec/helpers.rb
|
|
282
|
+
- spec/log/ey_flex_postgresql_db_wanting.sizes
|
|
296
283
|
- spec/snapshot_minder_spec.rb
|
|
297
284
|
- spec/spec_helper.rb
|
|
298
285
|
homepage: http://developer.engineyard.com
|
|
@@ -333,6 +320,7 @@ test_files:
|
|
|
333
320
|
- spec/gpg.public
|
|
334
321
|
- spec/gpg.sekrit
|
|
335
322
|
- spec/helpers.rb
|
|
323
|
+
- spec/log/ey_flex_postgresql_db_wanting.sizes
|
|
336
324
|
- spec/snapshot_minder_spec.rb
|
|
337
325
|
- spec/spec_helper.rb
|
|
338
326
|
- features/downloading_a_mysql_backup.feature
|