ey_cloud_server 1.4.54 → 1.5.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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZTdlYWY2NzRmNzI0YjA2ODVlNzBhYWM5ZTQyZTIzODJjMjEyMmJjYw==
4
+ ZmFkNjBkYjY1OTNhZjQwNjMxNjUxMmNmOTRmOWQyZDMxMDJlNTUzYQ==
5
5
  data.tar.gz: !binary |-
6
- ZDkxZjUxMjA5Njc3MTI3NmViNmEwYmJhMDZlZjgwNTMyNTBkNWIzOQ==
6
+ YTA0NTBmMTAxYzk5ZmE5YjQ0MzViYjE4NWE2YjRjZGM5YWYxZDNiNQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YmQ1MDQwODBkMWNlYTFkMmI5NzM0YzdhM2UyYzVkNDQyZmNlODJjOTdkNDc0
10
- YWM3ZjliNjFiZDhhMjc2MmFlZTRjMzU5MjUzY2FhNzQ0ZjkyYTUyZDQzMzM2
11
- YTQwZjI3MzZmYTZhNTNlYTgzZDg2ZDFiNWNkMzg5ZDZlZDBjMzc=
9
+ ZmRlZTQ1YThjMWU0N2RkMzVlNmFkNGY3YThmMDJhMzRjZTE4MzU4ZGJjOTAx
10
+ NzM5MDc4OGZmNTMxNzM1OGI1ZTk4NDgxZjY5MTIwYmZmMTI1NmNjNjc0NmFl
11
+ OTI3OWFmMjdiZGE0N2QwZmEyNjljYzQ0NTg0MjBmZWI3NTBiOWU=
12
12
  data.tar.gz: !binary |-
13
- MDQwZWY0MzgzMGI4NTI2MTgyMGQ0ZWQ5MzRlM2EzOTI5ZWFlY2EzMzAxMmRi
14
- MTAwNDI3YzI0ODU4YmY3YzliZTcxNmFlNmNjMWM0Y2I3YzYzMDNkODdmODEx
15
- ZWM4YTk3NTM3NTM1MGMwZjcxYTc1ODYyM2Q5ZWY5YTRkZTg0YTQ=
13
+ MTdlZDkwYmQ1ZmNiYTJmY2U0YWMzMmQ2YjZmNzA5NWEzYTVlNTA4ODM1ZGZj
14
+ ZTBkN2EwZmVlODczYTAxMjYwNzA3OTk3ODcxODlkOTQwZjYwZDNhNjE4Mjk2
15
+ YThjMTZiNjBmMmY3NTE5Yjg0NjI2YjRkOGQwMDg0YmQ4OTY3ZGM=
data/bin/ey-snapshots CHANGED
@@ -10,6 +10,7 @@ require File.dirname(__FILE__) + '/../lib/ey-flex'
10
10
  begin
11
11
  EY::SnapshotMinder.run(ARGV)
12
12
  rescue => e
13
- EY.notify_snapshot_error(e)
13
+ puts e.message
14
+ EY.notify_snapshot_error(e) unless STDOUT.isatty
14
15
  raise
15
16
  end
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
- EY.notify_backup_error(e)
16
- raise
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 ADDED
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'yaml'
5
+ require 'tempfile'
6
+ require 'timeout'
7
+
8
+ def opt_parse(argv)
9
+ options = {}
10
+ @optparse = OptionParser.new do |opts|
11
+ opts.banner = "Usage: #{__FILE__} --env <environment name> --database <database_name> --action <action> [options]"
12
+ opts.separator ''
13
+ opts.separator "Wrapper for eybackup used for listing, downloading and restoring database backups. The main purpose for this tool is to"
14
+ opts.separator "simplify the process of accessing backups from one environment in a different environment (e.g. access Production backups"
15
+ opts.separator "from Staging). This can also be used for restoring backups within the current environment."
16
+ opts.separator ''
17
+ opts.on('-h', '--help', 'Displays this Help message') { puts opts; exit}
18
+
19
+ opts.separator ''
20
+ opts.separator 'Common Options:'
21
+
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 list backups for.") { |dbname| options[:databases] = [dbname] }
24
+ actions=['list', 'restore', 'download']
25
+ opts.on('-a', '--action action_name', "The action you want to perform using the backup #{actions}.") do |action|
26
+ unless actions.include?(action)
27
+ puts "Invalid action: '#{action}' must be one of #{actions}\n\n"
28
+ puts opts
29
+ exit 1
30
+ end
31
+ options[:action] = action
32
+ end
33
+ opts.on('-i', '--index BACKUP_INDEX', 'Download/Restore the backup specified by index','BACKUP_INDEX uses the format #{index_number}:#{db_name}',
34
+ "The string 'last' will automatically reference the most recent available backup.") do |index|
35
+ options[:index] = index
36
+ end
37
+ opts.on('-f', '--force', 'Force mode, skips all confirmation prompts for use with automated restores.') { options[:force] = true }
38
+
39
+ opts.separator ''
40
+ opts.separator 'Additional Options:'
41
+
42
+ opts.on('-u', '--aws_secret_id key', 'AWS S3 Key') { |key| options[:aws_key_id] = key }
43
+ opts.on('-p', '--aws_secret_key secret', 'AWS S3 Secret Key') { |secret| options[:aws_secret_key] = secret }
44
+ opts.on('-b', '--backup_bucket bucket', 'AWS S3 bucket identifier') { |bucket| options[:backup_bucket] = bucket }
45
+ opts.on('-v', '--verbose', "Enable debug info for this wrapper.") { $verbose = true }
46
+ opts.on('-c', '--config', "Alternate config file for eyrestore (default ~/.eyrestore.confg.yml)") do |path|
47
+ unless File.exists?(path)
48
+ puts "Invalid config file path '#{path}', no file at that location."
49
+ exit 1
50
+ end
51
+ options[:def_config] = path
52
+ end
53
+ opts.separator ''
54
+ opts.separator 'Examples:'
55
+ opts.separator '* List backups from an environment named production for a database named todo'
56
+ opts.separator " sudo -i #{__FILE__} --env production --database todo --action list"
57
+ opts.separator ''
58
+ opts.separator '* Download the most recent backup from an environment named production for a database named todo'
59
+ opts.separator " sudo -i #{__FILE__} --env production --database todo --action download --index last"
60
+ opts.separator ''
61
+ opts.separator "* Download a backup from an environment named production for a database named todo, prompt with a list of available backups"
62
+ opts.separator " sudo -i #{__FILE__} --env production --database todo --action download"
63
+ opts.separator ''
64
+ opts.separator '* Restore the most recent backup from an environment named production for a database named todo'
65
+ opts.separator " sudo -i #{__FILE__} --env production --database todo --action restore --index last"
66
+ opts.separator ''
67
+ end
68
+ @optparse.parse!
69
+ options
70
+ end
71
+
72
+ def debug(msg)
73
+ puts '[' + Time.now.strftime("%D %T") + ']: DEBUG: ' + msg if $verbose
74
+ end
75
+
76
+ def find_engine
77
+ debug("Searching for database engine based on file '/etc/.*.backups.yml'")
78
+ main_config = Dir['/etc/.*.backups.yml'].first
79
+ abort "Failed to find eybackup config at '/etc/.*.backups.yml', this needs to run on a database instance." unless main_config
80
+ debug("Found file '#{main_config}'")
81
+ File.basename(main_config).split('.')[1]
82
+ end
83
+
84
+ def extra_validations(options)
85
+ mandatory = [:databases, :env, :action]
86
+ mandatory << :index if %w(download restore).include? options[:action]
87
+ missing = mandatory.select{ |param| options[param].nil? }
88
+ raise OptionParser::MissingArgument, missing.join(',') unless missing.empty?
89
+ end
90
+
91
+ def list_backups(engine, db, path)
92
+ command = "eybackup -e #{engine} -l #{db} -c #{path}"
93
+ debug("Listing backups with command: #{command}")
94
+ res = %x{#{command}}
95
+ last = res.chomp.split("\n").last
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
99
+ res
100
+ end
101
+
102
+ def gets_timeout( prompt, secs )
103
+ print prompt + "[timeout=#{secs}secs]: "
104
+ Timeout::timeout( secs ) { gets.chomp }
105
+ rescue Timeout::Error
106
+ puts "*timeout"
107
+ '' # return nil if timeout
108
+ end
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
+
118
+ def last_backup_idx(listing)
119
+ last = listing.chomp.split("\n").last
120
+ if last.split.first.match(/\d+:\w+/)
121
+ index = last.split.first
122
+ else
123
+ index = last.split[3]
124
+ end
125
+ end
126
+
127
+ options = opt_parse(ARGV)
128
+ options[:def_config] = '~/.eyrestore.config.yml' unless options[:def_config]
129
+
130
+ # auto-detect the type of database running in the given environment
131
+ engine = find_engine
132
+
133
+ # parse eybackup config file
134
+ config = YAML.load_file("/etc/.#{engine}.backups.yml")
135
+ this_env = config[:env]
136
+ # delete these just in case
137
+ config.delete(:env)
138
+ config.delete(:databases)
139
+
140
+ # override config from eyrestore config file
141
+ eyrestore = YAML.load_file(options[:def_config]) if File.exists?(options[:def_config])
142
+ config.merge!(eyrestore) unless eyrestore.nil?
143
+
144
+ # override config from command line options
145
+ config.merge!(options)
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
155
+ debug("Config Options: '#{config}'")
156
+
157
+ # create a temporary configuration file from ~/.eyrestore.config.yml
158
+ temp_config = Tempfile.new("eyrestore")
159
+ temp_config.write(config.to_yaml)
160
+ temp_config.rewind
161
+ debug("Generated temporary config file: '#{temp_config.path}'")
162
+
163
+ db = config[:databases].first
164
+ temp = temp_config.path
165
+ if config[:action] == 'list' or ! config[:index]
166
+ puts list_backups(engine, db, temp)
167
+ end
168
+
169
+ # confirm environment overwrite if restoring
170
+ if config[:action] == 'restore' and ! config[:force]
171
+ response = gets_timeout("You are restoring the backup for '#{db}' from '#{config[:env]}' into '#{this_env}', THIS MAY BE DESTRUCTIVE; are you sure you want to proceed (Y/n) ? ", 30)
172
+ exit unless response.downcase == 'y'
173
+ end
174
+
175
+
176
+ if ['download','restore'].include?(config[:action])
177
+ config[:index] = gets_timeout("Enter the backup index to use for #{config[:action]} (e.g. 9:#{db}) ", 30) unless config[:index]
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+$/)
182
+
183
+ action = config[:action] == 'restore' ? '-r' : '-d'
184
+ debug("Set action flag for eybackup to '#{action}' based on action of '#{config[:action]}'")
185
+
186
+ # test eybackup version for force option
187
+ ey_cloud_server_version = Gem::Version.new(%x{/usr/local/ey_resin/ruby/bin/gem list ey_cloud_server}.split[1].gsub('(','').gsub(')','').gsub(',',''))
188
+ force = ey_cloud_server_version >= Gem::Version.new('1.4.51') ? '--force' : ''
189
+ debug("eybackup version '#{ey_cloud_server_version}' so force is set to '#{force}'")
190
+
191
+ command="eybackup -e #{engine} -c #{temp} #{action} #{config[:index]} #{force}"
192
+
193
+ puts "Running '#{config[:action]}' on index '#{config[:index]}'."
194
+ debug("Running command: #{command}")
195
+
196
+ res=%x{#{command}}
197
+ puts res
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
207
+ end
208
+
209
+ # cleanup temporary config file
210
+ temp_config.close
211
+ temp_config.unlink
@@ -1,10 +1,12 @@
1
1
  Given \
2
2
  /^there is a mysql database \(([^\)]*)\)$/ do |db_key|
3
3
  create_mysql_database(db_key)
4
+ setup_dna({:db_stack_name => "mysql5_5"})
4
5
  end
5
6
 
6
7
  Given \
7
8
  /^the "([^\"]*)" mysql database is dropped$/ do |db_key|
8
9
  db_name = created_mysql_dbs[db_key]
9
10
  drop_mysql_database(db_name)
11
+ teardown_dna
10
12
  end
@@ -1,3 +1,6 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
1
4
  require File.dirname(__FILE__) + "/../../lib/ey-flex"
2
5
  require File.dirname(__FILE__) + "/../../lib/ey_backup"
3
6
  require 'fakeweb'
@@ -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(:key => filename, :body => contents)
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
@@ -25,7 +25,7 @@ module EY
25
25
  end
26
26
 
27
27
  def self.enzyme_api
28
- @enzyme_api ||= EY::Enzyme::API.new(
28
+ @enzyme_api ||= EY::Enzyme::API::Client.new(
29
29
  enzyme_config[:api],
30
30
  enzyme_config[:instance_id],
31
31
  enzyme_config[:token],
@@ -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
- @s3.put_object(@bucket_minder.bucket_name, object_name, File.open(filename,'r'))
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
- @s3.delete_object(@bucket_minder.bucket_name, object_name) rescue nil
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
@@ -31,26 +31,26 @@ module EY
31
31
  end
32
32
 
33
33
  def self.list(databases)
34
- names = databases.map do |db|
35
- db.name
36
- end
37
- info "Listing database backups for #{names.join(', ')}"
38
-
39
- backups = databases.map{ |db| db.backups }.flatten
40
-
41
- puts "#{backups.size} backup(s) found"
42
-
43
- backups.each_with_index do |backup_set, i|
44
- puts "#{i}:#{backup_set.database_name} #{backup_set.normalized_name}"
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
- backups
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.empty?
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.to_i]
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
@@ -9,18 +9,50 @@ module EY
9
9
 
10
10
  config_path = options[:config] || "/etc/.#{options[:engine]}.backups.yml"
11
11
 
12
- config_for(config_path).merge(options)
12
+ options = config_for(config_path).merge(options)
13
+ options = late_options.merge(options)
14
+
15
+ options[:tmp_dir] = '/mnt/tmp' if options[:tmp_dir].nil?
16
+ options = delayed_engine(File.join(options[:tmp_dir], '/chef/dna.json')).merge(options){|k, v1, v2| v2.nil? ? v1 : v2}
17
+ abort("Unable to determine database stack from dna; specify the engine with -e") if options[:engine].nil?
18
+ options
13
19
  end
14
20
 
15
21
  def default_options
16
22
  {
17
23
  :command => :new_backup,
18
24
  :format => "gzip",
19
- :engine => 'mysql',
25
+ :engine => db_stack_name('/etc/chef/dna.json'),
20
26
  :verbose => false,
21
27
  }
22
28
  end
23
29
 
30
+ def late_options
31
+ {
32
+ :log_coordinates => false,
33
+ :skip_analyze => false,
34
+ :allow_concurrent => false,
35
+ }
36
+ end
37
+
38
+ def delayed_engine(path)
39
+ {
40
+ :engine => db_stack_name(path),
41
+ }
42
+ end
43
+
44
+ def db_stack_name(file)
45
+
46
+ if File.exist?(file)
47
+ case %x{cat #{file} | grep db_stack_name}
48
+ when /mysql/
49
+ 'mysql'
50
+ when /postgres/
51
+ 'postgresql'
52
+ end
53
+ end
54
+ end
55
+
24
56
  def verify_restore
25
57
  res = ''
26
58
  puts "This will overwrite your database, are you sure you would like to proceed (Y/n)?"
@@ -34,14 +66,16 @@ module EY
34
66
  # Build a parser for the command line arguments
35
67
  options = {}
36
68
 
37
- opts = OptionParser.new do |opts|
69
+ optparse = OptionParser.new do |opts|
38
70
  opts.version = EY::CloudServer::VERSION
39
71
 
40
72
  opts.banner = "\nUsage: eybackup [-flag] [argument]"
41
- opts.define_head " eybackup: manage dump (mysqldump/pg_dump) style backups of your database."
73
+ opts.define_head " eybackup: manage logical (mysqldump/pg_dump) style backups of your database.",
74
+ " When backing up multiple databases, each database is backed up separately."
42
75
  opts.separator '*'*80
43
76
 
44
- opts.on("-l", "--list-backup DATABASE", "List backups for DATABASE") do |db|
77
+ opts.on("-l", "--list-backup DATABASE_NAME", "List backups for DATABASE_NAME; ",
78
+ " accepts 'all' to list all databases in the config (/etc/.*.backups.yml)") do |db|
45
79
  if db == "all"
46
80
  db = nil
47
81
  end
@@ -52,7 +86,9 @@ module EY
52
86
  opts.on("-e", "--engine DATABASE_ENGINE", "The database engine. ex: mysql, postgresql.") do |engine|
53
87
  options[:engine] = engine
54
88
  end
55
- opts.on("-n", "--new-backup", "Create a new backup (default)") do
89
+ opts.on("-n", "--new-backup [DATABASE_NAME]", "Create a new backup (default).",
90
+ " Backs up each database in '/etc/.*.backups.yml' if DATABASE_NAME not set.") do |db|
91
+ options[:db] = db.split(',') unless db.nil? or db == ''
56
92
  options[:command] = :new_backup
57
93
  end
58
94
 
@@ -89,9 +125,11 @@ module EY
89
125
  ' Run `eybackup -l #{db_name}` to get the index.',
90
126
  ' BACKUP_INDEX uses the format #{index_number}:#{db_name}') do |index_and_db|
91
127
  options[:command] = :download
92
- db, index = split_index(index_and_db)
128
+ index, db = split_index(index_and_db)
129
+ index = index.to_i
93
130
  options[:index] = index
94
131
  options[:db] = db
132
+ 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
133
  end
96
134
 
97
135
  opts.on("-r", "--restore BACKUP_INDEX", "Download and apply the backup specified by index.",
@@ -99,9 +137,11 @@ module EY
99
137
  ' Run `eybackup -l #{db_name}` to get the index.',
100
138
  ' BACKUP_INDEX uses the format #{index_number}:#{db_name}') do |index_and_db|
101
139
  options[:command] = :restore
102
- db, index = split_index(index_and_db)
140
+ index, db = split_index(index_and_db)
141
+ index = index.to_i
103
142
  options[:index] = index
104
143
  options[:db] = db
144
+ 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
145
  end
106
146
 
107
147
  options[:force] = false
@@ -109,16 +149,33 @@ module EY
109
149
  " For use with automated restore operations (e.g. Staging).") do
110
150
  options[:force] = true
111
151
  end
152
+
153
+ opts.on("--log_coordinates", "Record MySQL binary log position so backup can be used with replication or Point in Time functions.") do
154
+ options[:log_coordinates] = true
155
+ end
156
+
157
+ opts.on("--allow_concurrent", "Allow eybackup process to run concurrently with other eybackup processes.") do
158
+ options[:allow_concurrent] = true
159
+ end
160
+
161
+ opts.on("--skip_analyze", "Skip automatic analyze during PostgreSQL Restore operations.") do
162
+ options[:skip_analyze] = true
163
+ end
112
164
 
113
165
  end
114
166
 
115
- opts.parse!(argv)
167
+ begin
168
+ optparse.parse!(argv)
169
+ rescue => e
170
+ puts optparse
171
+ raise
172
+ end
116
173
 
117
174
  options
118
175
  end
119
176
 
120
177
  def split_index(index)
121
- index.split(':').reverse
178
+ index.split(':')
122
179
  end
123
180
 
124
181
  def config_for(filename)
@@ -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
- message.gsub!("\n", '\n')
30
- full_txt= "Severity: #{alert_level}\n" \
31
- + "Time: #{Time.now.to_i}\n" \
32
- + "Type: process-dbbackup summary\n" \
33
- + "Plugin: exec\n" \
34
- + "raw_message: '#{message}'"
35
- full_txt = Shellwords.escape(full_txt)
36
- alert_command = %Q(echo #{full_txt} | /engineyard/bin/ey-alert.rb 2>&1)
37
- system(alert_command)
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("Successful backup: #{@database.name} (#{@database.backup_size})", @database.name)
61
+ okay(@database.name, @database.backup_size)
52
62
  backup_set.cleanup
53
63
  backup_set.rm!
54
64
  @database.backup_size
@@ -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, :log_coordinates
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, log_coordinates)
30
+ @username, @password, @host, @key_id, @force, @allow_concurrent, @skip_analyze, @log_coordinates = username, password, host, key_id, force, allow_concurrent, skip_analyze, log_coordinates
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?