ridoku 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|