sgfa 0.1.0 → 0.1.1
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 +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
|
|