import os
import json
import shlex
import pprint
import asyncio
import tempfile
import functools
import subprocess
import synapse.exc as s_exc
import synapse.common as s_common
import synapse.lib.cmd as s_cmd
import synapse.lib.cli as s_cli
ListHelp = '''
Lists all the keys underneath a particular key in the hive.
Syntax:
hive ls|list [path]
Notes:
If path is not specified, the root is listed.
'''
GetHelp = '''
Display or save to file the contents of a key in the hive.
Syntax:
hive get [--file] [--json] {path}
'''
DelHelp = '''
Deletes a key in the cell's hive.
Syntax:
hive rm|del {path}
Notes:
Delete will recursively delete all subkeys underneath path if they exist.
'''
EditHelp = '''
Edits or creates a key in the cell's hive.
Syntax:
hive edit|mod {path} [--string] ({value} | --editor | -f {filename})
Notes:
One may specify the value directly on the command line, from a file, or use an editor. For the --editor option,
the environment variable VISUAL or EDITOR must be set.
'''
[docs]
class HiveCmd(s_cli.Cmd):
'''
Manipulates values in a cell's Hive.
A Hive is a hierarchy persistent storage mechanism typically used for configuration data.
'''
_cmd_name = 'hive'
_cmd_syntax = (
('line', {'type': 'glob'}), # type: ignore
)
def _make_argparser(self):
parser = s_cmd.Parser(prog='hive', outp=self, description=self.__doc__)
subparsers = parser.add_subparsers(title='subcommands', required=True, dest='cmd',
parser_class=functools.partial(s_cmd.Parser, outp=self))
parser_ls = subparsers.add_parser('list', aliases=['ls'], help="List entries in the hive", usage=ListHelp)
parser_ls.add_argument('path', nargs='?', help='Hive path')
parser_get = subparsers.add_parser('get', help="Get any entry in the hive", usage=GetHelp)
parser_get.add_argument('path', help='Hive path')
parser_get.add_argument('-f', '--file', default=False, action='store',
help='Save the data to a file.')
parser_get.add_argument('--json', default=False, action='store_true', help='Emit output as json')
parser_rm = subparsers.add_parser('del', aliases=['rm'], help='Delete a key in the hive', usage=DelHelp)
parser_rm.add_argument('path', help='Hive path')
parser_edit = subparsers.add_parser('edit', aliases=['mod'], help='Sets/creates a key', usage=EditHelp)
parser_edit.add_argument('--string', action='store_true', help="Edit value as a single string")
parser_edit.add_argument('path', help='Hive path')
group = parser_edit.add_mutually_exclusive_group(required=True)
group.add_argument('value', nargs='?', help='Value to set')
group.add_argument('--editor', default=False, action='store_true',
help='Opens an editor to set the value')
group.add_argument('--file', '-f', help='Copies the contents of the file to the path')
return parser
[docs]
async def runCmdOpts(self, opts):
line = opts.get('line')
if line is None:
self.printf(self.__doc__)
return
core = self.getCmdItem()
try:
opts = self._make_argparser().parse_args(shlex.split(line))
except s_exc.ParserExit:
return
handlers = {
'list': self._handle_ls,
'ls': self._handle_ls,
'del': self._handle_rm,
'rm': self._handle_rm,
'get': self._handle_get,
'edit': self._handle_edit,
'mod': self._handle_edit,
}
await handlers[opts.cmd](core, opts)
[docs]
@staticmethod
def parsepath(path):
''' Turn a slash-delimited path into a list that hive takes '''
return path.split('/')
async def _handle_ls(self, core, opts):
path = self.parsepath(opts.path) if opts.path is not None else None
keys = await core.listHiveKey(path=path)
if keys is None:
self.printf('Path not found')
return
for key in keys:
self.printf(key)
async def _handle_get(self, core, opts):
path = self.parsepath(opts.path)
valu = await core.getHiveKey(path)
if valu is None:
self.printf(f'{opts.path} not present')
return
if opts.json:
prend = json.dumps(valu, indent=4, sort_keys=True)
rend = prend.encode()
elif isinstance(valu, str):
rend = valu.encode()
prend = valu
elif isinstance(valu, bytes):
rend = valu
prend = pprint.pformat(valu)
else:
rend = json.dumps(valu, indent=4, sort_keys=True).encode()
prend = pprint.pformat(valu)
if opts.file:
with s_common.genfile(opts.file) as fd:
fd.truncate(0)
fd.write(rend)
self.printf(f'Saved the hive entry [{opts.path}] to {opts.file}')
return
self.printf(f'{opts.path}:\n{prend}')
async def _handle_rm(self, core, opts):
path = self.parsepath(opts.path)
await core.popHiveKey(path)
async def _handle_edit(self, core, opts):
path = self.parsepath(opts.path)
if opts.value is not None:
if opts.value[0] not in '([{"':
data = opts.value
else:
data = json.loads(opts.value)
await core.setHiveKey(path, data)
return
elif opts.file is not None:
with open(opts.file) as fh:
s = fh.read()
if len(s) == 0:
self.printf('Empty file. Not writing key.')
return
data = s if opts.string else json.loads(s)
await core.setHiveKey(path, data)
return
editor = os.getenv('VISUAL', (os.getenv('EDITOR', None)))
if editor is None or editor == '':
self.printf('Environment variable VISUAL or EDITOR must be set for --editor')
return
tnam = None
try:
with tempfile.NamedTemporaryFile(mode='w', delete=False) as fh:
old_valu = await core.getHiveKey(path)
if old_valu is not None:
if opts.string:
if not isinstance(old_valu, str):
self.printf('Existing value is not a string, therefore not editable as a string')
return
data = old_valu
else:
try:
data = json.dumps(old_valu, indent=4, sort_keys=True)
except (ValueError, TypeError):
self.printf('Value is not JSON-encodable, therefore not editable.')
return
fh.write(data)
tnam = fh.name
while True:
retn = subprocess.call(f'{editor} {tnam}', shell=True)
if retn != 0: # pragma: no cover
self.printf('Editor failed with non-zero code. Aborting.')
return
with open(tnam) as fh:
rawval = fh.read()
if len(rawval) == 0: # pragma: no cover
self.printf('Empty file. Not writing key.')
return
try:
valu = rawval if opts.string else json.loads(rawval)
except json.JSONDecodeError as e: # pragma: no cover
self.printf(f'JSON decode failure: [{e}]. Reopening.')
await asyncio.sleep(1)
continue
# We lose the tuple/list distinction in the telepath round trip, so tuplify everything to compare
if (opts.string and valu == old_valu) or (not opts.string and s_common.tuplify(valu) == old_valu):
self.printf('Valu not changed. Not writing key.')
return
await core.setHiveKey(path, valu)
break
finally:
if tnam is not None:
os.unlink(tnam)