'''
Created on Dec 27, 2017
@author: Charlie McLouth
'''
from p4rest.p4 import P4Exception
from p4rest.exceptions import P4RestCommandError
import logging
logger = logging.getLogger(__name__)
class Command(object):
'''documentation'''
CT_NONE = 0
CT_RUN = 1
CT_FETCH = 2
CT_SAVE = 3
CT_UPDATE = 4
CT_DELETE = 5
CT_ITERATE = 6
CT_PARSE = 7
CT_FORMAT = 8
CT_FIRST = CT_RUN
CT_LAST = CT_FORMAT
CT_NAME = {"run": CT_RUN,
"fetch": CT_FETCH,
"save": CT_SAVE,
"update": CT_UPDATE,
"delete": CT_DELETE,
"parse": CT_PARSE,
"format": CT_FORMAT,
"iterate": CT_ITERATE}
# reverse lookup
CT_LOOKUP = dict((y,x) for x,y in CT_NAME.items())
# unsupported commands
UNSUPPORTED_COMMANDS = set(["run_login", "run_logut", "run_tickets", "run_init",
"run_clone"])
UNSUPPORTED_COMMANDTYPES = set([CT_PARSE, CT_FORMAT])
def __init__(self, p4api, command=None):
logger.debug("__init__ enter")
self.p4api = p4api
self.command = command
logger.debug("__init__ exit")
def __run(self, command, *args, **kwargs):
'''
return the results of a p4 command
'''
runFunc = getattr(self.p4api, "run_{}".format(command))
p4result = runFunc(*args, **kwargs)
# if list of one element convert to just the element
# if isinstance(p4result, list) and len(p4result) == 1:
# p4result = p4result[0]
return p4result
def __fetch(self, spec, *args, **kwargs):
'''
returns spec
'''
# note that we cannot use the P4().fetch_*() command because it assumes
# that the first parameter is the name. But it needs to be the last.
runFunc = getattr(self.p4api, "run_{}".format(spec))
# args[-1] is expected to be the itemname
# args is a tuple convert to list
if isinstance(args, tuple):
args = list(args)
# ignore if the itemname is new
if args[-1] == "new":
args[-1] = "-o"
else:
args.insert(len(args) - 1, "-o")
p4result = runFunc(*args, **kwargs)[0]
return p4result
def __save(self, spec, *args, **kwargs):
'''
returns a list of two elements, with the first element being the result
of the save and the second element is the saved spec
'''
saveFunc = getattr(self.p4api, "save_{}".format(spec))
# args[-1] is expected to be a dict
p4result1 = saveFunc(*args, **kwargs)[0]
# <spec> <specname> created|updated.
itemname = p4result1.split(maxsplit=2)[1]
# fetch again
fetchFunc = getattr(self.p4api, "fetch_{}".format(spec))
p4result2 = fetchFunc(itemname)
return [p4result1, p4result2]
def __update(self, spec, *args, **kwargs):
'''
returns a list of two elements, with the first element being the result
of the save and the second element is the saved spec
raises P4Exception if spec doesn't exist
'''
# args[-1] is expected to be a dict
# args[-2] is expected to be the itemname
# args is a tuple convert to list
if isinstance(args, tuple):
args = list(args)
dictdata = args.pop()
itemname = args.pop()
# validate that the spec actually exists. We use an undocumented
# option --exists
runFunc = getattr(self.p4api, "run_{}".format(spec))
fetchFunc = getattr(self.p4api, "fetch_{}".format(spec))
saveFunc = getattr(self.p4api, "save_{}".format(spec))
# the following raises an exception if it doesn't exist
specdata = runFunc("--exists", "-o", itemname)[0]
specdata.update(dictdata)
# add the dict as the first argument
args.insert(0, specdata)
p4result1 = saveFunc(*args, **kwargs)[0]
p4result2 = fetchFunc(itemname)
return [p4result1, p4result2]
def __delete(self, spec, *args, **kwargs):
'''
returns a list of two elements, with the first element being the result
of the delete and the second element is the spec before the delete
raises P4Exception if spec doesn't exist
'''
# validate that the spec actually exists. We use an undocumented
# option --exists
runFunc = getattr(self.p4api, "run_{}".format(spec))
# args[-1] is expected to be the itemname
itemname = args[-1]
# fetch first
# the following raises an exception if it doesn't exist
# Client 'badclient' doesn't exist.
p4result2 = runFunc("--exists", "-o", itemname)[0]
# delete
deleteFunc = getattr(self.p4api, "delete_{}".format(spec))
p4result1 = deleteFunc(*args, **kwargs)[0]
return [p4result1, p4result2]
def __iterate(self, specs, *args, **kwargs):
'''returns list of specs'''
iterateCommand = "iterate_{}".format(specs)
iterateFunc = getattr(self.p4api, iterateCommand)
p4result = []
for spec in iterateFunc(*args, **kwargs):
if isinstance(spec, list) and len(spec) == 1:
p4result.append(spec[0])
else:
p4result.append(spec)
return p4result
def __validateCommand(self, command=None):
'''
validates a command and returns a numeric commandtype
raises P4RestCommandError on error
'''
logger.debug("__validateCommand enter")
commandtype = Command.CT_NONE
if command is not None:
self.command = command
if self.p4api is None:
reason = "p4api not set"
raise P4RestCommandError(400, reason)
if self.command is None or not isinstance(self.command, str) \
or len(self.command) < 1:
reason = "command not set"
raise P4RestCommandError(404, reason)
# commands must be in the form of run_, fetch_, save_, update_, delete_,
# parse_, or format_, iterate_
for prefix in Command.CT_NAME.keys():
if self.command.startswith(prefix + "_") and \
len(self.command[len(prefix) + 1:]) > 0:
commandtype = Command.CT_NAME[prefix]
break
if commandtype < Command.CT_FIRST \
or commandtype > Command.CT_LAST:
reason = "unknown command ({!s})".format(self.command)
raise P4RestCommandError(404, reason)
# unsupported command types
elif commandtype in Command.UNSUPPORTED_COMMANDTYPES or self.command in Command.UNSUPPORTED_COMMANDS:
reason = "unsupported command ({})".format(self.command)
raise P4RestCommandError(404, reason)
# spec related/ validate the spectype or spectype plural
elif commandtype in [Command.CT_SAVE,
Command.CT_UPDATE,
Command.CT_DELETE,
Command.CT_FETCH,
Command.CT_ITERATE]:
spectype = self.command[len(Command.CT_LOOKUP[commandtype]) + 1:]
if commandtype == Command.CT_ITERATE:
if spectype not in self.p4api.specfields:
reason = "unknown command ({!s})".format(self.command)
raise P4RestCommandError(404, reason)
else:
bSpecValidated = False
for specname, unused in self.p4api.specfields.values():
bSpecValidated = spectype == specname
if bSpecValidated:
break
if not bSpecValidated:
reason = "unknown command ({!s})".format(self.command)
raise P4RestCommandError(404, reason)
# run_spec plural commands are disallowed
elif commandtype == Command.CT_RUN:
subcommand = self.command[len(Command.CT_LOOKUP[commandtype]) + 1:]
# if subcommand in self.p4api.specfields:
# reason = "unsupported command ({})".format(self.command)
# raise P4RestCommandError(404, reason)
for specname, unused in self.p4api.specfields.values():
if subcommand == specname:
reason = "unsupported command ({})".format(self.command)
raise P4RestCommandError(404, reason)
logger.debug("__validateCommand exit")
return commandtype
def __validateCommandArgs(self, commandtype, *args, **kwargs):
'''
validates arguments for a commandtype
raises exception if validation fails
'''
logger.debug("__validateCommandArgs enter")
# assume command has already been validated by __validateCommand
# attempt to validate arguments
#
# disallow run_spec commands if they include -o or -i arguments
if commandtype == Command.CT_RUN:
subcommand = self.command[len(Command.CT_LOOKUP[commandtype]) + 1:]
if subcommand in self.p4api.specfields:
for arg in args:
if isinstance(arg, str) and arg in ["-i", "-o"]:
reason = "unsupported command ({})".format(self.command)
raise P4RestCommandError(404, reason)
# save_spec - requires at least one argument and at most two.
# the first argument must be a dict. if two arguments provided the
# second can only be -f
elif commandtype == Command.CT_SAVE:
if len(args) < 1:
reason = "command ({}) expects at least one argument of type"\
" dict".format(self.command)
raise P4RestCommandError(400, reason)
elif not isinstance(args[0], dict) or len(args[0]) < 1:
reason = "command ({}) expects first argument to be of dict"\
" type".format(self.command)
raise P4RestCommandError(400, reason)
# update_spec - the last argument must be a dict and second to last is
# the specitem name
elif commandtype == Command.CT_UPDATE:
if len(args) < 2:
reason = "command ({}) expects at least two "\
"arguments".format(self.command)
raise P4RestCommandError(404, reason)
elif not isinstance(args[-2], (str, int)):
reason = "command ({}) expects second to last argument to be"\
" of simple type".format(self.command)
raise P4RestCommandError(404, reason)
elif isinstance(args[-2], str) and len(args[-2]) < 1:
reason = "command ({}) expects second to last argument to be"\
" of simple type".format(self.command)
raise P4RestCommandError(404, reason)
elif not isinstance(args[-1], dict) or len(args[-1]) < 1:
reason = "command ({}) expects last argument to be of dict"\
" type".format(self.command)
raise P4RestCommandError(400, reason)
# delete_spec or fetch_spec - the last argument is specitem name
elif commandtype in [Command.CT_DELETE,
Command.CT_FETCH]:
if len(args) < 1:
reason = "command ({}) expects at least one argument of "\
"simple type".format(self.command)
raise P4RestCommandError(404, reason)
elif not isinstance(args[-1], (str, int)):
reason = "command ({}) expects last argument to be"\
" of simple type".format(self.command)
raise P4RestCommandError(404, reason)
elif isinstance(args[-1], str) and len(args[-1]) < 1:
reason = "command ({}) expects last argument to be"\
" of simple type".format(self.command)
raise P4RestCommandError(404, reason)
# save_spec and update_spec are the only commands that support dict
# arguments. all other commands should only have arguments of simple
# types.
if commandtype not in [Command.CT_SAVE, Command.CT_UPDATE]:
for arg in args:
if not isinstance(arg, (str, int)):
reason = "command ({}) expects all arguments to be"\
" of simple type".format(self.command)
raise P4RestCommandError(400, reason)
logger.debug("__validateCommandArgs exit")
def executeCommandtype(self, commandtype, *args, **kwargs):
'''return dict and http status code'''
# All validation has been performed.
logger.debug("executeCommandtype enter")
result = None
p4result = None
httpstatuscode = 200
try:
subcommand = self.command[len(Command.CT_LOOKUP[commandtype]) + 1:]
if commandtype == Command.CT_RUN:
p4result = self.__run(subcommand, *args, **kwargs)
elif commandtype == Command.CT_FETCH:
p4result = self.__fetch(subcommand, *args, **kwargs)
elif commandtype == Command.CT_SAVE:
p4result = self.__save(subcommand, *args, **kwargs)
elif commandtype == Command.CT_UPDATE:
p4result = self.__update(subcommand, *args, **kwargs)
elif commandtype == Command.CT_DELETE:
p4result = self.__delete(subcommand, *args, **kwargs)
elif commandtype == Command.CT_ITERATE or \
commandtype == Command.CT_LIST:
p4result = self.__iterate(subcommand, *args, **kwargs)
except Exception as e:
if not isinstance(e, P4Exception):
# P4Exceptions are processed as p4api.errors
raise e
p4result = None
# if isinstance(p4result, list):
# if len(p4result) < 1:
# p4result = None
# elif len(p4result) == 1:
# p4result = p4result[0]
if len(self.p4api.errors) > 0 or len(self.p4api.warnings) > 0:
result = {}
if p4result is not None:
result["results"] = p4result
if len(self.p4api.errors) > 0:
result["errors"] = self.p4api.errors
logger.debug("p4api has errors:({})".format(result))
if self.p4api.errors[0].startswith("Unknown command") \
or self.p4api.errors[0].endswith("doesn't exist."):
result = None
httpstatuscode = 404
elif self.p4api.errors[0].startswith("You don't have permission for this operation"):
result = None
httpstatuscode = 401
else:
httpstatuscode = 400
if len(self.p4api.warnings) > 0:
logger.debug("p4api has warnings:({})".format(self.p4api.warnings))
if self.p4api.warnings[0].startswith("More results available"):
result["meta"] = {"more_results": True,
"next_offset": self.p4api.warnings[1]["next_offset"]}
else:
result["warnings"] = self.p4api.warnings
#no errors and no warnings
else:
result = p4result
logger.debug("executeCommandtype exit")
return result, httpstatuscode
def validateCommand(self, command=None, *args, **kwargs):
'''validate command and arguments'''
if command is not None:
self.command = command
commandtype = self.__validateCommand()
self.__validateCommandArgs(commandtype, *args, **kwargs)
return commandtype
def executeCommand(self, command=None, *args, **kwargs):
'''return dict and http status code'''
if command is not None:
self.command = command
commandtype = self.__validateCommand()
self.__validateCommandArgs(commandtype, *args, **kwargs)
return self.executeCommandtype(commandtype, *args, **kwargs)