diff --git a/bin/bastion.py b/bin/bastion.py index b26de0c..407b9c1 100755 --- a/bin/bastion.py +++ b/bin/bastion.py @@ -27,7 +27,7 @@ from Bastion.Site import Site from Bastion.Condo import Condex from Bastion.Model import ARK -from Bastion.CARP import Request, isReceipt, isReport +from Bastion.CARP import Request, isResult, isReport import Bastion.Vaults.HPSS import Bastion.Vaults.BFD import Bastion.Vaults.SFTP @@ -107,8 +107,14 @@ def tongue(self): def logroot(self): return self.conf.get(asPath, "bastion.logging.path", "/var/log/bastion") - def site(self, name): - return Site(name).configured(self.conf) + def site(self, s): + if isinstance(s, ARK): + return Site(s.name).configure(self.conf) + elif isinstance(s, str): + #-- Assume that s is the name of a site. + return Site(s).configured(self.conf) + else: + raise ValueError('"{}" is not a valid reference to a site'.format(str(s))) def asset(self, ark): return self.site(ark.site).asset(ark) @@ -160,19 +166,35 @@ def run(self): "backup asset", self.do_bank_asset), ("amend asset", self.do_amend_asset), + #("amend assets", self.do_amend_assets), + ("amend site", self.do_amend_site), + ("amend zone", self.do_amend_zone), - ("update asset", self.do_update_asset), - ("update zone", self.do_update_zone), - ("update site", self.do_update_site), + ("export site catalog", + "list site catalog", self.do_export_site_catalog), ("export sites provisioned", "list sites provisioned", self.do_export_sites_provisioned), + ("export zones provisioned", "list zones provisioned", self.do_export_zones_provisioned), + ("export manifest", "show manifest", self.do_export_manifest), - ("refresh keytab", self.do_refresh_keytab), + ("export sites declared", + "list sites declared", self.do_export_sites_declared), + + ("export zones declared", + "list zones declared", self.do_export_zones_declared), + + ("export assets declared", + "list assets declared", self.do_export_assets_declared), + + ("export vaults declared", + "list vaults declared", self.do_export_vaults_declared), + + ("refresh keytab", self.do_refresh_keytab) ] #-- Look for an explicitly declared session ID in opts; @@ -193,12 +215,12 @@ def run(self): tokens = idiom.split() if tokens == comargs[:len(tokens)]: action = method - request = Request(idiom, comargs[len(tokens):], ID = self.session, context = opts) + request = Request(idiom, *comargs[len(tokens):], ID = self.session, context = opts) #-- execute the action within crash guardrails try: answer = action(request) - if not isinstance(answer, isReceipt): + if not isinstance(answer, isResult): raise ValueError("actions must respond with an instance of CARP.isReceipt") except Exception as err: answer = request.crashed(err) @@ -254,22 +276,22 @@ def emit(self, answer, ostream = None): do_emit = self.emitters.get(form, self.emitters['YAML']) return do_emit(answer, ostream) - def emit_YAML(self, answer, ostream): + def emit_YAML(self, result, ostream): yaml = YAML() yaml.default_flow_style = False - yaml.dump(answer, ostream) + yaml.dump(result.toJDN(), ostream) - def emit_JSON(self, answer, ostream): - json.dump(answer, ostream, sort_keys = True, indent = 3) + def emit_JSON(self, result, ostream): + json.dump(result.toJDN(), ostream, sort_keys = True, indent = 3) - def emit_PROSE(self, answer, ostream): - request = answer.request - requested = ' '.join([request.action] + request.args) - ostream.write("request: {}\n".format(requested)) - ostream.write("reply: {reply.code} {reply.gloss}\n".format(reply=answer.status)) - ostream.write("# {reply.lede}".format(reply=answer)) + def emit_PROSE(self, result, ostream): + request = result.request + ostream.write("request: {}\n".format(request.lede)) + ostream.write("reply: {reply.code} {reply.gloss}\n".format(reply=result.status)) + ostream.write("# {reply.lede}\n".format(reply=result)) ostream.write("----\n") - ostream.write(answer.report) + if isinstance(result, isReport): + ostream.write(result.body) ostream.write("\n") #---------------------- @@ -303,18 +325,45 @@ def do_help(self, request): doc = '\n----\n'.join(stanzas) - return request.succeeded(doc) + return request.succeeded(doc, report = doc) #-------------------------------------------------------- #-- BEGIN bank (backup) operations | #-- all "bank" operations create full (level 0) backups | #↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + def do_bank_asset(self, request): + """ + backup asset {ARK} + * creates a full backup of {ARK} + """ + ark = ARK(request.args[0]) + site = self.site(ark.site) + asset = site.asset(ark) + vault = self.vault(asset.policy.vault) + + request['log.scope'] = "site/{}".format(ark.site) + result = vault.push(asset, client = self.hostname) + + if result.indicates_success: + return request.succeeded(result, report = "pushed full backup of {} to {}".format(str(ark), str(result.blonde))) + else: + return request.failed(result, report = "while pushing full backup of {}, something went wrong!".format(str(ark))) + def do_bank_site(self, request): """ bank site {site} * creates a full backup for each asset in site. """ - raise NotImplementedError + site = self.site(request.args[0]) + + request['log.scope'] = "site/{}".format(site.name) + + arks = [ ] + for zone in site.zones: + for asset in zone: + arks.append(asset.ark) + + return self._bank_assets(request, arks) def do_bank_zone(self, request): """ @@ -333,44 +382,42 @@ def do_bank_zone(self, request): else: raise ValueError("do_bank_zone expects zone to be given as a CURIE'd ARK") - site = self.site(ark.site) - zone = site.zone(ark.zone) - - receipts = [ ] - for asset in zone: - vault = self.vault(asset.policy.vault) - 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]) - - pushed = [receipt.body['blonde'] for receipt in receipts if receipt.indicates_success] + site = self.site(ark) + zone = site.zone(ark) + arks = [asset.ark for asset in zone] + request['log.scope'] = "site/{}".format(site.name) + return self._bank_assets(request, arks) - if all_succeeded: - return request.succeeded("all {} assets successfully pushed".format(len(receipts)), receipts) - elif all_failed: - return request.failed("push FAILED for ALL {} ASSETS".format(len(receipts)), receipts) - else: - return request.inconclusive("{}/{} assets successfully pushed".format(len(pushed), len(receipts))) + def do_bank_assets(self, request): + """ + bank assets {ARK} [{ARK},...] + """ + request['log.scope'] = "site/*" + arks = [ARK(request.args[i]) for i in sorted(request.args.keys())] + return self._bank_assets(request, arks) - def do_bank_asset(self, request): + def _bank_assets(self, request, arks): """ - backup asset {ARK} - * creates a full backup of {ARK} + helper method to push (bank) many assets given a single (batched) request. """ - ark = ARK(request.args[0]) - site = self.site(ark.site) - asset = site.asset(ark) - vault = self.vault(asset.policy.vault) + results = [ ] + for ark in arks: + asset = self.asset(ark) + vault = self.vault(asset.policy.vault) + result = vault.push(asset, client = self.hostname) + results.append(result) - request['log.scope'] = "site/{}".format(ark.site) - result = vault.push(asset, client = self.hostname) + all_succeeded = all([result.indicates_success for result in results]) + all_failed = all([result.indicates_failure for result in results]) - if result.indicates_success: - return request.succeeded(result, report = "pushed full backup of {} to {}".format(str(ark), str(result.blonde))) + pushed = [result['blonde'] for result in results if result.indicates_success] + + if all_succeeded: + return request.succeeded(results, report = "all {} assets successfully pushed".format(len(results))) + elif all_failed: + return request.failed(results, report = "push FAILED for ALL {} ASSETS".format(len(results))) else: - return request.failed(result, report = "while pushing full backup of {}, something went wrong!".format(str(ark))) + return request.inconclusive(results, report = "{}/{} assets successfully pushed".format(len(pushed), len(results))) #↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ #-- END bank (backup) operations | @@ -386,20 +433,80 @@ def do_amend_asset(self, request): * creates a differential backup of {ARK} """ ark = ARK(request.args[0]) - site = self.site(ark.site) + site = self.site(ark) asset = site.asset(ark) vault = self.vault(asset.policy.vault) - receipt = vault.push(asset, detail = 'D') + result = vault.ammend(asset) - extras = { - 'log.scope': "site/{}".format(ark.site) - } + request['log.scope'] = "site/{}".format(ark.site) + + if result.indicates_success: + blonde = result.body['blonde'] + return request.succeeded(result, report = "amended asset {} to {}".format(str(ark), str(blonde))) + else: + return request.failed(result, report = "amend operation on asset {} failed".format(str(ark))) + + def do_amend_site(self, request): + """ + amend site {site} + * creates a differential backup for each asset in site. + """ + site = self.site(request.args[0]) + + request['log.scope'] = "site/{}".format(site.name) + + arks = [ ] + for zone in site.zones: + for asset in zone: + arks.append(asset.ark) - if receipt.indicates_success: - blonde = receipt.body['blonde'] - return request.succeeded("amended asset {} to {}".format(str(ark), str(blonde)), receipt, context = extras) + return self._amend_assets(request, arks) + + def do_amend_zone(self, request): + """ + amend zone {site} {zone} + amend zone {ARK} + * creates a differential backup for each asset in the given zone + * zone can be given as two arguments (site, zone) + * zone can be given as a single argument in ARK format + """ + if len(request.args) == 1: + #-- We were given a single ARK argument. + ark = ARK(request.args[0]) + elif len(request.args) == 2: + #-- We were given two arguments (site, zone) + ark = ARK(request.args[0], request.args[1]) + else: + raise ValueError("do_amend_zone expects zone to be given as a CURIE'd ARK") + + site = self.site(ark) + zone = site.zone(ark) + arks = [asset.ark for asset in zone] + request['log.scope'] = "site/{}".format(site.name) + return self._amend_assets(request, arks) + + def _amend_assets(self, request, arks): + """ + helper method to amend (differential backup) many assets given a single (batched) request. + """ + results = [ ] + for ark in arks: + asset = self.asset(ark) + vault = self.vault(asset.policy.vault) + result = vault.amend(asset, client = self.hostname) + results.append(result) + + all_succeeded = all([result.indicates_success for result in results]) + all_failed = all([result.indicates_failure for result in results]) + + amended = [result['blonde'] for result in results if result.indicates_success] + + if all_succeeded: + return request.succeeded(result, report = "all {} assets successfully amended".format(len(results))) + elif all_failed: + return request.failed(result, report = "amend FAILED for ALL {} ASSETS".format(len(results))) else: - return request.failed("amend operation on asset {} failed".format(str(ark)), receipt, context = extras) + return request.inconclusive(result, report = "{}/{} assets successfully pushed".format(len(amended), len(results))) #↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ #-- END amend (differential) operations | @@ -434,25 +541,26 @@ def do_update_asset(self, request): * performs backup based on determined level * typically used to "automatically" do updates in a scheduled (cron) job """ - ark = ARK(request.args[0]) - asset = self.asset(ark) - vault = self.vault(asset.policy.vault) - - banked = vault.manifest(ark) - if banked.anchors: - anchor = banked.anchors[-1] - receipt = vault.push(asset, anchor) - else: - receipt = vault.push(asset) - - extras = { - 'log.scope': "site/{}".format(ark.site) - } - - if receipt.indicates_success: - return request.succeeded("updated asset {}".format(str(ark)), receipt, context = extras) - else: - return request.failed("update request on asset {} failed".format(str(ark)), receipt, context = extras) + raise NotImplementedError +# ark = ARK(request.args[0]) +# asset = self.asset(ark) +# vault = self.vault(asset.policy.vault) +# +# banked = vault.manifest(ark) +# if banked.anchors: +# anchor = banked.anchors[-1] +# receipt = vault.push(asset, anchor) +# else: +# receipt = vault.push(asset) +# +# extras = { +# 'log.scope': "site/{}".format(ark.site) +# } +# +# if receipt.indicates_success: +# return request.succeeded("updated asset {}".format(str(ark)), receipt, context = extras) +# else: +# return request.failed("update request on asset {} failed".format(str(ark)), receipt, context = extras) #↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ #-- END update (automatic) operations | @@ -465,28 +573,39 @@ def do_update_asset(self, request): #-- none of these ops will modify any storage | #-- aka "list" or "show" | #↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ - def do_export_zone_assets(self, comargs, comdex, opts): + def do_export_zone_assets(self, request): """ - export zone assets {ark} + export site assets {site} """ - spec = comdex[3] - ark = ARK(spec) - site = self.site( ark.site ) - zone = site.zone( ark.zone ) - assets = { } - for asset in zone.assets: - assets[asset.name] = {'ARK': str(asset.ARK), 'badge': asset.badge} - - manifest = "\n".join(["* {}".format(asset.name) for asset in zone.assets]) - report = """ -# {site} -## {zone} -{assets} - """.format(site = ark.site, zone = ark.zone, assets = manifest) - - return SUCCESS(report.strip(), assets) + site = self.site(request.args[0]) + inventory = [ ] + for zone in site.zones: + inventory.extend( zone.assets ) + return self._export_assets(request, inventory) - def do_export_manifest(self, comargs, comdex, opts): + def do_export_site_assets(self, request): + """ + export site assets {site} + """ + site = self.site(request.args[0]) + inventory = [ ] + for zone in site.zones: + inventory.extend( zone.assets ) + return self._export_assets(request, inventory) + + def _export_assets(self, request, arks): + sites = sorted(set([ark.site for ark in arks])) + doc = ["# List of assets"] + for sname in sites: + doc.append("## {}".format(sname)) + site = self.site(sname) + for zone in sorted(site.zones): + doc.append(["### {}: {}".format(zone.name, str(zone.root))]) + for asset in zone.assets: + doc.append("* {} {}".format(asset.ARK, asset.badge)) + return '\n'.join( doc ) + + def do_export_manifest(self, request): """ export manifest {ARK} export manifest {ARK} {vault} @@ -496,102 +615,189 @@ def do_export_manifest(self, comargs, comdex, opts): * vault can be explicitly named to override policy """ #-- ARK of interest? - ark = ARK(comdex[2]) + ark = ARK(request.args[0]) site = self.site(ark.site) asset = site.asset(ark) - #-- were we given an explicit vault? - there = self.comdex.get(3, None) - #-- if we were given a specific vault, use that, otherwise look at the policy for the asset. - vault = self.vault(there) if (there is not None) else self.vault(asset.policy.vault) + altvault = request.args.get(1, None) + vault = self.vault(altvault) if (altvault is not None) else self.vault(asset.policy.vault) manifest = vault.manifest(ark) spool = ["# manifest for {} banked in {}".format(str(ark), vault.name)] for item in manifest: - spool.append("* {}".format(str(item))) - report = '\n'.join(spool) + spool.append("* {} ({})".format(str(item), item.when.earliest.isoformat())) + doc = '\n'.join(spool) + + return request.succeeded(manifest, report = doc) - return SUCCESS(report, manifest.toJDN()) + def do_export_site_catalog(self, request): + """ + export site catalog {site} + export site catalog {site} {vault} + Answers a list of all anchors associated to the given site. + """ + site_name = request.args[0] + site = self.site(site_name) - def do_export_sites_provisioned(self, request, comargs, comdex, opts): + #-- if we were given a specific vault, use that, otherwise look at the policy for the asset. + altvault = request.args.get(1, None) + vault = self.vault(altvault) if (altvault is not None) else self.vault(site.policy.vault) + + catalog = { } + zone_names = vault.zones(site_name) + for zone_name in zone_names: + catalog[zone_name] = vault.assets(site_name, zone_name) + + doclins = ["#Site {} assets archived in vault {}".format(site_name, vault.name)] + for zone_name in vault.zones(site): + doclins.append("## {}".format(zone_name)) + for asset_name in catalog[zone_name]: + manifest = vault.manifest(ARK(site_name, zone_name, asset_name)) + anchor = manifest.head.anchor + origin = anchor.when.datetime.isoformat() + recent = manifest.head.tail.when.datetime.isoformat() + doclins.append("* {} {} ({} → {})".format(asset_name, str(anchor), origin, recent)) + doc = '\n'.join(doclins) + + return request.succeeded(catalog, report = doc) + + def do_export_sites_provisioned(self, request): """ export sites provisioned {vault} * lists all sites that are provisioned in the given vault """ - comdex = dict(enumerate(request.args.keys())) - vault = self.vault(comdex[0]) - sites = list(vault.sites) + vault = self.vault( request.args[0] ) + + site_names = list(vault.sites) - page = ["# sites banked in {}".format(vault.name)] - for site in sites: - page.append("* {}".format(site)) - report = '\n'.join(page) - data = {vault.name: sites} + page = ["# Sites provisioned in {}".format(vault.name)] + for site_name in site_names: + page.append("* {}".format(site_name)) + doc = '\n'.join(page) - return SUCCESS(report, data) + return request.succeeded(site_names, report = doc) - def do_export_zones_provisioned(self, comargs, comdex, opts): + def do_export_zones_provisioned(self, request): """ export zones provisioned {vault} {site} * lists all zones provisioned in the given vault for the named site """ - vault = self.vault(comdex[3]) - site = comdex.get(4, None) + vault = self.vault( request.args[0] ) + site_name = request.args[1] - if site is None: - return FAILED("must specify a site provisioned in {}".format(vault.name)) - if site not in vault.sites: - return FAILED("site {} is not known in {}".format(site, vault.name)) + zone_names = vault.zones(site_name) - zones = vault.zones(site) - - spool = ["# {} zones banked in {}".format(site, vault.name)] - if zones: - for zone in zones: - spool.append("* {}".format(zone)) + spool = ["# Site {} banked in {}".format(site_name, vault.name)] + if zone_names: + for zone_name in zone_names: + spool.append("* {}".format(zone_name)) else: spool.append("no zones are currently banked") - report = "\n".join(spool) - data = {vault.name: {site: list(zones)}} + doc = "\n".join(spool) + record = {vault.name: {site_name: list(zone_names)}} + + return request.succeeded(record, report = doc) + + def do_export_vaults_declared(self, request): + """ + export vaults declared + """ + vault_names = sorted(self.conf["vaults"].keys()) + record = {'vaults': {}} + page = ["# Vaults declared"] + for vault_name in vault_names: + vault_protocol = self.conf.get("vaults.{}.protocol".format(vault_name), '???') + page.append("* {} ({})".format(vault_name, vault_protocol)) + record['vaults'][vault_name] = vault_protocol + doc = '\n'.join(page) + + return request.succeeded(record, report = doc) + + def do_export_sites_declared(self, request): + """ + export sites declared + """ + page = ["# Sites declared"] + for site in self.sites: + page.append("* {}".format(site.name)) + doc = '\n'.join(page) + record = [site.name for site in self.sites] - return SUCCESS(report, data) + return request.succeeded(record, report = doc) + + def do_export_zones_declared(self, request): + """ + export zones declared {site} + """ + site = self.site(request.args[0]) + + page = ["# Zones declared in {}".format(site.name)] + for zone in site.zones: + page.append("* {} {}".format(zone.name, str(zone.root))) + doc = '\n'.join(page) + record = {site.name: [[zone.name, str(zone.root)] for zone in site.zones]} + + return request.succeeded(record, report = doc) + + def do_export_assets_declared(self, request): + """ + export assets declared {site} {zone} + export assets declared {ark} + """ + if len(request.args) == 2: + site_name = request.args[0] + zone_name = request.args[1] + ark = ARK(site_name, zone_name) + elif len(request.args) == 1: + ark = ARK(request.args[0]) + site_name = ark.site + zone_name = ark.zone + else: + raise ValueError("export assets declared takes either (site,zone) or an ARK") + + site = self.site(site_name) + zone = site.zone(zone_name) + + page = ["# Assets declared in {}".format(str(ark))] + for asset in site.assets(zone): + page.append("* {} {} {}".format(asset.name, str(asset.badge), str(asset.halo))) + doc = '\n'.join(page) + record = {site.name: {zone.name: [[asset.name, asset.badge, str(asset.halo)] for asset in site.assets(zone)]}} + + return request.succeeded(record, report = doc) #↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ #-- END "export" operations | #-------------------------------- - def do_refresh_keytab(self, comargs, comdex, opts): + def do_refresh_keytab(self, request): """ refresh keytab {vault} * uses ssh+scp to regenerate the private keytab for the named vault. """ - subject = comdex[2] - vault = self.vault(subject) - - extras = { - 'log.scope': "vault/{}".format(subject) - } + subject = request.args[0] + vault = self.vault(subject) if not vault: #-- We didn't get a reference to an actual vault. #-- The vault requested isn't declared. #-- FAIL! - extras['log.scope'] = "vault" - return FAILED("vault {} is not declared".format(subject), context = extras) + request['log.scope'] = "vault/*" + return request.failed(None, report = "vault {} is not declared".format(subject)) if not callable( getattr(vault, 'refresh_keytab', None) ): #-- FAIL immediately since this vault doesn't declare #-- a method to refresh keytabs ... probably because the #-- vault doesn't use keytab files. - return FAILED("vault {} doesn't support keytab refresh".format(), context = extras) + return request.failed(None, report = "vault {} doesn't support keytab refresh".format()) - flag, stdout, stderr = vault.refresh_keytab() - if flag: - return SUCCESS(stdout, {'stdout': stdout, 'stderr': stderr}, context = extras) + result = vault.refresh_keytab(self) + if result.succeeded: + return request.succeeded(result, report = "keytab refreshed") else: - return FAILED(stdout, {'stdout': stdout, 'stderr': stderr}, context = extras) + return request.failed(result, report = "keytab regeneration failed") diff --git a/lib/Bastion/CARP.py b/lib/Bastion/CARP.py index 5a47889..a2f9daf 100644 --- a/lib/Bastion/CARP.py +++ b/lib/Bastion/CARP.py @@ -150,6 +150,10 @@ def __init__(self, action, *args, **kwargs): for k, v in kwargs['context'].items(): self.context[k] = v + @property + def lede(self): + return ' '.join( [self.action] + [self.args[i] for i in sorted(self.args.keys())] ) + def __getitem__(self, k): return self.context[k] @@ -243,6 +247,17 @@ def toJDN(self, **kwargs): return jdn + def __getitem__(self, k, *args): + if k in self.context: + return self.context[k] + elif args: + return args[0] + else: + raise KeyError(k) + + def __setitem__(self, k, v): + self.context[k] = v + @property def succeeded(self): return self.status.indicates_success diff --git a/lib/Bastion/Model.py b/lib/Bastion/Model.py index a28f013..5272702 100644 --- a/lib/Bastion/Model.py +++ b/lib/Bastion/Model.py @@ -150,11 +150,11 @@ def zones(self, site): """ return self.clerk.zones(site) - def assets(self, zone): + def assets(self, site_name, zone_name): """ I am the set of all assets tracked by this vault. """ - return self.clerk.assets(zone) + return self.clerk.assets(site_name, zone_name) def manifest(self, ark): """ @@ -227,6 +227,15 @@ def unpack(self, halo, root, **kwargs): #-- BEGIN SUBCLASS RESPONSIBILITY | #-- Subclasses must implement these methods. | #↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ + def amend(self, asset, **kwargs): + """ + Given an asset, I push a differential backup based on the most recent genus. + """ + #-- Get the current genus (aka full backup or anchor) + manifest = self.clerk.manifest(asset.ark) + #-- Call .push with the genus as the basis for the differential backup. + return self.push(asset, manifest.head.genus, **kwargs) + def push(self, asset, basis = None, **kwargs): """ Given an asset, I push a backup of the asset to this vault. @@ -238,10 +247,7 @@ def push(self, asset, basis = None, **kwargs): 2. use .put() to transfer the local archive to the vault space 3. perform a vault-specific transfer verification 4. remove the local, scratch (spool) archive file. - Answers a tuple of (transferred, blonde, receipt), where... - * transferred - is the True/False indication answered by .put() - * blonde - is the string reprsentation of the blonde for the archive of the asset, - * receipt - is detailed, structured answer given by the .put() operation. + Answers an instance of CARP.isResult (typically a Report instance) """ raise NotImplementedError @@ -388,7 +394,7 @@ def zones(self, site): """ raise NotImplementedError - def assets(self, zone): + def assets(self, site_name, zone_name): """ I am the set of all assets tracked by this vault. """ diff --git a/lib/Bastion/Site.py b/lib/Bastion/Site.py index 581465f..4f2b807 100644 --- a/lib/Bastion/Site.py +++ b/lib/Bastion/Site.py @@ -70,7 +70,12 @@ def zones(self): return [self._zones[z] for z in sorted(self._zones.keys())] def zone(self, z): - return self._zones[RDN(z)] + if isinstance(z, ARK): + return self.zone(z.zone) + elif isinstance(z, str): + return self._zones[RDN(z)] + else: + raise ValueError("zone expects an ARK or a name") @property def RDN(self): diff --git a/lib/Bastion/Vaults/Common.py b/lib/Bastion/Vaults/Common.py index be1a538..77d0aae 100644 --- a/lib/Bastion/Vaults/Common.py +++ b/lib/Bastion/Vaults/Common.py @@ -37,7 +37,6 @@ def push(self, asset, basis = None, **kwargs): request = PushRequest(asset, basis, **kwargs) receipt = PushReceipt() - #-- Do the pack activity. packing = self.pack(asset, basis, **kwargs) receipt.append(packing, 'packed')