Skip to content

Commit

Permalink
whirlwind of updates; rapidly evolving and debugging
Browse files Browse the repository at this point in the history
  • Loading branch information
ndenny committed Oct 3, 2024
1 parent 243d811 commit 5328fed
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 68 deletions.
22 changes: 15 additions & 7 deletions bin/bastion.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class App:
CONF_SEARCH_ORDER = [
pathlib.Path('/etc/bastion'),
APP_PATH / 'etc',
pathlib.Path('/CONF'), #-- mostly used for dockerized instances.
pathlib.Path('~/.bastion').expanduser()
]

Expand Down Expand Up @@ -154,6 +155,12 @@ def vault(self, name, site = None):
else:
return None

@property
def sites(self):
#-- I am an iterator over Site instances that I know.
for nick in self.conf['sites'].keys():
yield self.site(nick)

def run(self):
#-- scan the command line for options of the form "-{opt}:{value}"
#-- options are removed from the command sequence
Expand Down Expand Up @@ -355,6 +362,7 @@ def do_bank_zone(self, comargs, comdex, opts):
* zone can be given as two arguments (site, zone)
* zone can be given as a single argument in ARK format
"""
raise NotImplementedError

def do_bank_asset(self, comargs, comdex, opts):
"""
Expand Down Expand Up @@ -438,8 +446,8 @@ def do_update_asset(self, comargs, comdex, opts):
vault = self.vault(asset.policy.vault)
banked = vault.manifest(ark)
if banked.anchors:
basis = banked.anchors[-1]
blonde = vault.push(asset, basis)
anchor = banked.anchors[-1]
blonde = vault.push(asset, anchor)
else:
blonde = vault.push(asset)

Expand Down Expand Up @@ -576,7 +584,6 @@ def do_refresh_keytab(self, comargs, comdex, opts):
extras['log.scope'] = "vault"
return FAILED("vault {} is not declared".format(subject), context = extras)


if not callable( getattr(vault, 'refresh_keytab', None) ):
#-- FAIL immediately since this vault doesn't declare
#-- a method to refresh keytabs ... probably because the
Expand All @@ -596,10 +603,11 @@ def do_refresh_keytab(self, comargs, comdex, opts):
if sys.argv[1:] != ['shell']:
app.run( )
else:
rusina = app.site('rusina')
soundscapes = rusina.zone('soundscapes')
asset = soundscapes['HackathonData']
vault = app.vault(asset.policy.vault)
if os.environ.get('BASTION_SITE', None) == 'rusina':
rusina = app.site('rusina')
soundscapes = rusina.zone('soundscapes')
asset = soundscapes['HackathonData']
vault = app.vault(asset.policy.vault)

#-- does a full, level 0 backup
#bastion backup site {site}
Expand Down
File renamed without changes.
13 changes: 12 additions & 1 deletion lib/Bastion/Common.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def __init__(self, **kwargs):
def RDN(x):
"""
Answers the relatively distinguishing name (RDN) of object, x.
If x is a string, it is assumed that x is the name.
If x is a string, it is assumed that x is already a name.
If x is an object with an RDN attribute, answers x.RDN
"""
if isinstance(x, str):
Expand Down Expand Up @@ -69,6 +69,10 @@ def isDuration(self):
def isString(self):
return isinstance(self.subject, str)

@property
def hasRDN(self):
return hasattr(self.subject, 'RDN')



class UnknownType(object):
Expand Down Expand Up @@ -297,7 +301,11 @@ def Boggle(*args):
#-- This is an error.
raise ValueError


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.
"""
if isinstance(x, int):
return x
elif isinstance(x, str):
Expand All @@ -308,4 +316,7 @@ def asLogLevel(x):


def asPath(x):
"""
Accessor/wrapper for answering x already cast as a pathlib.Path instance
"""
return pathlib.Path(x)
92 changes: 52 additions & 40 deletions lib/Bastion/Curator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class BLONDE(canTextify):
BLOb Name and Description Encoding
I am a structured name describing a point in time for a single ARK.
"""
def __init__(self, asset, detail, basis = None, when = None):
def __init__(self, asset, detail, genus = None, when = None):
if isinstance(asset, ARK):
self.badge = asset.badge
elif isinstance(asset, isAsset):
Expand All @@ -29,60 +29,64 @@ def __init__(self, asset, detail, basis = None, when = None):

self.when = Quantim(when)
self.detail = detail
self.basis = basis
self.genus = genus
self.RDN = str(self)

#-- Automatically create a basis reference for full backups.
if (self.detail == 'F') and (self.basis is None):
self.basis = Boggle(3)
#-- Automatically create a new genus as lineage reference for full backups.
if (self.detail == 'F') and (self.genus is None):
self.genus = Boggle(3)

@property
def anchor(self):
"""
I am an alternate attribute name for the anchor reference ID.
"""
return self.basis
def revise(self, whence = None):
if self.isRevision:
raise Exception("Can only create revisions of anchor backups")

if whence is not None:
return BLONDE.forDiffBackup(self)
else:
return BLONDE.forDiffBackup(self, when = whence)

def age(self, whence = None):
whence = whence if (whence is not None) else datetime.datetime.now()
return (whence - self.when.earliest)

@property
def isAnchor(self):
return (self.detail == 'F')

@property
def isDifferential(self):
def isRevision(self):
return (self.detail == 'D')

@staticmethod
def encode(badge, when, detail, basis):
return "{}{}{}{}".format(badge, str(Quantim(when)), detail, basis)
def encode(badge, when, detail, genus):
return "{}{}{}{}".format(badge, str(Quantim(when)), detail, genus)

def __str__(self):
return self.encode(self.badge, self.when, self.detail, self.basis)
return self.encode(self.badge, self.when, self.detail, self.genus)

@classmethod
def decode(cls, blonde):
ds = Thing(**{
'badge': blonde[0:8],
'when': Quantim(blonde[8:16]),
'detail': blonde[16],
'basis': blonde[17:20],
'genus': blonde[17:20],
})
return cls(ds.badge, ds.detail, ds.basis, ds.when)
return cls(ds.badge, ds.detail, ds.genus, ds.when)

@classmethod
def forFullBackup(cls, ark, **kwargs):
when = kwargs.get('when', datetime.datetime.utcnow())
basis = kwargs.get('basis', Boggle(3))
return cls(ark, 'F', basis, when)
when = kwargs.get('when', datetime.datetime.now())
genus = kwargs.get('genus', Boggle(3))
return cls(ark, 'F', genus, when)

@classmethod
def forDiffBackup(cls, anchor, **kwargs):
"""
Given a full backup reference (BLONDE), I generate a new BLONDE for a differential backup.
"""
when = kwargs.get('when', datetime.datetime.utcnow())
return cls(anchor.badge, 'D', anchor.basis, when)


when = kwargs.get('when', datetime.datetime.now())
return cls(anchor.badge, 'D', anchor.genus, when)


class Thread(tuple):
Expand All @@ -96,8 +100,12 @@ def anchor(self):
return self[0]

@property
def basis(self):
return self.anchor.basis
def revisions(self):
return tuple(self[1:])

@property
def genus(self):
return self.anchor.genus

@property
def earliest(self):
Expand All @@ -109,11 +117,11 @@ def latest(self):

@property
def begins(self):
return self.anchor.when.datetime()
return self.earliest.when.datetime()

@property
def ends(self):
return self.head.when.datetime()
return self.latest.when.datetime()

@property
def drift(self):
Expand All @@ -125,10 +133,10 @@ def drift(self):
#-- Each snap is a 2-tuple of (anchor, differential)
#-- An "anchor" is a full backup of the dataset.
#-- Each blob in the archive is recorded as ...
#-- {slug}{quantim}[A|D]{anchor}
#-- {slug}{quantim}[F|D]{anchor}
#-- Where {slug} is the Slug40 encoding (a 8 character, base32 word) of the dataset name (relative to the Rz),
#-- {quantim} is the 8 character encoding of timestamp using the Quantim method
#-- A if the blob is an anchor (full backup) and D if the blob is a differential.
#-- F if the blob is an anchor (full backup) and D if the blob is a differential.
#-- {anchor} - is a 3 character random string that cannot conflict with any other anchors currently in the archive.
class Manifest(canTextify):
"""
Expand All @@ -144,7 +152,7 @@ def __init__(self, asset, items):

blondes = list(items)
self._items = tuple(sorted([blonde for blonde in blondes if (blonde.badge == self.badge)], key = lambda b: b.RDN))
self._anchors = dict([(item.basis, item) for item in self._items if item.isAnchor])
self._anchors = dict([(item.genus, item) for item in self._items if item.isAnchor])

def __iter__(self):
return iter(self._items)
Expand Down Expand Up @@ -179,10 +187,10 @@ def thread(self, ankle):
"""
if isinstance(ankle, str):
anchor = ankle
elif isinstance(ankle, snap):
anchor = ankle.anchor.basis
elif isinstance(ankle, Snap):
anchor = ankle.anchor.genus
elif isinstance(ankle, BLONDE):
anchor = ankle.basis
anchor = ankle.genus
return Thread([snap for snap in self.snaps if snap.basis == anchor])

def anchor(self, item):
Expand Down Expand Up @@ -227,21 +235,21 @@ def __init__(self, head, anchor):
self.anchor = anchor

@property
def basis(self):
return self.anchor.basis
def genus(self):
return self.anchor.genus

def age(self, whence = None):
"""
I answer a datetime timedelta (elapsed time) between whence and the encoded datetime of this snap.
If no "whence" is explicitly given, I assume the current UTC time.
If no "whence" is explicitly given, I assume the current local time.
"""
whence = whence if whence is not None else datetime.datetime.utcnow()
return (whence - self.head.when.datetime())
whence = whence if whence is not None else datetime.datetime.now()
return (whence - self.head.when.datetime)

@property
def drift(self):
#-- elapsed time between head and its anchor.
return (self.head.when.datetime() - self.anchor.when.datetime())
return (self.head.when.datetime - self.anchor.when.datetime)

@property
def isAnchor(self):
Expand All @@ -250,3 +258,7 @@ def isAnchor(self):
@property
def isDifferential(self):
return self.head.isDifferential

@property
def isRevision(self):
return self.head.isRevision
48 changes: 39 additions & 9 deletions lib/Bastion/Model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
import pathlib

from .Common import RDN, CURIE, Slug40
from .Common import RDN, CURIE, Slug40, entity


class ARK(tuple):
Expand Down Expand Up @@ -76,8 +76,14 @@ def badge(self):
return Slug40(str(self.CURIE))


class isVault:
"""
abstract class for all vaults.
"""
pass

class Vault:

class Vault(isVault):
"""
I am the base class for all storage vaults.
"""
Expand Down Expand Up @@ -190,13 +196,6 @@ def RDN(self):
self._RDN = self.badge
return self._RDN

# @property
# def path(self):
# """
# I answer the local (host) file system path to this asset.
# """
# return self.zone.root / pathlib.Path(self.name)

@property
def CURIE(self):
"""
Expand Down Expand Up @@ -225,6 +224,9 @@ def halo(self):

@property
def zolo(self):
"""
zolo (zone location) is the path of this object relative to it's zone.
"""
return self.ARK.zolo


Expand All @@ -248,3 +250,31 @@ def RDN(self):
def __div__(self, name):
return self.ASSET_CLS(self, name)


class isSite:
"""
asbtract site class; forward declaration of Site class(es).
"""
pass


#-- Monkey patch the "entity" class for syntax sugar.
def entity_isSite(self):
return isinstance(self.subject, isSite)

def entity_isAsset(self):
return isinstance(self.subject, isAsset)

def entity_isZone(self):
return isinstance(self.subject, isZone)

def entity_isARK(self):
return isinstance(self.subject, isARK)

def entity_isVault(self):
return isinstance(self.subject, isVault)

entity.isAsset = property(entity_isAsset)
entity.isZone = property(entity_isZone)
entity.isSite = property(entity_isSite)
entity.isVault = property(entity_isVault)
Loading

0 comments on commit 5328fed

Please sign in to comment.