Skip to content

Commit

Permalink
continued evolution of CARP
Browse files Browse the repository at this point in the history
  • Loading branch information
ndenny committed Oct 29, 2024
1 parent 436fc70 commit 2fd6e01
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 91 deletions.
117 changes: 64 additions & 53 deletions bin/bastion.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from Bastion.Condo import *
from Bastion.Actions import *
from Bastion.Model import ARK
from Bastion.CARP import SUCCESS, FAILURE, CRASH, SUCCEEDED, FAILED, CRASHED
from Bastion.CARP import Request
import Bastion.Vaults.HPSS
import Bastion.Vaults.BFD

Expand Down Expand Up @@ -134,6 +134,9 @@ def sites(self):
yield self.site(nick)

def run(self):
#-- generate a session ID.
self.session = "{}{}".format(Quantim.now().quaver, Boggle(5))

#-- scan the command line for options of the form "-{opt}:{value}"
#-- options are removed from the command sequence
opts = { }
Expand Down Expand Up @@ -190,24 +193,20 @@ def run(self):
tokens = idiom.split()
if tokens == comargs[:len(tokens)]:
action = method
request = CARP.Request(idiom, comargs[len(tokens):])
request = Request(idiom, comargs[len(tokens):], ID = self.session)

#-- execute the action within crash guardrails
try:
answer = action(comargs, comdex, opts)
answer = action(request)
except Exception as err:
tb = traceback.format_exception(err)
answer = CRASHED( ''.join(tb), tb )
#-- always log crashes!
answer.context['log.scope'] = '*'

#-- embed the process tracking info as part of the reply context
for k, v in proc.items():
answer['context'][k] = v

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

#-- write the answer to the log, if there is a given log.scope
self.record(answer, opts)
Expand All @@ -218,11 +217,9 @@ def run(self):
#-- explicitly exit based on the answer to the request
#-- status codes follow the general "theory of reply codes"
#-- e.g. those used in SMTP, HTTP, etc.
if answer['reply']['status'][0] in ('1','2','3'):
#-- codes in 100, 200, 300 blocks indicate success
if not answer.status.indicates_failure:
sys.exit(0)
else:
#-- codes in 400, 500 (others?) indicate failure
sys.exit(1)

def record(self, answer, opts):
Expand All @@ -232,13 +229,13 @@ def record(self, answer, opts):
I need answer to include "log.scope" in the context block.
If no "log.scope" is declared, I silently ignore the request to record.
"""
if 'log.scope' in answer['context']:
if 'log.scope' in answer.context:
#-- only write a log file if we have an explicit log.scope in the answer's context block.
session = answer['context']['task.session']
session = answer.request.ID

scope = self.logroot
if answer['context']['log.scope'] != '*':
scope = scope / answer['context']['log.scope']
if answer.context['log.scope'] != '*':
scope = scope / answer.context['log.scope']

halo = scope / "{}.yaml".format(session)
halo.parent.mkdir(parents = True, exist_ok = True)
Expand All @@ -259,23 +256,26 @@ def emit(self, answer, opts, ostream = None):
def emit_YAML(self, answer, opts, ostream):
yaml = YAML()
yaml.default_flow_style = False
answer['report'] = PreservedScalarString(answer['report'])
answer.report = PreservedScalarString(answer.report'])
yaml.dump(answer, ostream)

def emit_JSON(self, answer, opts, ostream):
json.dump(answer, ostream, sort_keys = True, indent = 3)

def emit_PROSE(self, answer, opts, ostream):
ostream.write("request: {request}\n".format(**answer))
ostream.write("reply: {status} {message}\n".format(**answer['reply']))
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))
ostream.write("----\n")
ostream.write(answer['report'])
ostream.write(answer.report)
ostream.write("\n")

#----------------------
#-- basic operations |
#----------------------
def do_help(self, comargs, comdex, opts):
def do_help(self, request):
#-- generate a document by scanning all of my "do_*" methods.
menu = [ ]
for attr in dir(self):
Expand Down Expand Up @@ -303,20 +303,20 @@ def do_help(self, comargs, comdex, opts):

doc = '\n----\n'.join(stanzas)

return SUCCESS(doc)
return request.succeeded(doc)

#--------------------------------------------------------
#-- BEGIN bank (backup) operations |
#-- all "bank" operations create full (level 0) backups |
#↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
def do_bank_site(self, comargs, comdex, opts):
def do_bank_site(self, request):
"""
bank site {site}
* creates a full backup for each asset in site.
"""
raise NotImplementedError

def do_bank_zone(self, comargs, comdex, opts):
def do_bank_zone(self, request):
"""
bank zone {site} {zone}
bank zone {ARK}
Expand All @@ -326,25 +326,27 @@ def do_bank_zone(self, comargs, comdex, opts):
"""
raise NotImplementedError

def do_bank_asset(self, comargs, comdex, opts):
def do_bank_asset(self, request):
"""
backup asset {ARK}
* creates a full backup of {ARK}
"""
ark = ARK(comdex[2])
site = self.site(ark.site)
asset = site.asset(ark)
vault = self.vault(asset.policy.vault)
blonde = vault.push(asset, client = self.hostname)
ark = ARK(request.args[0])
site = self.site(ark.site)
asset = site.asset(ark)
vault = self.vault(asset.policy.vault)

receipt = vault.push(asset, client = self.hostname)

extras = {
'log.scope': "site/{}".format(ark.site)
}

if blonde:
return SUCCESS("pushed full backup {}".format(str(blonde)), context = extras)
if receipt.indicates_success:
blonde = receipt.body['blonde']
return request.succeeded("pushed full backup of {} to {}".format(str(ark), str(blonde)), receipt, context = extras)
else:
return FAILED("something went wrong!", context = extras)
return request.failed("while pushing full backup of {}, something went wrong!".format(str(ark)), receipt, context = extras)

#↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
#-- END bank (backup) operations |
Expand All @@ -354,21 +356,26 @@ def do_bank_asset(self, comargs, comdex, opts):
#-- BEGIN amend (differential) operations |
#-- all "amend" operations create differential (level 1) backups |
#↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
def do_amend_asset(self, comargs, comdex, opts):
def do_amend_asset(self, request):
"""
amend asset {ARK}
* creates a differential backup of {ARK}
"""
ark = ARK(comdex[2])
site = self.site(ark.site)
asset = site.asset(ark)
vault = self.vault(asset.policy.vault)
flag, stdout, stderr = vault.push(asset, detail = 'D', client = self.hostname)
flag, stdout, stderr = vault.push(asset, detail = 'D')
if flag:
return SUCCESS(stdout, {'stdout': stdout, 'stderr': stderr})
ark = ARK(request.args[0])
site = self.site(ark.site)
asset = site.asset(ark)
vault = self.vault(asset.policy.vault)
receipt = vault.push(asset, detail = 'D')

extras = {
'log.scope': "site/{}".format(ark.site)
}

if receipt.indicates_success:
blonde = receipt.body['blonde']
return request.succeeded("amended asset {} to {}".format(str(ark), str(blonde)), receipt, context = extras)
else:
return FAILED(stdout, {'stdout': stdout, 'stderr': stderr})
return request.failed("amend operation on asset {} failed".format(str(ark)), receipt, context = extras)

#↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
#-- END amend (differential) operations |
Expand All @@ -395,30 +402,33 @@ def do_update_zone(self, comargs, comdex, opts):
"""
raise NotImplementedError

def do_update_asset(self, comargs, comdex, opts):
def do_update_asset(self, request):
"""
update asset {ARK}
* asset given in ARK format
* determines backup level based on asset's policy and current manifest
* performs backup based on determined level
* typically used to "automatically" do updates in a scheduled (cron) job
"""
ark = ARK(comdex[2])
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]
blonde = vault.push(asset, anchor)
receipt = vault.push(asset, anchor)
else:
blonde = vault.push(asset)
receipt = vault.push(asset)

extras = {
'log.scope': "site/{}".format(ark.site)
}

return SUCCESS("pushed update {}".format(str(blonde)), context = extras)

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 |
Expand Down Expand Up @@ -481,18 +491,19 @@ def do_export_manifest(self, comargs, comdex, opts):

return SUCCESS(report, manifest.toJDN())

def do_export_sites_provisioned(self, comargs, comdex, opts):
def do_export_sites_provisioned(self, request, comargs, comdex, opts):
"""
export sites provisioned {vault}
* lists all sites that are provisioned in the given vault
"""
vault = self.vault(comdex[3])
comdex = dict(enumerate(request.args.keys()))
vault = self.vault(comdex[0])
sites = list(vault.sites)

spool = ["# sites banked in {}".format(vault.name)]
page = ["# sites banked in {}".format(vault.name)]
for site in sites:
spool.append("* {}".format(site))
report = '\n'.join(spool)
page.append("* {}".format(site))
report = '\n'.join(page)
data = {vault.name: sites}

return SUCCESS(report, data)
Expand Down
Loading

0 comments on commit 2fd6e01

Please sign in to comment.