From 6fc7e690adfab924e9cf6d0402931c7218a34373 Mon Sep 17 00:00:00 2001 From: Nathan Denny Date: Wed, 21 Feb 2024 13:21:57 -0500 Subject: [PATCH] created new Bastion library with modules; start on effective .xlsx conf file --- .gitignore | 2 +- docs/idifhub_conf.xlsx | Bin 0 -> 16972 bytes environment.yml | 8 +++ lab/strawman01.py | 23 +++++++ lib/Bastion/Common.py | 48 ++++++++++++++ lib/Bastion/Curator.py | 138 ++++++++++++++++++++++++++++++++++++++++ lib/Bastion/Site.py | 88 +++++++++++++++++++++++++ lib/Bastion/Vault.py | 33 ++++++++++ lib/Bastion/__init__.py | 0 9 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 docs/idifhub_conf.xlsx create mode 100644 environment.yml create mode 100644 lab/strawman01.py create mode 100644 lib/Bastion/Common.py create mode 100644 lib/Bastion/Curator.py create mode 100644 lib/Bastion/Site.py create mode 100644 lib/Bastion/Vault.py create mode 100644 lib/Bastion/__init__.py diff --git a/.gitignore b/.gitignore index b6e4761..ae8712a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ dist/ downloads/ eggs/ .eggs/ -lib/ +#lib/ lib64/ parts/ sdist/ diff --git a/docs/idifhub_conf.xlsx b/docs/idifhub_conf.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..f7c2748e1ac572ffda262bf197d7ec4cdb9a2f85 GIT binary patch literal 16972 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 UVBLU#&;fsb0N32e{l|a*2X1dM1^@s6 literal 0 HcmV?d00001 diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..6352d55 --- /dev/null +++ b/environment.yml @@ -0,0 +1,8 @@ +name: bastion +channels: + - defaults +dependencies: + - python=3.10 + - pyyaml + - openpyxl +prefix: /home/parselmouth/.conda/envs/bastion diff --git a/lab/strawman01.py b/lab/strawman01.py new file mode 100644 index 0000000..115f7b8 --- /dev/null +++ b/lab/strawman01.py @@ -0,0 +1,23 @@ +import Bastion.Site +import Bastion.Curator +import Bastion.Vault + + +site = Site.loadConfig() +vault = HPSS.Vault(site.vault['Purdue-Fortress']) + +for asset in site.assets: + if vault[asset].branches.latest.elapsed(today) > asset.policy.longevity: + result = vault.branch(asset) #-- create a new branch with a full backup at its base + else: + result = vault.put(asset, latest) #-- does a differential backup relative to latest. + + #---------------------------------- + #-- Do I need to do any cleanup? | + #---------------------------------- + if len(vault[asset].branches) > site.assets[].policy.LOCKS: + #-- Sort the backups from most to least recent (youngest to oldest) + q = sorted(vault[asset].branches, key = lambda b: b.created, reverse = True) + #-- Select the oldest branches for removal. + for branch in q[asset.policy.LOCKS:]: + vault.purge(branch) diff --git a/lib/Bastion/Common.py b/lib/Bastion/Common.py new file mode 100644 index 0000000..666a5b6 --- /dev/null +++ b/lib/Bastion/Common.py @@ -0,0 +1,48 @@ +""" +Bastion.Common +""" +import json +import hashlib +import base64 + +import yaml + + +def Slug40(text): + """ + I generate a 5-character slug based on the given text. + The slug is generated by hashing the text using SHAKE128, + then taking a 40-bit digest and encoding the digest using base32. + """ + h = hashlib.shake_128() + h.update(text.encode('utf-8')) + bs = h.digest(5) + return base64.b32encode(bs) + + + +class Sable: + def toJDN(self, **kwargs): + raise NotImplementedError + + def toJSON(self, **kwargs): + jdn = self.toJDN(**kwargs) + return json.dumps(jdn, indent = 3, sort_keys = True) + + def toYAML(self, **kwargs): + jdn = self.toJDN(**kwargs) + return yaml.dump(jdn, default_flow_style = False, indent = 3) + + @classmethod + def fromJDN(cls, jdn, **kwargs): + raise NotImplementedError + + @classmethod + def fromJSON(cls, js, **kwargs): + jdn = json.loads(js) + return cls.fromJDN(jdn, **kwargs) + + @classmethod + def fromYAML(cls, ydoc, **kwargs): + jdn = yaml.safe_load(ydoc) + return cls.fromJDN(jdn, **kwargs) diff --git a/lib/Bastion/Curator.py b/lib/Bastion/Curator.py new file mode 100644 index 0000000..4732e9c --- /dev/null +++ b/lib/Bastion/Curator.py @@ -0,0 +1,138 @@ +""" +Bastion.Curator + +I provide mostly data structures for working with archives and backups. +""" +from .Common import Sable, Slug40 +import hashlib +import base64 + + + +class Asset(Sable): + def __init__(name, path, about, **kwargs): + self.name = RDN + self.path = pathlib.Path(path) + self.about = about + self.RDN = None + + for kwarg in ['RDN']: + if kwarg in kwargs: + setattr(self, kwarg, kwargs[kwarg]) + + if self.RDN is None: + self.RDN = Slug40(self.name) + + def toJDN(self, **kwargs): + jdn = { + '_type': "Curator.Asset", + 'name': self.name, + 'path': str(self.path), + 'about': self.about, + 'RDN': self.RDN + } + return jdn + + +class Archive(Sable): + """ + I represent a top-level archive of some dataset. + I am analagous to a git repository in that I may contain + multiple branches of object evolution. + """ + def __init__(self, name, **kwargs): + self.name = name + self.RDN = None + + for kwarg in ['RDN']: + if kwarg in kwargs: + setattr(self, kwarg, kwargs[kwarg]) + + if self.RDN is None: + self.RDN = Slug40(self.name) + + def toJDN(self, **kwargs): + jdn = { + '_type': "Curator.Archive", + 'name': self.name, + 'RDN': self.RDN + } + return jdn + + +class Branch(Sable): + """ + I represent a branch (timeline of object evolution) relative to an archive. + """ + def __init__(self, RDN): + self.RDN = RDN + self.name = RDN + self._snaps = [ ] + + def head(self): + return self._snaps[-1] + + def base(self): + return self._snap[0] + + def created(self): + return self.base.deposited + + def updated(self): + return self.head.deposited + + def commit(self, snap): + self._snaps.append(snap) + self._snaps = sorted(self._snaps, key = lambda s: s.deposited) + + def __iter__(self): + return iter(self._snaps) + + @property + def age(self, whence = None): + whence = whence if (whence is not None) else datetime.datetime.now() + return (whence - self.created) + + + +class BlobRef: + def __init__(self, RDN, archive, branch, deposited): + self.RDN = RDN + self.name = RDN + self.archive = archive.RDN if isinstance(archive, Archive) else str(archive) + self.branch = branch.RDN if isinstance(branch, Branch) else str(branch) + self.deposited = deposited + + + +class Snap(Sable): + """ + I represent a "snapshot" in time and contain the necessary + information to restore a dataset to the state observed when this + snap was deposited. + """ + def __init__(self, RDN, archive, branch, deposited, layers, **kwargs): + self.RDN = RDN + self.name = RDN + self.archive = archive.RDN if isinstance(archive, Archive) else str(archive) + self.branch = branch.RDN if isinstance(branch, Branch) else str(branch) + self.deposited = deposited + self.layers = layers + self.about = kwargs.get('about', "") + + @property + def age(self, whence = None): + whence = whence if (whence is not None) else datetime.datetime.now() + return (whence - self.deposited) + + def toJDN(self): + jdn = { + 'RDN': self.RDN, + 'archive': self.archive, + 'branch': self.branch, + 'deposited': self.deposited.isoformat(), + 'layers': self.layers[:] + } + return jdn + + diff --git a/lib/Bastion/Site.py b/lib/Bastion/Site.py new file mode 100644 index 0000000..f82bab8 --- /dev/null +++ b/lib/Bastion/Site.py @@ -0,0 +1,88 @@ +""" +Bastion.Site +""" +import logging + +import openpyxl + +from .Common import Sable, Slug40 +from .Curator import Asset + +logger = logging.getLogger(__name__) + + +def loadSiteConfig(path = None): + if path is not None: + src = pathlib.Path(src) + else: + for p in ['~/.bastion/site.xlsx', '/etc/bastion/site.xlsx']: + p = pathlib.Path(p).expanduser() + if p.exists(): + src = p + break + + if src is None: + raise Exception("Cannot find site.xlsx configuration") + else: + logger.info("loading site configuration from {}".format(str(src))) + + conf = SiteConfig() + conf.loadXLSX(src) + + return conf + + + +class SiteConfig: + def __init__(self): + self.site = { } #-- confvar -> confval + self._assets = { } #-- @asset -> Asset + + @property + def assets(self): + return iter(self._assets.values()) + + def asset(self, k): + return self._assets[k] + + def loadXLSX(self, confpath): + wb = openpyxl.load_workbook(wb = str(confpath)) + #-- Read site conf... + self.gatherSiteEnv(wb) + #-- Read assets... + self.gatherAssets(wb) + + def gatherSiteVars(self): + ws = wb['site'] + raise NotImplementedError + + def gatherAssets(self): + pass + + +class CurationPolicy(Sable): + def __init__(self, name, path, **kwargs): + self.name = name + self.path = pathlib.Path(path) + + self.RDN = kwargs.get('RDN', Slug40(name)) + self.asset = self.RDN + self.LOCKS = kwargs.get('LOCKS', 2) #-- Lots Of Copies Keep us Safe, minimum # of branches to retain + self.longevity = kwargs.get('longevity', datetime.timedelta(days = 30)) #-- maximum time before a new branch is forced + self.about = kwargs.get('about', "") + + def toJDN(self, **kwargs): + jdn = { + '_type': "Bastion.Site.CurationPolicy", + 'RDN': self.RDN, + 'name': self.name, + 'LOCKS': self.LOCKS, + 'longevity': self.longevity.total_seconds() + 'path': str(self.path), + 'about': self.about + } + + @classmethod + def fromJDN(cls, jdn, **kwargs): + policy = cls(jdn['name'], jdn['path'], **jdn) + diff --git a/lib/Bastion/Vault.py b/lib/Bastion/Vault.py new file mode 100644 index 0000000..4a99469 --- /dev/null +++ b/lib/Bastion/Vault.py @@ -0,0 +1,33 @@ +""" +Bastion.Vault +""" +class isClerk: + """ + I am an abstract type for "clerk" objects that do data management + in the context of a vault. + """ + @property + def snaps(self): + raise NotImplementedError + + @property + def branches(self): + raise NotImplementedError + + + +class isVault: + """ + I am an abstract base type for specialized Vault classes. + """ + def __getitem__(self, asset): + raise NotImplementedError + + @property + def assets(self): + raise NotImplementedError + + def put(self, asset, latest = None): + pass + + def diff --git a/lib/Bastion/__init__.py b/lib/Bastion/__init__.py new file mode 100644 index 0000000..e69de29