sgfa 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/data/sgfa_web.css +37 -34
- data/lib/sgfa/binder.rb +205 -3
- data/lib/sgfa/binder_fs.rb +55 -20
- data/lib/sgfa/cli/binder.rb +248 -31
- data/lib/sgfa/cli/jacket.rb +206 -0
- data/lib/sgfa/entry.rb +96 -0
- data/lib/sgfa/error.rb +2 -2
- data/lib/sgfa/jacket.rb +314 -9
- data/lib/sgfa/jacket_fs.rb +18 -4
- data/lib/sgfa/state_fs.rb +33 -9
- data/lib/sgfa/store_s3.rb +119 -0
- data/lib/sgfa/web/base.rb +7 -0
- data/lib/sgfa/web/binder.rb +294 -85
- metadata +4 -3
data/lib/sgfa/cli/binder.rb
CHANGED
@@ -31,25 +31,6 @@ class Binder < Thor
|
|
31
31
|
desc: 'Path to the binder',
|
32
32
|
}
|
33
33
|
|
34
|
-
class_option :user, {
|
35
|
-
type: :string,
|
36
|
-
desc: 'User name.',
|
37
|
-
default: Etc.getpwuid(Process.euid).name,
|
38
|
-
}
|
39
|
-
|
40
|
-
class_option :body, {
|
41
|
-
type: :string,
|
42
|
-
desc: 'Body of an entry.',
|
43
|
-
default: '!! No body provided using CLI tool !!',
|
44
|
-
}
|
45
|
-
|
46
|
-
class_option :title, {
|
47
|
-
type: :string,
|
48
|
-
desc: 'Title of an entry.',
|
49
|
-
default: '!! No title provided using CLI tool !!',
|
50
|
-
}
|
51
|
-
|
52
|
-
|
53
34
|
|
54
35
|
#####################################
|
55
36
|
# info
|
@@ -57,17 +38,7 @@ class Binder < Thor
|
|
57
38
|
def info
|
58
39
|
|
59
40
|
# open binder
|
60
|
-
if !
|
61
|
-
puts 'Binder type and location required.'
|
62
|
-
return
|
63
|
-
end
|
64
|
-
bnd = ::Sgfa::BinderFs.new
|
65
|
-
begin
|
66
|
-
bnd.open(options[:fs_path])
|
67
|
-
rescue ::Sgfa::Error::Limits, ::Sgfa::Error::NonExistent => exp
|
68
|
-
puts exp.message
|
69
|
-
return
|
70
|
-
end
|
41
|
+
return if !(bnd = _open_binder)
|
71
42
|
|
72
43
|
# print info
|
73
44
|
tr = {
|
@@ -87,6 +58,21 @@ class Binder < Thor
|
|
87
58
|
#####################################
|
88
59
|
# create
|
89
60
|
desc 'create <id_text> <type_json>', 'Create a new Binder'
|
61
|
+
method_option :user, {
|
62
|
+
type: :string,
|
63
|
+
desc: 'User name.',
|
64
|
+
default: Etc.getpwuid(Process.euid).name,
|
65
|
+
}
|
66
|
+
method_option :body, {
|
67
|
+
type: :string,
|
68
|
+
desc: 'Body of an entry.',
|
69
|
+
default: '!! No body provided using CLI tool !!',
|
70
|
+
}
|
71
|
+
method_option :title, {
|
72
|
+
type: :string,
|
73
|
+
desc: 'Title of an entry.',
|
74
|
+
default: '!! No title provided using CLI tool !!',
|
75
|
+
}
|
90
76
|
def create(id_text, init_json)
|
91
77
|
|
92
78
|
if !options[:fs_path]
|
@@ -152,7 +138,238 @@ class Binder < Thor
|
|
152
138
|
:BindAddress => options[:addr],
|
153
139
|
})
|
154
140
|
|
155
|
-
end # def
|
141
|
+
end # def web_demo()
|
142
|
+
|
143
|
+
|
144
|
+
#####################################
|
145
|
+
# Backup to AWS S3
|
146
|
+
desc 'backup_s3', 'Backup to AWS S3'
|
147
|
+
method_option :key, {
|
148
|
+
type: :string,
|
149
|
+
desc: 'Path of AWS credentials',
|
150
|
+
required: true,
|
151
|
+
}
|
152
|
+
method_option :bucket, {
|
153
|
+
type: :string,
|
154
|
+
desc: 'S3 bucket name',
|
155
|
+
required: true,
|
156
|
+
}
|
157
|
+
method_option :level, {
|
158
|
+
type: :string,
|
159
|
+
desc: 'Debug level, "debug", "info", "warn", "error"',
|
160
|
+
default: 'error',
|
161
|
+
}
|
162
|
+
method_option :last, {
|
163
|
+
desc: 'Last backup state file name',
|
164
|
+
type: :string,
|
165
|
+
}
|
166
|
+
method_option :save, {
|
167
|
+
desc: 'Save backup state to file name',
|
168
|
+
type: :string,
|
169
|
+
}
|
170
|
+
def backup_s3()
|
171
|
+
|
172
|
+
return if !(aws = _get_aws)
|
173
|
+
return if !(bnd = _open_binder)
|
174
|
+
log = _get_log
|
175
|
+
last = _get_last
|
176
|
+
s3 = ::Aws::S3::Client.new(aws)
|
177
|
+
sto = ::Sgfa::StoreS3.new
|
178
|
+
sto.open(s3, options[:bucket])
|
179
|
+
last = bnd.backup_push(sto, prev: last, log: log)
|
180
|
+
bnd.close
|
181
|
+
sto.close
|
182
|
+
_put_last(last)
|
183
|
+
|
184
|
+
end # def backup_s3()
|
185
|
+
|
186
|
+
|
187
|
+
#####################################
|
188
|
+
# Restore from AWS S3
|
189
|
+
desc 'restore_s3 <id_text>', 'Restore from AWS S3'
|
190
|
+
method_option :key, {
|
191
|
+
type: :string,
|
192
|
+
desc: 'Path of AWS credentials',
|
193
|
+
required: true,
|
194
|
+
}
|
195
|
+
method_option :bucket, {
|
196
|
+
type: :string,
|
197
|
+
desc: 'S3 bucket name',
|
198
|
+
required: true,
|
199
|
+
}
|
200
|
+
method_option :level, {
|
201
|
+
type: :string,
|
202
|
+
desc: 'Debug level, "debug", "info", "warn", "error"',
|
203
|
+
default: 'error',
|
204
|
+
}
|
205
|
+
def restore_s3(id_text)
|
206
|
+
if !options[:fs_path]
|
207
|
+
puts 'Binder type and location required.'
|
208
|
+
return
|
209
|
+
end
|
210
|
+
return if !(aws = _get_aws)
|
211
|
+
log = _get_log
|
212
|
+
s3 = ::Aws::S3::Client.new(aws)
|
213
|
+
sto = ::Sgfa::StoreS3.new
|
214
|
+
sto.open(s3, options[:bucket])
|
215
|
+
bnd = ::Sgfa::BinderFs.new
|
216
|
+
bnd.create_raw(options[:fs_path], id_text)
|
217
|
+
bnd.open(options[:fs_path])
|
218
|
+
bnd.backup_pull(sto, log: log)
|
219
|
+
bnd.close
|
220
|
+
sto.close
|
221
|
+
end # def restore_s3()
|
222
|
+
|
223
|
+
|
224
|
+
#####################################
|
225
|
+
# backup to filesystem
|
226
|
+
desc 'backup_fs <dir>', 'Backup to file system'
|
227
|
+
method_option :last, {
|
228
|
+
desc: 'Last backup state file name',
|
229
|
+
type: :string,
|
230
|
+
}
|
231
|
+
method_option :save, {
|
232
|
+
desc: 'Save backup state to file name',
|
233
|
+
type: :string,
|
234
|
+
}
|
235
|
+
method_option :level, {
|
236
|
+
type: :string,
|
237
|
+
desc: 'Debug level, "debug", "info", "warn", "error"',
|
238
|
+
default: 'error',
|
239
|
+
}
|
240
|
+
def backup_fs(dest)
|
241
|
+
return if !(bnd = _open_binder)
|
242
|
+
log = _get_log
|
243
|
+
last = _get_last
|
244
|
+
sto = ::Sgfa::StoreFs.new
|
245
|
+
sto.open(dest)
|
246
|
+
last = bnd.backup_push(sto, prev: last, log: log)
|
247
|
+
bnd.close
|
248
|
+
sto.close
|
249
|
+
_put_last(last)
|
250
|
+
end # def backup_fs()
|
251
|
+
|
252
|
+
|
253
|
+
#####################################
|
254
|
+
# Restore from filesystem
|
255
|
+
desc 'restore_fs <id_text> <backup_store>', 'Restore from file system'
|
256
|
+
method_option :level, {
|
257
|
+
type: :string,
|
258
|
+
desc: 'Debug level, "debug", "info", "warn", "error"',
|
259
|
+
default: 'error',
|
260
|
+
}
|
261
|
+
def restore_fs(id_text, bak)
|
262
|
+
if !options[:fs_path]
|
263
|
+
puts 'Binder type and location required.'
|
264
|
+
return
|
265
|
+
end
|
266
|
+
sto = ::Sgfa::StoreFs.new
|
267
|
+
sto.open(bak)
|
268
|
+
log = _get_log
|
269
|
+
bnd = ::Sgfa::BinderFs.new
|
270
|
+
bnd.create_raw(options[:fs_path], id_text)
|
271
|
+
bnd.open(options[:fs_path])
|
272
|
+
bnd.backup_pull(sto, log: log)
|
273
|
+
bnd.close
|
274
|
+
sto.close
|
275
|
+
end # def restore_fs()
|
276
|
+
|
277
|
+
|
278
|
+
no_commands do
|
279
|
+
|
280
|
+
|
281
|
+
# Open the binder
|
282
|
+
def _open_binder()
|
283
|
+
# open binder
|
284
|
+
if !options[:fs_path]
|
285
|
+
puts 'Binder type and location required.'
|
286
|
+
return false
|
287
|
+
end
|
288
|
+
bnd = ::Sgfa::BinderFs.new
|
289
|
+
begin
|
290
|
+
bnd.open(options[:fs_path])
|
291
|
+
rescue ::Sgfa::Error::Limits, ::Sgfa::Error::NonExistent => exp
|
292
|
+
puts exp.message
|
293
|
+
return false
|
294
|
+
end
|
295
|
+
return bnd
|
296
|
+
end # def _open_binder()
|
297
|
+
|
298
|
+
|
299
|
+
# get the log
|
300
|
+
def _get_log
|
301
|
+
log = Logger.new(STDOUT)
|
302
|
+
case options[:level]
|
303
|
+
when 'debug'
|
304
|
+
log.level = Logger::DEBUG
|
305
|
+
when 'info'
|
306
|
+
log.level = Logger::INFO
|
307
|
+
when 'warn'
|
308
|
+
log.level = Logger::WARN
|
309
|
+
else
|
310
|
+
log.level = Logger::ERROR
|
311
|
+
end
|
312
|
+
return log
|
313
|
+
end # def _get_log
|
314
|
+
|
315
|
+
|
316
|
+
# Get AWS creds
|
317
|
+
def _get_aws
|
318
|
+
json = File.read(options[:key])
|
319
|
+
creds = JSON.parse(json)
|
320
|
+
opts = {
|
321
|
+
region: creds['aws_region'],
|
322
|
+
access_key_id: creds['aws_id'],
|
323
|
+
secret_access_key: creds['aws_key'],
|
324
|
+
}
|
325
|
+
return opts
|
326
|
+
|
327
|
+
rescue Errno::ENOENT
|
328
|
+
puts 'AWS keys file not found'
|
329
|
+
return false
|
330
|
+
|
331
|
+
rescue Errno::EACCESS
|
332
|
+
puts 'AWS key file permission denied'
|
333
|
+
return false
|
334
|
+
|
335
|
+
rescue JSON::JSONError
|
336
|
+
puts 'AWS key file JSON parse error'
|
337
|
+
return false
|
338
|
+
end # def get_aws
|
339
|
+
|
340
|
+
|
341
|
+
# Get last option
|
342
|
+
def _get_last()
|
343
|
+
return {} if !options[:last]
|
344
|
+
json = File.read(options[:last])
|
345
|
+
last = JSON.parse(json)
|
346
|
+
return last
|
347
|
+
rescue Errno::ENOENT
|
348
|
+
puts "Last backup state file not found"
|
349
|
+
exit
|
350
|
+
rescue Errno::EACCESS
|
351
|
+
puts "Access denied to last backup state file"
|
352
|
+
exit
|
353
|
+
rescue JSON::JSONError
|
354
|
+
puts "Last backup state file parse failed"
|
355
|
+
exit
|
356
|
+
end # def _get_last()
|
357
|
+
private :_get_last
|
358
|
+
|
359
|
+
|
360
|
+
# Save last
|
361
|
+
def _put_last(last)
|
362
|
+
return if !options[:save]
|
363
|
+
json = JSON.pretty_generate(last) + "\n"
|
364
|
+
File.open(options[:save], 'w', :encoding => 'utf-8'){|fi| fi.write json}
|
365
|
+
rescue Errno::EACCESS
|
366
|
+
puts "Access denied to backup state file"
|
367
|
+
exit
|
368
|
+
end # def _put_last()
|
369
|
+
private :_put_last
|
370
|
+
|
371
|
+
end
|
372
|
+
|
156
373
|
|
157
374
|
end # class Binder
|
158
375
|
|
data/lib/sgfa/cli/jacket.rb
CHANGED
@@ -9,9 +9,13 @@
|
|
9
9
|
# This program is distributed WITHOUT ANY WARRANTY; without even the
|
10
10
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
11
11
|
|
12
|
+
require 'csv'
|
13
|
+
|
14
|
+
require 'aws-sdk'
|
12
15
|
require 'thor'
|
13
16
|
|
14
17
|
require_relative '../jacket_fs'
|
18
|
+
require_relative '../store_s3'
|
15
19
|
|
16
20
|
module Sgfa
|
17
21
|
module Cli
|
@@ -270,6 +274,208 @@ class Jacket < Thor
|
|
270
274
|
end # def check()
|
271
275
|
|
272
276
|
|
277
|
+
#####################################
|
278
|
+
# Get stats
|
279
|
+
desc 'stats', 'Get stats for each entry in csv format'
|
280
|
+
method_option :header, {
|
281
|
+
type: :boolean,
|
282
|
+
desc: 'Include a header row',
|
283
|
+
default: true
|
284
|
+
}
|
285
|
+
method_option :title, {
|
286
|
+
type: :boolean,
|
287
|
+
desc: 'Include the entry title',
|
288
|
+
default: false
|
289
|
+
}
|
290
|
+
method_option :time, {
|
291
|
+
type: :boolean,
|
292
|
+
desc: 'Include the entry date and time',
|
293
|
+
default: false
|
294
|
+
}
|
295
|
+
method_option :output, {
|
296
|
+
type: :string,
|
297
|
+
desc: 'File name for CSV, if not provided, STDOUT is used'
|
298
|
+
}
|
299
|
+
def stats()
|
300
|
+
# open jacket
|
301
|
+
jck = _open_jacket
|
302
|
+
return if !jck
|
303
|
+
|
304
|
+
do_time = options[:time]
|
305
|
+
do_title = options[:title]
|
306
|
+
|
307
|
+
# CSV setup
|
308
|
+
if options[:output]
|
309
|
+
csv = ::CSV.open(options[:output], 'w', :encoding => 'utf-8')
|
310
|
+
else
|
311
|
+
csv = ::CSV.new(STDOUT, :encoding => 'utf-8')
|
312
|
+
end
|
313
|
+
|
314
|
+
# do the header row
|
315
|
+
if options[:header]
|
316
|
+
ha = [
|
317
|
+
'Stat name',
|
318
|
+
'Value',
|
319
|
+
'Account',
|
320
|
+
'Entry',
|
321
|
+
'Revision'
|
322
|
+
]
|
323
|
+
ha.push 'Date Time' if do_time
|
324
|
+
ha.push 'Title' if do_title
|
325
|
+
csv << ha
|
326
|
+
end
|
327
|
+
|
328
|
+
# read each entry and provide stats
|
329
|
+
hst = jck.read_history
|
330
|
+
hst.entry_max.times do |ix|
|
331
|
+
enum = ix + 1
|
332
|
+
ent = jck.read_entry(enum)
|
333
|
+
stats = ent.stats
|
334
|
+
next if stats.empty?
|
335
|
+
|
336
|
+
rnum = ent.revision
|
337
|
+
time = ent.time_str if do_time
|
338
|
+
title = ent.title if do_title
|
339
|
+
|
340
|
+
stats.each do |stat|
|
341
|
+
typ, val, accts = stat
|
342
|
+
if accts.empty?
|
343
|
+
ln = [typ, val, nil, enum, rnum]
|
344
|
+
ln.push time if do_time
|
345
|
+
ln.push title if do_title
|
346
|
+
csv << ln
|
347
|
+
next
|
348
|
+
end
|
349
|
+
accts.each do |acct|
|
350
|
+
ln = [typ, val, acct, enum, rnum]
|
351
|
+
ln.push time if do_time
|
352
|
+
ln.push title if do_title
|
353
|
+
csv << ln
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
jck.close
|
359
|
+
csv.close
|
360
|
+
|
361
|
+
end # def stats()
|
362
|
+
|
363
|
+
|
364
|
+
#####################################
|
365
|
+
# Backup to aws
|
366
|
+
desc 'backup_s3', 'Backup to AWS S3'
|
367
|
+
method_option :key, {
|
368
|
+
type: :string,
|
369
|
+
desc: 'Path of AWS credentials',
|
370
|
+
required: true,
|
371
|
+
}
|
372
|
+
method_option :bucket, {
|
373
|
+
type: :string,
|
374
|
+
desc: 'S3 bucket name',
|
375
|
+
required: true,
|
376
|
+
}
|
377
|
+
method_option :level, {
|
378
|
+
type: :string,
|
379
|
+
desc: 'Debug level, "debug", "info", "warn", "error"',
|
380
|
+
default: 'error',
|
381
|
+
}
|
382
|
+
def backup_s3()
|
383
|
+
# open jacket
|
384
|
+
jck = _open_jacket
|
385
|
+
return if !jck
|
386
|
+
|
387
|
+
# read in JSON config
|
388
|
+
json = File.read(options[:key])
|
389
|
+
creds = JSON.parse(json)
|
390
|
+
opts = {
|
391
|
+
region: creds['aws_region'],
|
392
|
+
access_key_id: creds['aws_id'],
|
393
|
+
secret_access_key: creds['aws_key'],
|
394
|
+
}
|
395
|
+
log = Logger.new(STDOUT)
|
396
|
+
case options[:level]
|
397
|
+
when 'debug'
|
398
|
+
log.level = Logger::DEBUG
|
399
|
+
when 'info'
|
400
|
+
log.level = Logger::INFO
|
401
|
+
when 'warn'
|
402
|
+
log.level = Logger::WARN
|
403
|
+
else
|
404
|
+
log.level = Logger::ERROR
|
405
|
+
end
|
406
|
+
|
407
|
+
puts 'id: %s' % creds['aws_id']
|
408
|
+
puts 'secret: %s' % creds['aws_key']
|
409
|
+
puts 'bucket: %s' % options[:bucket]
|
410
|
+
|
411
|
+
# client
|
412
|
+
s3 = ::Aws::S3::Client.new(opts)
|
413
|
+
sto = ::Sgfa::StoreS3.new
|
414
|
+
sto.open(s3, options[:bucket])
|
415
|
+
|
416
|
+
# backup
|
417
|
+
jck.backup(sto, log: log)
|
418
|
+
jck.close
|
419
|
+
|
420
|
+
end # def backup_s3()
|
421
|
+
|
422
|
+
|
423
|
+
#####################################
|
424
|
+
# Restore from AWS S3
|
425
|
+
desc 'restore_s3', 'Backup to AWS S3'
|
426
|
+
method_option :key, {
|
427
|
+
type: :string,
|
428
|
+
desc: 'Path of AWS credentials',
|
429
|
+
required: true,
|
430
|
+
}
|
431
|
+
method_option :bucket, {
|
432
|
+
type: :string,
|
433
|
+
desc: 'S3 bucket name',
|
434
|
+
required: true,
|
435
|
+
}
|
436
|
+
method_option :level, {
|
437
|
+
type: :string,
|
438
|
+
desc: 'Debug level, "debug", "info", "warn", "error"',
|
439
|
+
default: 'error',
|
440
|
+
}
|
441
|
+
def restore_s3()
|
442
|
+
# open jacket
|
443
|
+
jck = _open_jacket
|
444
|
+
return if !jck
|
445
|
+
|
446
|
+
# read in JSON config
|
447
|
+
json = File.read(options[:key])
|
448
|
+
creds = JSON.parse(json)
|
449
|
+
opts = {
|
450
|
+
region: creds['aws_region'],
|
451
|
+
access_key_id: creds['aws_id'],
|
452
|
+
secret_access_key: creds['aws_key'],
|
453
|
+
}
|
454
|
+
log = Logger.new(STDOUT)
|
455
|
+
case options[:level]
|
456
|
+
when 'debug'
|
457
|
+
log.level = Logger::DEBUG
|
458
|
+
when 'info'
|
459
|
+
log.level = Logger::INFO
|
460
|
+
when 'warn'
|
461
|
+
log.level = Logger::WARN
|
462
|
+
else
|
463
|
+
log.level = Logger::ERROR
|
464
|
+
end
|
465
|
+
|
466
|
+
puts 'id: %s' % creds['aws_id']
|
467
|
+
puts 'secret: %s' % creds['aws_key']
|
468
|
+
puts 'bucket: %s' % options[:bucket]
|
469
|
+
|
470
|
+
# client
|
471
|
+
s3 = ::Aws::S3::Client.new(opts)
|
472
|
+
sto = ::Sgfa::StoreS3.new
|
473
|
+
sto.open(s3, options[:bucket])
|
474
|
+
|
475
|
+
# backup
|
476
|
+
jck.restore(sto, log: log)
|
477
|
+
jck.close
|
478
|
+
end # def restore_s3
|
273
479
|
|
274
480
|
private
|
275
481
|
|