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.
@@ -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 !options[:fs_path]
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 web()
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
 
@@ -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