ProPeler
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
opt
/
cpanel-ccs
/
calendarserver
/
tools
/
shell
/
Filename :
cmd.py
back
Copy
## # Copyright (c) 2011-2017 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## """ Data store commands. """ __all__ = [ "UsageError", "UnknownArguments", "CommandsBase", "Commands", ] from getopt import getopt from twext.python.log import Logger from twisted.internet.defer import succeed from twisted.internet.defer import inlineCallbacks, returnValue from twisted.conch.manhole import ManholeInterpreter from txdav.common.icommondatastore import NotFoundError from calendarserver.version import version from calendarserver.tools.tables import Table from calendarserver.tools.purge import PurgePrincipalService from calendarserver.tools.shell.vfs import Folder, RootFolder from calendarserver.tools.shell.directory import findRecords, summarizeRecords, recordInfo log = Logger() class UsageError(Exception): """ Usage error. """ class UnknownArguments(UsageError): """ Unknown arguments. """ def __init__(self, arguments): UsageError.__init__(self, "Unknown arguments: %s" % (arguments,)) self.arguments = arguments class InsufficientArguments(UsageError): """ Insufficient arguments. """ def __init__(self): UsageError.__init__(self, "Insufficient arguments.") class CommandsBase(object): """ Base class for commands. @ivar protocol: a protocol for parsing the incoming command line. @type protocol: L{calendarserver.tools.shell.terminal.ShellProtocol} """ def __init__(self, protocol): self.protocol = protocol self.wd = RootFolder(protocol.service) @property def terminal(self): return self.protocol.terminal # # Utilities # def documentationForCommand(self, command): """ @return: the documentation for the given C{command} as a string. """ m = getattr(self, "cmd_%s" % (command,), None) if m: doc = m.__doc__.split("\n") # Throw out first and last line if it's empty if doc: if not doc[0].strip(): doc.pop(0) if not doc[-1].strip(): doc.pop() if doc: # Get length of indentation i = len(doc[0]) - len(doc[0].lstrip()) result = [] for line in doc: result.append(line[i:]) return "\n".join(result) else: self.terminal.write("(No documentation available for %s)\n" % (command,)) else: raise NotFoundError("Unknown command: %s" % (command,)) def getTarget(self, tokens, wdFallback=False): """ Pop's the first token from tokens and locates the File indicated by that token. @return: a C{File}. """ if tokens: return self.wd.locate(tokens.pop(0).split("/")) else: if wdFallback: return succeed(self.wd) else: return succeed(None) @inlineCallbacks def getTargets(self, tokens, wdFallback=False): """ For each given C{token}, locate a File to operate on. @return: iterable of C{File} objects. """ if tokens: result = [] for token in tokens: try: target = (yield self.wd.locate(token.split("/"))) except NotFoundError: raise UsageError("No such target: %s" % (token,)) result.append(target) returnValue(result) else: if wdFallback: returnValue((self.wd,)) else: returnValue(()) @inlineCallbacks def directoryRecordWithID(self, id): """ Obtains a directory record corresponding to the given C{id}. C{id} is assumed to be a record UID. For convenience, may also take the form C{type:name}, where C{type} is a record type and C{name} is a record short name. @return: an C{IDirectoryRecord} """ directory = self.protocol.service.directory record = yield directory.recordWithUID(id) if not record: # Try type:name form try: recordType, shortName = id.split(":") except ValueError: pass else: record = yield directory.recordWithShortName(recordType, shortName) returnValue(record) def commands(self, showHidden=False): """ @return: an iterable of C{(name, method)} tuples, where C{name} is the name of the command and C{method} is the method that implements it. """ for attr in dir(self): if attr.startswith("cmd_"): m = getattr(self, attr) if showHidden or not hasattr(m, "hidden"): yield (attr[4:], m) @staticmethod def complete(word, items): """ List completions for the given C{word} from the given C{items}. Completions are the remaining portions of words in C{items} that start with C{word}. For example, if C{"foobar"} and C{"foo"} are in C{items}, then C{""} and C{"bar"} are completions when C{word} C{"foo"}. @return: an iterable of completions. """ for item in items: if item.startswith(word): yield item[len(word):] def complete_commands(self, word): """ @return: an iterable of command name completions. """ def complete(showHidden): return self.complete( word, (name for name, method in self.commands(showHidden=showHidden)) ) completions = tuple(complete(False)) # If no completions are found, try hidden commands. if not completions: completions = complete(True) return completions @inlineCallbacks def complete_files(self, tokens, filter=None): """ @return: an iterable of C{File} path completions. """ if filter is None: filter = lambda item: True if tokens: token = tokens[-1] i = token.rfind("/") if i == -1: # No "/" in token base = self.wd word = token else: base = (yield self.wd.locate(token[:i].split("/"))) word = token[i + 1:] else: base = self.wd word = "" files = ( entry.toString() for entry in (yield base.list()) if filter(entry) ) if len(tokens) == 0: returnValue(files) else: returnValue(self.complete(word, files)) class Commands(CommandsBase): """ Data store commands. """ # # Basic CLI tools # def cmd_exit(self, tokens): """ Exit the shell. usage: exit """ if tokens: raise UnknownArguments(tokens) self.protocol.exit() def cmd_help(self, tokens): """ Show help. usage: help [command] """ if tokens: command = tokens.pop(0) else: command = None if tokens: raise UnknownArguments(tokens) if command: self.terminal.write(self.documentationForCommand(command)) self.terminal.nextLine() else: self.terminal.write("Available commands:\n") result = [] max_len = 0 for name, m in self.commands(): for line in m.__doc__.split("\n"): line = line.strip() if line: doc = line break else: doc = "(no info available)" if len(name) > max_len: max_len = len(name) result.append((name, doc)) format = " %%%ds - %%s\n" % (max_len,) for info in sorted(result): self.terminal.write(format % (info)) def complete_help(self, tokens): if len(tokens) == 0: return (name for name, method in self.commands()) elif len(tokens) == 1: return self.complete_commands(tokens[0]) else: return () def cmd_emulate(self, tokens): """ Emulate editor behavior. The only correct argument is: emacs Other choices include: none usage: emulate editor """ if not tokens: if self.protocol.emulate: self.terminal.write("Emulating %s.\n" % (self.protocol.emulate,)) else: self.terminal.write("Emulation disabled.\n") return editor = tokens.pop(0).lower() if tokens: raise UnknownArguments(tokens) if editor == "none": self.terminal.write("Disabling emulation.\n") editor = None elif editor in self.protocol.emulation_modes: self.terminal.write("Emulating %s.\n" % (editor,)) else: raise UsageError("Unknown editor: %s" % (editor,)) self.protocol.emulate = editor # FIXME: Need to update key registrations cmd_emulate.hidden = "incomplete" def complete_emulate(self, tokens): if len(tokens) == 0: return self.protocol.emulation_modes elif len(tokens) == 1: return self.complete(tokens[0], self.protocol.emulation_modes) else: return () def cmd_log(self, tokens): """ Enable logging. usage: log [file] """ if hasattr(self, "_logFile"): self.terminal.write("Already logging to file: %s\n" % (self._logFile,)) return if tokens: fileName = tokens.pop(0) else: fileName = "/tmp/shell.log" if tokens: raise UnknownArguments(tokens) from twisted.python.log import startLogging try: f = open(fileName, "w") except (IOError, OSError), e: self.terminal.write("Unable to open file %s: %s\n" % (fileName, e)) return startLogging(f) self._logFile = fileName cmd_log.hidden = "debug tool" def cmd_version(self, tokens): """ Print version. usage: version """ if tokens: raise UnknownArguments(tokens) self.terminal.write("%s\n" % (version,)) # # Filesystem tools # def cmd_pwd(self, tokens): """ Print working folder. usage: pwd """ if tokens: raise UnknownArguments(tokens) self.terminal.write("%s\n" % (self.wd,)) @inlineCallbacks def cmd_cd(self, tokens): """ Change working folder. usage: cd [folder] """ if tokens: dirname = tokens.pop(0) else: return if tokens: raise UnknownArguments(tokens) wd = (yield self.wd.locate(dirname.split("/"))) if not isinstance(wd, Folder): raise NotFoundError("Not a folder: %s" % (wd,)) # log.info("wd -> {wd}", wd=wd) self.wd = wd @inlineCallbacks def complete_cd(self, tokens): returnValue((yield self.complete_files( tokens, filter=lambda item: True # issubclass(item[0], Folder) ))) @inlineCallbacks def cmd_ls(self, tokens): """ List target. usage: ls [target ...] """ targets = (yield self.getTargets(tokens, wdFallback=True)) multiple = len(targets) > 0 for target in targets: entries = sorted((yield target.list()), key=lambda e: e.fileName) # # FIXME: this can be ugly if, for example, there are zillions # of entries to output. Paging would be good. # table = Table() for entry in entries: table.addRow(entry.toFields()) if multiple: self.terminal.write("%s:\n" % (target,)) if table.rows: table.printTable(self.terminal) self.terminal.nextLine() complete_ls = CommandsBase.complete_files @inlineCallbacks def cmd_info(self, tokens): """ Print information about a target. usage: info [target] """ target = (yield self.getTarget(tokens, wdFallback=True)) if tokens: raise UnknownArguments(tokens) description = (yield target.describe()) self.terminal.write(description) self.terminal.nextLine() complete_info = CommandsBase.complete_files @inlineCallbacks def cmd_cat(self, tokens): """ Show contents of target. usage: cat target [target ...] """ targets = (yield self.getTargets(tokens)) if not targets: raise InsufficientArguments() for target in targets: if hasattr(target, "text"): text = (yield target.text()) self.terminal.write(text) complete_cat = CommandsBase.complete_files @inlineCallbacks def cmd_rm(self, tokens): """ Remove target. usage: rm target [target ...] """ options, tokens = getopt(tokens, "", ["no-implicit"]) implicit = True for option, _ignore_value in options: if option == "--no-implicit": # Not in docstring; this is really dangerous. implicit = False else: raise AssertionError("We should't be here.") targets = (yield self.getTargets(tokens)) if not targets: raise InsufficientArguments() for target in targets: if hasattr(target, "delete"): target.delete(implicit=implicit) else: self.terminal.write("Can not delete read-only target: %s\n" % (target,)) cmd_rm.hidden = "Incomplete" complete_rm = CommandsBase.complete_files # # Principal tools # @inlineCallbacks def cmd_find_principals(self, tokens): """ Search for matching principals usage: find_principal search_term """ if not tokens: raise UsageError("No search term") directory = self.protocol.service.directory records = (yield findRecords(directory, tokens)) if records: self.terminal.write((yield summarizeRecords(directory, records))) else: self.terminal.write("No matching principals found.") self.terminal.nextLine() @inlineCallbacks def cmd_print_principal(self, tokens): """ Print information about a principal. usage: print_principal principal_id """ if tokens: id = tokens.pop(0) else: raise UsageError("Principal ID required") if tokens: raise UnknownArguments(tokens) directory = self.protocol.service.directory record = yield self.directoryRecordWithID(id) if record: self.terminal.write((yield recordInfo(directory, record))) else: self.terminal.write("No such principal.") self.terminal.nextLine() # # Data purge tools # @inlineCallbacks def cmd_purge_principals(self, tokens): """ Purge data associated principals. usage: purge_principals principal_id [principal_id ...] """ dryRun = True completely = False doimplicit = True directory = self.protocol.service.directory records = [] for id in tokens: record = yield self.directoryRecordWithID(id) records.append(record) if not record: self.terminal.write("Unknown UID: %s\n" % (id,)) if None in records: self.terminal.write("Aborting.\n") return if dryRun: toPurge = "to purge" else: toPurge = "purged" total = 0 for record in records: count, _ignore_assignments = (yield PurgePrincipalService.purgeUIDs( self.protocol.service.store, directory, (record.uid,), verbose=False, dryrun=dryRun, completely=completely, doimplicit=doimplicit, )) total += count self.terminal.write( "%d events %s for UID %s.\n" % (count, toPurge, record.uid) ) self.terminal.write( "%d total events %s.\n" % (total, toPurge) ) cmd_purge_principals.hidden = "incomplete" # # Sharing # @inlineCallbacks def cmd_share(self, tokens): """ Share a resource with a principal. usage: share mode principal_id target [target ...] mode: r (read) or rw (read/write) """ if len(tokens) < 3: raise InsufficientArguments() mode = tokens.pop(0) principalID = tokens.pop(0) record = yield self.directoryRecordWithID(principalID) if not record: self.terminal.write("Principal not found: %s\n" % (principalID,)) targets = yield self.getTargets(tokens) if mode == "r": mode = None elif mode == "rw": mode = None else: raise UsageError("Unknown mode: %s" % (mode,)) for _ignore_target in targets: raise NotImplementedError() cmd_share.hidden = "incomplete" # # Python prompt, for the win # def cmd_python(self, tokens): """ Switch to a python prompt. usage: python """ if tokens: raise UnknownArguments(tokens) if not hasattr(self, "_interpreter"): # Bring in some helpful local variables. from txdav.common.datastore.sql_tables import schema from twext.enterprise.dal import syntax localVariables = dict( self=self, store=self.protocol.service.store, schema=schema, ) # FIXME: Use syntax.__all__, which needs to be defined for key, value in syntax.__dict__.items(): if not key.startswith("_"): localVariables[key] = value class Handler(object): def addOutput(innerSelf, bytes, async=False): # @NoSelf """ This is a delegate method, called by ManholeInterpreter. """ if async: self.terminal.write("... interrupted for Deferred ...\n") self.terminal.write(bytes) if async: self.terminal.write("\n") self.protocol.drawInputLine() self._interpreter = ManholeInterpreter(Handler(), localVariables) def evalSomePython(line): if line == "exit": # Return to normal command mode. del self.protocol.lineReceived del self.protocol.ps try: del self.protocol.pn except AttributeError: pass self.protocol.drawInputLine() return more = self._interpreter.push(line) self.protocol.pn = bool(more) lw = self.terminal.lastWrite if not (lw.endswith("\n") or lw.endswith("\x1bE")): self.terminal.write("\n") self.protocol.drawInputLine() self.protocol.lineReceived = evalSomePython self.protocol.ps = (">>> ", "... ") cmd_python.hidden = "debug tool" # # SQL prompt, for not as winning # def cmd_sql(self, tokens): """ Switch to an SQL prompt. usage: sql """ if tokens: raise UnknownArguments(tokens) raise NotImplementedError("Command not implemented") cmd_sql.hidden = "not implemented" # # Test tools # def cmd_raise(self, tokens): """ Raises an exception. usage: raise [message ...] """ raise RuntimeError(" ".join(tokens)) cmd_raise.hidden = "test tool" def cmd_reload(self, tokens): """ Reloads code. usage: reload """ if tokens: raise UnknownArguments(tokens) import calendarserver.tools.shell.vfs reload(calendarserver.tools.shell.vfs) import calendarserver.tools.shell.directory reload(calendarserver.tools.shell.directory) self.protocol.reloadCommands() cmd_reload.hidden = "test tool" def cmd_xyzzy(self, tokens): """ """ self.terminal.write("Nothing happens.") self.terminal.nextLine() cmd_sql.hidden = ""