ridoku 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.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/AUTHORS +3 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +23 -0
- data/README.md +271 -0
- data/Rakefile +1 -0
- data/bin/rid +3 -0
- data/bin/ridoku +350 -0
- data/lib/helpers.rb +13 -0
- data/lib/io-colorize.rb +35 -0
- data/lib/options.rb +142 -0
- data/lib/ridoku.rb +11 -0
- data/lib/ridoku/backup.rb +393 -0
- data/lib/ridoku/base.rb +717 -0
- data/lib/ridoku/config_wizard.rb +195 -0
- data/lib/ridoku/cook.rb +103 -0
- data/lib/ridoku/create.rb +101 -0
- data/lib/ridoku/cron.rb +227 -0
- data/lib/ridoku/db.rb +235 -0
- data/lib/ridoku/defaults.rb +68 -0
- data/lib/ridoku/deploy.rb +157 -0
- data/lib/ridoku/domain.rb +124 -0
- data/lib/ridoku/dump.rb +132 -0
- data/lib/ridoku/env.rb +118 -0
- data/lib/ridoku/list.rb +168 -0
- data/lib/ridoku/log.rb +77 -0
- data/lib/ridoku/maintenance.rb +76 -0
- data/lib/ridoku/packages.rb +93 -0
- data/lib/ridoku/rails_defaults.rb +160 -0
- data/lib/ridoku/run.rb +137 -0
- data/lib/ridoku/service.rb +158 -0
- data/lib/ridoku/services/postgres.rb +77 -0
- data/lib/ridoku/services/rabbitmq.rb +48 -0
- data/lib/ridoku/version.rb +5 -0
- data/lib/ridoku/workers.rb +138 -0
- data/ridoku.gemspec +32 -0
- metadata +211 -0
data/lib/helpers.rb
ADDED
data/lib/io-colorize.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Add colorize method to the IO class.
|
2
|
+
|
3
|
+
class IO
|
4
|
+
def colorize(input, args)
|
5
|
+
args = [args] unless args.is_a?(Array)
|
6
|
+
colors = {
|
7
|
+
black: ["\033[30m", "\033[0m"],
|
8
|
+
red: ["\033[31m", "\033[0m"],
|
9
|
+
green: ["\033[32m", "\033[0m"],
|
10
|
+
brown: ["\033[33m", "\033[0m"],
|
11
|
+
blue: ["\033[34m", "\033[0m"],
|
12
|
+
magenta: ["\033[35m", "\033[0m"],
|
13
|
+
cyan: ["\033[36m", "\033[0m"],
|
14
|
+
gray: ["\033[37m", "\033[0m"],
|
15
|
+
bg_black: ["\033[40m", "\0330m"],
|
16
|
+
bg_red: ["\033[41m", "\033[0m"],
|
17
|
+
bg_green: ["\033[42m", "\033[0m"],
|
18
|
+
bg_brown: ["\033[43m", "\033[0m"],
|
19
|
+
bg_blue: ["\033[44m", "\033[0m"],
|
20
|
+
bg_magenta: ["\033[45m", "\033[0m"],
|
21
|
+
bg_cyan: ["\033[46m", "\033[0m"],
|
22
|
+
bg_gray: ["\033[47m", "\033[0m"],
|
23
|
+
bold: ["\033[1m", "\033[22m"],
|
24
|
+
reverse_color: ["\033[7m", "\033[27m"]
|
25
|
+
}
|
26
|
+
return input unless self.isatty
|
27
|
+
|
28
|
+
args.each do |ar|
|
29
|
+
next unless colors.key?(ar)
|
30
|
+
input = "#{colors[ar].first}#{input}#{colors[ar].last}"
|
31
|
+
end
|
32
|
+
|
33
|
+
input
|
34
|
+
end
|
35
|
+
end
|
data/lib/options.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'getoptlong'
|
2
|
+
|
3
|
+
module Ridoku
|
4
|
+
def self.init_options
|
5
|
+
@options = [
|
6
|
+
[ '--debug', '-D', GetoptLong::NO_ARGUMENT,<<-EOF
|
7
|
+
|
8
|
+
Turn on debugging outputs (for AWS and Exceptions).
|
9
|
+
EOF
|
10
|
+
],
|
11
|
+
[ '--no-wait', '-n', GetoptLong::NO_ARGUMENT,<<-EOF
|
12
|
+
|
13
|
+
When issuing a command, do not wait for the command to return.
|
14
|
+
EOF
|
15
|
+
],
|
16
|
+
[ '--key', '-k', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
17
|
+
<key>
|
18
|
+
Use the specified key as the AWS_ACCESS_KEY
|
19
|
+
EOF
|
20
|
+
],
|
21
|
+
[ '--force', '-F', GetoptLong::NO_ARGUMENT,<<-EOF
|
22
|
+
|
23
|
+
Used to force an operation (e.g., deploy)
|
24
|
+
EOF
|
25
|
+
],
|
26
|
+
[ '--secret', '-s', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
27
|
+
<secret>
|
28
|
+
Use the specified secret as the AWS_SECRET_KEY
|
29
|
+
EOF
|
30
|
+
],
|
31
|
+
[ '--set-app', '-A', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
32
|
+
<app>
|
33
|
+
Use the specified App as the default Application.
|
34
|
+
EOF
|
35
|
+
],
|
36
|
+
[ '--set-backup-bucket', '-B', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
37
|
+
<bucket name>
|
38
|
+
Use the specified bucket name as the default Backup Bucket.
|
39
|
+
EOF
|
40
|
+
],
|
41
|
+
[ '--backup-bucket', '-b', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
42
|
+
<bucket name>
|
43
|
+
Use the specified bucket name as the current Backup Bucket.
|
44
|
+
EOF
|
45
|
+
],
|
46
|
+
[ '--set-stack', '-S', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
47
|
+
<stack>
|
48
|
+
Use the specified Stack as the default Stack.
|
49
|
+
EOF
|
50
|
+
],
|
51
|
+
[ '--set-user', '-U', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
52
|
+
<user>
|
53
|
+
Use the specified user as the default login user in 'run:shell'.
|
54
|
+
EOF
|
55
|
+
],
|
56
|
+
[ '--set-ssh-key', '-K', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
57
|
+
<key file>
|
58
|
+
Use the specified file as the default ssh key file.
|
59
|
+
EOF
|
60
|
+
],
|
61
|
+
[ '--ssh-key', '-f', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
62
|
+
<key file>
|
63
|
+
Override the default ssh key file for this call.
|
64
|
+
EOF
|
65
|
+
],
|
66
|
+
[ '--app', '-a', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
67
|
+
<app>
|
68
|
+
Override the default App name for this call.
|
69
|
+
EOF
|
70
|
+
],
|
71
|
+
[ '--stack', '-t', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
72
|
+
<stack>
|
73
|
+
Override the default Stack name for this call.
|
74
|
+
EOF
|
75
|
+
],
|
76
|
+
[ '--instances', '-i', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
77
|
+
<instances>
|
78
|
+
Run command on specified instances; valid delimiters: ',' or ':'
|
79
|
+
example:
|
80
|
+
ridoku deploy --instances mukujara,tanuki
|
81
|
+
EOF
|
82
|
+
],
|
83
|
+
[ '--user', '-u', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
84
|
+
<user>
|
85
|
+
Override the default user name for this call.
|
86
|
+
EOF
|
87
|
+
],
|
88
|
+
[ '--comment', '-m', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
89
|
+
<message>
|
90
|
+
Optional for: #{$stderr.colorize('deploy', :bold)}
|
91
|
+
Add the specified message to the deploy:* action.
|
92
|
+
EOF
|
93
|
+
],
|
94
|
+
[ '--domains', '-d', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
95
|
+
<domains>
|
96
|
+
Optional for: #{$stderr.colorize('create:app', :bold)}
|
97
|
+
Add the specified domains to the newly created application.
|
98
|
+
EOF
|
99
|
+
],
|
100
|
+
[ '--lines', '-L', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
101
|
+
<lines>
|
102
|
+
Optional for: #{$stderr.colorize('log:*', :bold)}
|
103
|
+
Print the specified number of lines.
|
104
|
+
EOF
|
105
|
+
],
|
106
|
+
[ '--migrate', '-M', GetoptLong::NO_ARGUMENT,<<-EOF
|
107
|
+
|
108
|
+
Optional for: #{$stderr.colorize('deploy', :bold)}
|
109
|
+
Migrate the database after deploying the source.
|
110
|
+
EOF
|
111
|
+
],
|
112
|
+
[ '--layer', '-l', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
113
|
+
EOF
|
114
|
+
],
|
115
|
+
[ '--repo', '-r', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
116
|
+
EOF
|
117
|
+
],
|
118
|
+
[ '--service-arn', '-V', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
119
|
+
EOF
|
120
|
+
],
|
121
|
+
[ '--instance-arn', '-N', GetoptLong::REQUIRED_ARGUMENT,<<-EOF
|
122
|
+
EOF
|
123
|
+
],
|
124
|
+
[ '--practice', '-p', GetoptLong::NO_ARGUMENT,<<-EOF
|
125
|
+
EOF
|
126
|
+
],
|
127
|
+
[ '--wizard', '-w', GetoptLong::NO_ARGUMENT,<<-EOF
|
128
|
+
EOF
|
129
|
+
],
|
130
|
+
]
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.add_options(opts)
|
134
|
+
@options<< opts
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.options
|
138
|
+
@options
|
139
|
+
end
|
140
|
+
|
141
|
+
init_options
|
142
|
+
end
|
data/lib/ridoku.rb
ADDED
@@ -0,0 +1,393 @@
|
|
1
|
+
#
|
2
|
+
# Command: backup
|
3
|
+
# Description: List/Modify the current apps database backups
|
4
|
+
# backup - lists the database backups
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'ridoku/base'
|
8
|
+
|
9
|
+
module Ridoku
|
10
|
+
register :backup
|
11
|
+
|
12
|
+
class Backup < Base
|
13
|
+
attr_accessor :dbase
|
14
|
+
|
15
|
+
def run
|
16
|
+
cline = Base.config[:command]
|
17
|
+
current = cline.shift
|
18
|
+
command = cline.shift
|
19
|
+
sub = cline.shift
|
20
|
+
|
21
|
+
case command
|
22
|
+
when 'list', nil, 'info'
|
23
|
+
list
|
24
|
+
when 'init'
|
25
|
+
init
|
26
|
+
when 'capture'
|
27
|
+
capture(sub)
|
28
|
+
when 'url'
|
29
|
+
url(ARGV.shift)
|
30
|
+
when 'restore'
|
31
|
+
restore(sub, ARGV.shift)
|
32
|
+
when 'delete', 'remove', 'rm'
|
33
|
+
remove(ARGV.shift)
|
34
|
+
else
|
35
|
+
print_backup_help
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def load_database
|
42
|
+
Base.fetch_stack
|
43
|
+
Base.fetch_layer
|
44
|
+
self.dbase =
|
45
|
+
Base.custom_json['deploy'][Base.config[:app]]['database']
|
46
|
+
end
|
47
|
+
|
48
|
+
def print_backup_help
|
49
|
+
$stderr.puts <<-EOF
|
50
|
+
Command: backup
|
51
|
+
|
52
|
+
List/Modify the current app's database backups.
|
53
|
+
backup[:list] lists the stored database backups.
|
54
|
+
backup:init initialize backup capability (check S3 permissions, and
|
55
|
+
generate required S3 buckets).
|
56
|
+
backup:rm <name> remove specified backup by name.
|
57
|
+
backup:capture capture a backup form the specified application database.
|
58
|
+
backup:url <name> get a download URL for the specified database backup.
|
59
|
+
|
60
|
+
Development:
|
61
|
+
backup:capture:local <pg_dump path>
|
62
|
+
Capture a backup locally using the apps config. Include the path the
|
63
|
+
desired 'pg_dump' command. Version 9.2 or above is required.
|
64
|
+
backup:restore:local <pg_restore path>
|
65
|
+
Restore a backup locally using the apps config. Include the path the
|
66
|
+
desired 'pg_restore' command. Version 9.2 or above is required.
|
67
|
+
|
68
|
+
EOF
|
69
|
+
end
|
70
|
+
|
71
|
+
def objects_from_bucket(bucket)
|
72
|
+
s3 = AWS::S3.new
|
73
|
+
|
74
|
+
bucket = s3.buckets[bucket]
|
75
|
+
|
76
|
+
# hack: couldn't find how to get an object count...
|
77
|
+
objects = 0
|
78
|
+
|
79
|
+
bucket.objects.map do |obj|
|
80
|
+
{ key: obj.key, size: obj.content_length, type: obj.content_type }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def object_list_to_hash(list)
|
85
|
+
final = {}
|
86
|
+
max = 0
|
87
|
+
|
88
|
+
list.each do |obj|
|
89
|
+
comp = obj[:key].match(
|
90
|
+
%r(^(.*)-([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2}).sqd$)
|
91
|
+
)
|
92
|
+
|
93
|
+
max = obj[:key].length if obj[:key].length > max
|
94
|
+
|
95
|
+
unless comp.nil?
|
96
|
+
app = comp[1]
|
97
|
+
final[app] ||= []
|
98
|
+
obj[:app] = app
|
99
|
+
obj[:date] = "#{comp[3]}/#{comp[4]}/#{comp[2]} #{comp[5]}:#{comp[6]}"
|
100
|
+
final[app] << obj
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
[ final, max ]
|
105
|
+
end
|
106
|
+
|
107
|
+
def pspace(str, max)
|
108
|
+
str + ' ' * (max - str.length)
|
109
|
+
end
|
110
|
+
|
111
|
+
def list
|
112
|
+
bucket_exists!
|
113
|
+
|
114
|
+
list = objects_from_bucket(Base.config[:backup_bucket])
|
115
|
+
app_hash, max = object_list_to_hash(list)
|
116
|
+
|
117
|
+
app_hash.each do |key, value|
|
118
|
+
$stdout.puts "#{$stdout.colorize(key, :green)}:"
|
119
|
+
value.each do |obj|
|
120
|
+
$stdout.puts " #{pspace(obj[:key], max)} "\
|
121
|
+
"#{obj[:date]}\t#{obj[:type]}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def init
|
127
|
+
fail_undefined_bucket! if Base.config[:backup_bucket].nil?
|
128
|
+
|
129
|
+
s3 = AWS::S3.new
|
130
|
+
bucket = s3.buckets[Base.config[:backup_bucket]]
|
131
|
+
$stdout.puts "Checking for '#{$stdout.colorize(bucket.name, :bold)}' "\
|
132
|
+
'S3 Buckets...'
|
133
|
+
if bucket.exists?
|
134
|
+
$stdout.puts "Bucket exists!"
|
135
|
+
else
|
136
|
+
$stdout.puts "Bucket does not exist. Generating..."
|
137
|
+
begin
|
138
|
+
bucket = s3.buckets.create(Base.config[:backup_bucket],
|
139
|
+
acl: :bucket_owner_full_control)
|
140
|
+
rescue => e
|
141
|
+
$stderr.puts "Oops! Something went wrong when trying to create your bucket!"
|
142
|
+
raise e
|
143
|
+
end
|
144
|
+
|
145
|
+
$stdout.puts 'Bucket created successfully!'
|
146
|
+
$stdout.puts "#{$stdout.colorize(bucket.name, [:bold, :green])}: owner read/write."
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def bucket_exists!
|
151
|
+
# Fail if bucket not set
|
152
|
+
fail_undefined_bucket! if Base.config[:backup_bucket].nil?
|
153
|
+
|
154
|
+
s3 = AWS::S3.new
|
155
|
+
bucket = s3.buckets[Base.config[:backup_bucket]]
|
156
|
+
|
157
|
+
$stdout.print "Checking for '#{$stdout.colorize(bucket.name, :bold)}' "\
|
158
|
+
'S3 Buckets... ' if $stdout.tty?
|
159
|
+
|
160
|
+
fail_uninitialized_bucket! unless bucket.exists?
|
161
|
+
|
162
|
+
$stdout.puts $stdout.colorize('Found!', :green) if $stdout.tty?
|
163
|
+
end
|
164
|
+
|
165
|
+
def object_exists!(sub, action)
|
166
|
+
fail_undefined_object!(sub, action) if sub.nil? || !sub.present?
|
167
|
+
|
168
|
+
s3 = AWS::S3.new
|
169
|
+
bucket = s3.buckets[Base.config[:backup_bucket]]
|
170
|
+
$stdout.print "Checking for '#{$stdout.colorize(sub, :bold)}' "\
|
171
|
+
'in bucket... ' if $stdout.tty?
|
172
|
+
|
173
|
+
object = bucket.objects[sub]
|
174
|
+
fail_object_not_found! unless object.exists?
|
175
|
+
|
176
|
+
$stdout.puts $stdout.colorize('Found!', :green) if $stdout.tty?
|
177
|
+
|
178
|
+
object
|
179
|
+
end
|
180
|
+
|
181
|
+
def dump_local()
|
182
|
+
load_database
|
183
|
+
|
184
|
+
command = ARGV.shift
|
185
|
+
|
186
|
+
unless m = `#{command} --version`.match(/(9[.][23]([.][0-9]+)?)/)
|
187
|
+
$stderr.puts "Invalid pg_dump version #{m[1]}."
|
188
|
+
return
|
189
|
+
end
|
190
|
+
|
191
|
+
backup_file = "#{Base.config[:app]}-"\
|
192
|
+
"#{Time.now.utc.strftime("%Y%m%d%H%M%S")}.sql"
|
193
|
+
|
194
|
+
$stdout.puts "pg_dump version: #{$stdout.colorize(m[1], :green)}"
|
195
|
+
$stdout.puts "Creating backup file: #{backup_file}"
|
196
|
+
|
197
|
+
# Add PGPASSWORD to the environment.
|
198
|
+
ENV['PGPASSWORD'] = dbase['password']
|
199
|
+
|
200
|
+
system(["#{command}",
|
201
|
+
"-Fc",
|
202
|
+
"-h #{dbase['host']}",
|
203
|
+
"-U #{dbase['username']}",
|
204
|
+
"-p #{dbase['port']}",
|
205
|
+
"#{dbase['database']}",
|
206
|
+
"> #{backup_file}"].join(' '))
|
207
|
+
|
208
|
+
$stdout.puts $stdout.colorize("pg_dump complete.", :green)
|
209
|
+
$stdout.puts "File size: #{::File.size(backup_file)}"
|
210
|
+
ENV['PGPASSWORD'] = nil
|
211
|
+
end
|
212
|
+
|
213
|
+
def capture(opt)
|
214
|
+
return dump_local if opt == 'local'
|
215
|
+
|
216
|
+
bucket_exists!
|
217
|
+
|
218
|
+
recipe_data = {
|
219
|
+
backup: {
|
220
|
+
databases: Base.config[:app].downcase.split(','),
|
221
|
+
dump: {
|
222
|
+
type: 's3',
|
223
|
+
region: 'us-west-1',
|
224
|
+
bucket: Base.config[:backup_bucket]
|
225
|
+
}
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
229
|
+
Base.fetch_app
|
230
|
+
Base.fetch_instance
|
231
|
+
instances = Base.get_instances_for_layer('postgresql')
|
232
|
+
instance_ids = instances.map { |inst| inst[:instance_id] }
|
233
|
+
|
234
|
+
command = Base.execute_recipes(Base.app[:app_id], instance_ids,
|
235
|
+
'Capturing application DB backup.', ['s3',
|
236
|
+
'postgresql::backup_database'], recipe_data)
|
237
|
+
|
238
|
+
Base.run_command(command)
|
239
|
+
end
|
240
|
+
|
241
|
+
def restore_local(file)
|
242
|
+
load_database
|
243
|
+
|
244
|
+
command = ARGV.shift
|
245
|
+
|
246
|
+
unless m = `#{command} --version`.match(/(9[.][23]([.][0-9]+)?)/)
|
247
|
+
$stderr.puts "Invalid pg_restore version #{m[1]}."
|
248
|
+
return
|
249
|
+
end
|
250
|
+
|
251
|
+
$stdout.puts "pg_restore version: #{$stdout.colorize(m[1], :green)}"
|
252
|
+
$stdout.puts "Using backup file: #{file}"
|
253
|
+
|
254
|
+
# Add PGPASSWORD to the environment.
|
255
|
+
ENV['PGPASSWORD'] = dbase['password']
|
256
|
+
|
257
|
+
system(["#{command}",
|
258
|
+
"--clean",
|
259
|
+
"#{'--single-transaction' unless Base.config[:force]}",
|
260
|
+
"-h #{dbase['host']}",
|
261
|
+
"-U #{dbase['username']}",
|
262
|
+
"-p #{dbase['port']}",
|
263
|
+
"-d #{dbase['database']}",
|
264
|
+
"#{file}"].join(' '))
|
265
|
+
|
266
|
+
$stdout.puts $stdout.colorize("pg_restore complete.", :green)
|
267
|
+
$stdout.puts "File size: #{::File.size(backup_file)}"
|
268
|
+
ENV['PGPASSWORD'] = nil
|
269
|
+
end
|
270
|
+
|
271
|
+
def restore(sub, arg)
|
272
|
+
return restore_local(file) if sub == 'local'
|
273
|
+
|
274
|
+
bucket_exists!
|
275
|
+
object_exists!(arg, 'restore')
|
276
|
+
$stdout.puts 'Are you sure you want to restore this database dump? [yes/N]'
|
277
|
+
res = $stdin.gets.chomp
|
278
|
+
|
279
|
+
if res.present? && res == 'yes'
|
280
|
+
recipe_data = {
|
281
|
+
backup: {
|
282
|
+
databases: Base.config[:app].downcase.split(','),
|
283
|
+
force: Base.config[:force] || false,
|
284
|
+
dump: {
|
285
|
+
type: 's3',
|
286
|
+
region: 'us-west-1',
|
287
|
+
bucket: Base.config[:backup_bucket],
|
288
|
+
key: arg
|
289
|
+
}
|
290
|
+
}
|
291
|
+
}
|
292
|
+
|
293
|
+
Base.fetch_app
|
294
|
+
Base.fetch_instance
|
295
|
+
layer_ids = Base.get_layer_ids('postgresql')
|
296
|
+
|
297
|
+
Base.instances.select! do |inst|
|
298
|
+
next false unless inst[:status] == 'online'
|
299
|
+
next layer_ids.each do |id|
|
300
|
+
break true if inst[:layer_ids].include?(id)
|
301
|
+
end || false
|
302
|
+
end
|
303
|
+
|
304
|
+
instance_ids = Base.instances.map { |inst| inst[:instance_id] }
|
305
|
+
|
306
|
+
command = Base.execute_recipes(Base.app[:app_id], instance_ids,
|
307
|
+
'Restoring application DB backup.', ['s3',
|
308
|
+
'postgresql::restore_database'], recipe_data)
|
309
|
+
|
310
|
+
Base.run_command(command)
|
311
|
+
else
|
312
|
+
$stdout.puts 'Aborting.'
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def url(sub)
|
317
|
+
bucket_exists!
|
318
|
+
object = object_exists!(sub, 'get url')
|
319
|
+
|
320
|
+
$stdout.puts object.url_for(:read, expires: 60)
|
321
|
+
end
|
322
|
+
|
323
|
+
def remove(sub)
|
324
|
+
bucket_exists!
|
325
|
+
object = object_exists!(sub, 'remove')
|
326
|
+
|
327
|
+
$stdout.puts 'Are you sure you want to delete this file? [yes/N]'
|
328
|
+
res = $stdin.gets.chomp
|
329
|
+
|
330
|
+
if res.present? && res == 'yes'
|
331
|
+
s3 = AWS::S3.new
|
332
|
+
object.delete
|
333
|
+
|
334
|
+
$stdout.puts 'Request object deleted.'
|
335
|
+
else
|
336
|
+
$stdout.puts 'Aborting.'
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
protected
|
341
|
+
|
342
|
+
def fail_undefined_bucket!
|
343
|
+
fail ArgumentError.new(<<EOF
|
344
|
+
Your Backup S3 bucket name is undefined.
|
345
|
+
Please set it by passing in --set-backup-bucket followed by the desired bucket
|
346
|
+
name.
|
347
|
+
|
348
|
+
Example:
|
349
|
+
$ ridoku --set-backup-bucket zv1ns-database-backups
|
350
|
+
|
351
|
+
EOF
|
352
|
+
)
|
353
|
+
end
|
354
|
+
|
355
|
+
def fail_uninitialized_bucket!
|
356
|
+
# Fail if bucket doesn't exist.
|
357
|
+
fail ArgumentError.new(<<EOF
|
358
|
+
|
359
|
+
The specified S3 backup bucket does not yet exist. Please create it manually,
|
360
|
+
or by running ([] represents optional arguments):
|
361
|
+
|
362
|
+
$ ridoku backup:init [--backup-bucket <bucket name>]
|
363
|
+
|
364
|
+
EOF
|
365
|
+
)
|
366
|
+
end
|
367
|
+
|
368
|
+
def fail_undefined_object!(sub, action)
|
369
|
+
# Fail if object name not set
|
370
|
+
fail ArgumentError.new(<<EOF
|
371
|
+
The specified backup (#{Base.config[:backup_bucket]}/#{sub}) doesn't exist!
|
372
|
+
|
373
|
+
Example:
|
374
|
+
$ ridoku backup:#{action} <name>
|
375
|
+
|
376
|
+
EOF
|
377
|
+
)
|
378
|
+
end
|
379
|
+
|
380
|
+
def fail_object_not_found!
|
381
|
+
# Fail if bucket doesn't exist.
|
382
|
+
fail ArgumentError.new(<<EOF
|
383
|
+
|
384
|
+
The specified S3 backup object does not yet exist. Please create it manually,
|
385
|
+
or by running ([] represents optional arguments):
|
386
|
+
|
387
|
+
$ ridoku backup:capture [--backup-bucket <bucket name> --app <app name>]
|
388
|
+
|
389
|
+
EOF
|
390
|
+
)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|