Skip to content

Commit

Permalink
WORK IN PROGRESS
Browse files Browse the repository at this point in the history
  • Loading branch information
ndenny committed Dec 6, 2024
1 parent f48e6a5 commit fe7cd70
Show file tree
Hide file tree
Showing 9 changed files with 478 additions and 354 deletions.
13 changes: 6 additions & 7 deletions bin/bastion.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,13 @@ def run(self):
if not isinstance(answer, isReceipt):
raise ValueError("actions must respond with an instance of CARP.isReceipt")
except Exception as err:
tb = traceback.format_exception(err)
answer = request.crashed( ''.join(tb), tb )
answer = request.crashed(err)
#-- always log crashes!
answer.context['log.scope'] = '*'
answer['log.scope'] = '*'

#-- check for override on the logging scope.
if "log.scope" in opts:
answer.context['log.scope'] = opts['log.scope']
answer['log.scope'] = opts['log.scope']

#-- write the answer to the log, if there is a given log.scope
self.remember(answer)
Expand Down Expand Up @@ -345,8 +344,8 @@ def do_bank_zone(self, request):
receipt = vault.push(asset)
receipts.append(receipt)

all_succeeded = all(receipt.indicates_success for receipt in receipts])
all_failed = all(receipt.indicates_failure for receipt in receipts])
all_succeeded = all([receipt.indicates_success for receipt in receipts])
all_failed = all([receipt.indicates_failure for receipt in receipts])

pushed = [receipt.body['blonde'] for receipt in receipts if receipt.indicates_success]

Expand All @@ -367,8 +366,8 @@ def do_bank_asset(self, request):
asset = site.asset(ark)
vault = self.vault(asset.policy.vault)

request['log.scope'] = "site/{}".format(ark.site)
receipt = vault.push(asset, client = self.hostname)
request.context['log.scope'] = 'log.scope': "site/{}".format(ark.site)

if receipt.indicates_success:
blonde = receipt.body['blonde']
Expand Down
117 changes: 67 additions & 50 deletions lib/Bastion/CARP.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
Common Action-Result/Report Protocol
"""
import operator
import uuid
import traceback

import yaml

from Bastion.Common import toJDN

class ReplyStatus(tuple):
Expand Down Expand Up @@ -70,7 +73,7 @@ def __repr__(self):
return "{}: {}".format(self.code, self.gloss)

@property
def title(self):
def lede(self):
return self.DEFAULT_CATEGORY_GLOSS[self.category]

@property
Expand Down Expand Up @@ -115,14 +118,24 @@ class isRequest:
def __init__(self, action, *args, **kwargs):
self.ID = kwargs.get('ID', str(uuid.uuid4()))
self.action = action
self.when = kwargs.get('when', datetime.datetime.now())
self.when = kwargs.get('opened', datetime.datetime.now())
self.args = dict(enumerate(args))
self.context = { }

excluded = {'ID', 'opened', 'context'}
for k, v in kwargs.items():
if k not in excluded:
self.context[k] = v
if 'context' in kwargs:
for k, v in kwargs['context'].items():
self.context[k] = v

def __getitem__(self, k):
return self.context[k]

def __setitem__(self, k, v):
self.context[k] = v

def toJDN(self, **kwargs):
jdn = {
'ID': self.ID,
Expand All @@ -141,12 +154,12 @@ def toJDN(self, **kwargs):
return jdn


class isReceipt:
def __init__(self, request, status, obj, **kwargs):
class isResult:
def __init__(self, request, status, record, **kwargs):
#-- POSITIONAL ARGS...
#-- request is a REQUIRED 2-tuple of (action, [arg1, ...])
#-- status is a REQUIRED instance of ReplyStatus
#-- obj is a REQUIRED JSON serializable object, the outcome of the action implied by the request.
#-- record is a REQUIRED JSON serializable object, the outcome of the action implied by the request.
#-- KEYWORD ARGS...
#-- context: an OPTIONAL dict of extra key-value associations.
#-- ID: an OPTIONAL given ID string, defaults to a randomly generated UUID
Expand All @@ -158,8 +171,8 @@ def __init__(self, request, status, obj, **kwargs):
self.ID = kwargs.get('ID', str(uuid.uuid4()))
self.request = request
self.status = status
self.lede = kwargs.get('lede', status.category)
self.record = obj
self.lede = kwargs.get('lede', status.lede)
self.record = record
self.context = { }
self.when = kwargs.get('when', datetime.datetime.now())

Expand All @@ -180,7 +193,7 @@ def toJDN(self, **kwargs):
'answered': self.when.isoformat( ),
'elapsed': self.elapsed.total_seconds(),
'context': { },
'record': toJDN(self.record)
'record': toJDN(self.record) if self.record else None
}
#-- Populate the result's context (if any)
for k, v in self.context.items( ):
Expand All @@ -190,6 +203,7 @@ def toJDN(self, **kwargs):
jdn = { }
jdn['request'] = toJDN(self.request)
jdn['request']['result'] = result

return jdn

@property
Expand All @@ -205,37 +219,51 @@ def inconclusive(self):
return self.status.is_inconclusive


class Receipt(isReceipt):
pass


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

def changed(self, *args):
if 'body' in args:
self._body = None
@property
def body(self):
if getattr(self, '_body', None) is None:
self._body = self.toMD()
return self._body

def toMD(self):
"""
I answer a markdown representation of self.
Base class is naive and just returns an empty string.
Override in subclasses for detailed output.
"""
return ""

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

@property
def body(self):
if getattr(self, '_body', None) is None:
#-- encode my object, if I have one, to a YAML string.
if self.record:
self._body = toJDN(self.record)
else:
self._body = ""
return self._body

def __str__(self):
return yaml.dump(self.toJDN(), default_flow_style = False, sort_keys = True)
return yaml.dump(self.toJDN(), default_flow_style = False, indent = 3, sort_keys = True)

@classmethod
def Success(cls, request, record = None, *args, **kwargs):
return cls(request. ResultStatus.Ok, record, *args, **kwargs)

@classmethod
def Failed(cls, request, record = None, *args, **kwargs):
return cls(request, ResultStatus.Failed, record, *args, **kwargs)

@classmethod
def Crashed(cls, request, record = None, *args, **kwargs):
if isinstance(record, Exception):
return ReportException(request, ResultStatus.Crashed, record, *args, **kwargs)
else:
return cls(request, ResultStatus.Crashed, record, *args, **kwargs)

@classmethod
def Inconclusive(cls, request, record = None, *args, **kwargs):
return cls(request, ResultStatus.Inconclusive, record, *args, **kwargs)


class ReportException(Report):
Expand All @@ -253,32 +281,21 @@ def __init__(self, request, *args, **kwargs):
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)
tracer = ''.join( traceback.format_exception(errobj) )

def

def __str__(self):
return ''.join( traceback.format_exception(self.xobj) )
Report.__init__(self, request, status, errobj, body = tracer)



class Request(isRequest):
#-- Subclasses can set their own REPORT.
#-- This is used mostly in the convenience methods: .succeeded, .failed, etc.
REPORTS = { } #-- An optional mapping of ReplyStatus code to report class or function.

def succeeded(self, *args, **kwargs):
return Report(self, ResultStatus.Ok, *args, **kwargs)
def succeeded(self, record = None, *args, **kwargs):
return Report.Success(self, record, *args, **kwargs)

def failed(self, *args, **kwargs):
return Report(self, ResultStatus.Failed, *args, **kwargs)
def failed(self, record = None, *args, **kwargs):
return Report.Failed(self, record, *args, **kwargs)

def crashed(self, *args, **kwargs):
return Report(self, ResultStatus.Crashed, *args, **kwargs)
def crashed(self, record = None, *args, **kwargs):
return Report.Crashed(self, record, *args, **kwargs)

def inconclusive(self, *args, **kwargs)
return Report(self, ResultStatus.Inconclusive, *args, **kwargs)
def inconclusive(self, record = None, *args, **kwargs):
return Report.Inconclusive(self, record, *args, **kwargs)
21 changes: 21 additions & 0 deletions lib/Bastion/Common.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ class Thing:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
self._varnames = list(kwargs.keys())

def toJDN(self, **kwargs):
jdn = { }
for v in self._varnames:
jdn[v] = getattr(self, v)
return jdn


def RDN(x):
Expand Down Expand Up @@ -93,7 +100,18 @@ def __call__(self, subject, **kwargs):
def _toJDN_date(x, **kwargs):
#-- x is an instance of datetime.date
return x.isoformat()

def _toJDN_Exception(x, **kwargs):
#-- x is an instance of Exception
fqn = "{}.{}".format(type(x).__module__, type(x).__name__)
return {"exception": fqn, "message": str(x)}

def _toJDN_path(x, **kwargs):
return str(x)

toJDN.spells.append( (datetime.date, _toJDN_date) )
toJDN.spells.append( (Exception, _toJDN_Exception) )
toJDN.spells.append( (pathlib.PurePath, _toJDN_path) )


class entity:
Expand Down Expand Up @@ -203,6 +221,9 @@ def __str__(self):
def __repr__(self):
return str(self)

def toJDN(self, **kwargs):
return str(self)

@property
def ref(self):
"""
Expand Down
Loading

0 comments on commit fe7cd70

Please sign in to comment.