Skip to content

Commit

Permalink
Working on packer and improving human readable report output.
Browse files Browse the repository at this point in the history
  • Loading branch information
ndenny committed Nov 24, 2024
1 parent eb11ecb commit 2c63a62
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 54 deletions.
37 changes: 34 additions & 3 deletions lib/Bastion/CARP.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
Common Action-Result/Report Protocol
"""
import uuid
import traceback

from Bastion.Common import toJDN


class ReplyStatus(tuple):
DEFAULT_CATEGORY_GLOSS = {
'1': 'FYI',
Expand Down Expand Up @@ -166,6 +166,10 @@ def __init__(self, request, status, obj, **kwargs):
if context is not None:
self.context = context.copy()

@property
def elapsed(self):
return (self.when - self.request.when)

def toJDN(self, **kwargs):
#-- Build result stanza.
result = {
Expand All @@ -174,6 +178,7 @@ def toJDN(self, **kwargs):
'message': self.status.gloss,
'lede': self.lede,
'answered': self.when.isoformat( ),
'elapsed': self.elapsed.total_seconds(),
'context': { },
'record': toJDN(self.record)
}
Expand Down Expand Up @@ -205,7 +210,7 @@ class Receipt(isReceipt):


class Report(isReceipt):
def __init__(self, request, status, *args, **kwargs):
def __init__(self, request, status, obj = None, *args, **kwargs):
isReceipt.__init__(self, request, status, *args, **kwargs)
self._body = kwargs.get('report', None)

Expand All @@ -215,7 +220,7 @@ def changed(self, *args):

def toJDN(self, **kwargs):
jdn = isReceipt.toJDN(self, **kwargs)
jdn['request']['result']['report'] = str(self.report)
jdn['request']['result']['report'] = str(self)
return jdn

@property
Expand All @@ -225,6 +230,32 @@ def body(self):
return self._body


class ReportException(Report):
def __init__(self, request, *args, **kwargs):
#-- Can be called as...
#-- ReportException(request, errobj), or...
#-- ReportException(request, status, errobj)
if isinstance(args[0], ReplyStatus):
status = args[0]
errobj = args[1]
else:
status = ReplyStatus.failed
errobj = args[0]

assert status.indicates_failure, "ReportException must be created with a reply status indicating failure"
assert isinstance(errobj, Exception), "error object must be an instance of Exception"

Report.__init__(self, request, status, errobj)

self.xobj = x
self.xname = type(xobj).__name__
self.xmsg = str(xobj)

def __str__(self):
return ''.join( traceback.format_exception(self.xobj) )




class Request(isRequest):
#-- Subclasses can set their own REPORT.
Expand Down
19 changes: 19 additions & 0 deletions lib/Bastion/Common.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,25 @@ def Boggle(*args):
raise ValueError


def prefer(options, **kwargs):
"""
prefer(options) - chooses the first item in sequence (options) that is not None
Optional keyword args...
* choose - a function that returns True for options that should be in the selection.
* n - an integer, if larger than 1 answers a sequence of up to n items, if 1 (default) answers the first item (scalar) in the selection.
"""
choose = kwargs.get("choose", lambda x: (x is not None))
n = kwargs.get("n", 1)

choices = [option for option in options if choose(option)]

if n == 1:
return choices[0]

return choiecs[:n]



def asLogLevel(x):
"""
Accessor/wrapper for answering the ordinal log level (integer) that is assocationed with one of the common symbolic (string) log level names.
Expand Down
30 changes: 30 additions & 0 deletions lib/Bastion/Humane.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
Bastion.Humane
Utilities mostly for creating "humane" (i.e. human scale) messages.
"""
import datetime

from Bastion.Common import Alchemist

humanize = Alchemist('Humane', "humanize")

def _humanize_timedelta(elapsed, **kwargs):
dt = elapsed.total_seconds( )

if dt > (86400*2):
days = round(dt/86400.0, 1)
return "{:.1f} days".format(days)

if dt > 7200.0:
hrs = round(dt/3600.0, 1)
return "{:.1f} hours".format(hrs)

if dt > 600.0:
mins = round(dt/60.0, 1)
return "{:.1f} minutes".format(dt / mins)

return "{:.1f} seconds".format(dt)


humanize.spells.append( (datetime.timedelta, _humanize_timedelta) )
13 changes: 13 additions & 0 deletions lib/Bastion/Model.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ class isClerk:
"""
abstract class for metadata and file management specific to the capabilities of a given vault type.
"""
def __init__(self, vault):
assert isinstance(vault, isVault), "vault must be an instance of Bastion.Model.isVault"
self.vault = vault

@property
def ARKs(self):
"""
Expand Down Expand Up @@ -359,6 +363,11 @@ def manifest(self, ark):


class isPacker:
def __init__(self, vault):
assert isinstance(vault, isVault), "vault must be an instance of Bastion.Model.isVault"

self.vault = vault

def pack(self, asset, basis = None, **kwargs):
"""
Given a local asset, I package (.tar, .zip, etc) the asset into my scratch (spool) space.
Expand Down Expand Up @@ -386,6 +395,10 @@ class isMover:
"""
abstract class for file movement in to and out of a specific vault type.
"""
def __init__(self, vault):
assert isinstance(vault, isVault), "vault must be an instance of Bastion.Model.isVault"
self.vault = vault

def provision(self, *args):
"""
provision(ark) - ensures that the site, zone, and asset folders exist.
Expand Down
34 changes: 2 additions & 32 deletions lib/Bastion/Movers/SFTP.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,9 @@ def asPurePath(x):

class Mover(Bastion.Model.isMover):
def __init__(self, vault, **kwargs):
isClerk.__init__(self)
self.vault = vault
Bastion.Model.isMover.__init__(self, vault)
self.scurler = SCURLer(self.vault.sfURL, keyfile = self.vault.keypath)

@property
def mover(self):
if getattr(self, '_mover', None) is None:
self._mover = Mover(self, self.host, self.login, self.keypath)
return self._mover

@property
def clerk(self):
if getattr(self, '_clerk', None) is None:
self._clerk = Clerk(self, target, self.host, self.login, self.keypath)
return self._clerk

@property
def bank(self):
client = SCURLer(self.login, self.host, self.root, keyfile = self.key)
Expand All @@ -59,14 +46,7 @@ def provision(self, *args):
provision(ark) - ensures that the site, zone, and asset folders exist.
provision(site, zone, asset_name) - an alias for provision(ark)
"""
ark = None
if len(args) == 1:
ark = args[0]
elif len(args) == 3:
ark = ARK(args[0], args[1], args[2])
else:
raise ValueError

ark = ARK(*args)
repo = self.scurler / ark.site / ark.zone / ark.asset
return repo.mkdir(parents = True, exist_ok = True)

Expand Down Expand Up @@ -116,7 +96,6 @@ def pack(self, asset, basis = None, **kwargs):
#-- use the built-in python tar archiver, using "pax" (POSIX.1-2001) extensions.
pax((self.scratch / tag), asset.halo, **opts)


#-- Answer the receipt.
return receipt

Expand Down Expand Up @@ -165,12 +144,3 @@ def get(self, tag, halo, **kwargs):
#↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
#-- END Bastion.Model.Vault PROTOCOL |
#-------------------------------------
def _provision_ark(self, ark):
repo = self.bank / ark.site / ark.zone / ark.asset
return repo.mkdir(parents = True, exist_ok = True)

def _provision_site_zone_asset(self, site, zone, asset_name):
return self._provision_ark( ARK(site, zone, asset_name) )


Vault.register()
13 changes: 2 additions & 11 deletions lib/Bastion/Packers/CARP.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,7 @@
logger = logging.getLogger(__name__)


class PackReportSuccess(Report):
def __init__(self, request, status, *args, **kwargs):
Report.__init__(self, request, status, *args, **kwargs)


class PackRequest(Request):
REPORTS = {
RequestStatus.Ok.code: PackReportSuccess
}

class PackRequest(Bastion.CARP.Request):
def __init__(self, asset, basis = None, **kwargs):
#-- Which asset will be packed?
#-- If differential backup, what basis is used for determining changes?
Expand All @@ -29,7 +20,7 @@ def __init__(self, asset, basis = None, **kwargs):

if basis:
self.context['detail'] = 'D'
self.context['basis'] = str(kwargs['basis'])
self.context['basis'] = str(basis)
self.context['whence'] = kwargs['whence'].isoformat()
if 'genus' in kwargs:
self.context['genus'] = kwargs['genus']
Expand Down
Loading

0 comments on commit 2c63a62

Please sign in to comment.