Source code for synapse.cmds.hive

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)