Skip to content

Commit

Permalink
feat(ZNTA-2729): release RPM in Zanata_Team Repo
Browse files Browse the repository at this point in the history
  • Loading branch information
definite committed Sep 3, 2018
1 parent 9b525af commit c32bba7
Show file tree
Hide file tree
Showing 6 changed files with 648 additions and 64 deletions.
74 changes: 62 additions & 12 deletions ZanataArgParser.py
Expand Up @@ -15,7 +15,7 @@
import re
import sys

from argparse import ArgumentParser, ArgumentError
from argparse import ArgumentParser, ArgumentError, RawDescriptionHelpFormatter
# Following are for mypy
from argparse import Action # noqa: F401 # pylint: disable=W0611
from argparse import Namespace # noqa: F401 # pylint: disable=W0611
Expand All @@ -30,6 +30,20 @@
sys.stderr.write("python typing module is not installed" + os.linesep)


class NoSuchMethodError(Exception):
"""Method does not exist
Args:
method_name (str): Name of the method
"""
def __init__(self, method_name):
super(NoSuchMethodError, self).__init__()
self.method_name = method_name

def __str__(self):
return "No such method: %s" % self.method_name


class ColoredFormatter(logging.Formatter):
"""Log colored formated
Inspired from KurtJacobson's colored_log.py"""
Expand Down Expand Up @@ -93,10 +107,30 @@ def format(self, record):


class ZanataArgParser(ArgumentParser):
"""Zanata Argument Parser"""
"""Zanata Argument Parser that support sub-commands and environment
Examples:
>>> import ZanataArgParser
>>> parser = ZanataArgParser.ZanataArgParser('my-prog')
>>> parser.add_common_argument('-b', '--branch', default='master')
>>> parser.add_sub_command('list', None, None)
>>> args = parser.parse_all(['list', '-b', 'release'])
>>> print(args.sub_command)
list
>>> print(args.branch)
release
>>> args2 = parser.parse_all(['list'])
>>> print(args2.branch)
master
"""

def __init__(self, *args, **kwargs):
# type: (Any, Any) -> None
super(ZanataArgParser, self).__init__(*args, **kwargs)
# Ignore mypy "ArgumentParser" gets multiple values for keyword
# argument "formatter_class"
# See https://github.com/python/mypy/issues/1028
super(ZanataArgParser, self).__init__(
*args, formatter_class=RawDescriptionHelpFormatter, **kwargs)
self.env_def = {} # type: Dict[str, dict]
self.parent_parser = ArgumentParser(add_help=False)
self.add_argument(
Expand All @@ -105,7 +139,7 @@ def __init__(self, *args, **kwargs):
help='Valid values: %s'
% 'DEBUG, INFO, WARNING, ERROR, CRITICAL, NONE')

self.sub_parsers = None
self.sub_parsers = None # type: _SubParsersAction
self.sub_command_obj_dict = {} # type: Dict[str, Any]

def add_common_argument(self, *args, **kwargs):
Expand All @@ -117,17 +151,15 @@ def add_common_argument(self, *args, **kwargs):
self.parent_parser.add_argument(*args, **kwargs)

def add_sub_command(self, name, arguments, obj=None, **kwargs):
# type: (str, List, Any, Any) -> ArgumentParser
# type: (str, List, Any, Any) -> None
"""Add a sub command
Args:
name (str): name of the sub-command
arguments (dict): argments to be passed to argparse.add_argument()
obj (Any, optional): Defaults to None. The sub_command is
a method of the obj.
Returns:
[type]: [description]
kwargs (Any, optional): other arguments for add_parser
"""
if not self.sub_parsers:
self.sub_parsers = self.add_subparsers(
Expand All @@ -153,7 +185,6 @@ def add_sub_command(self, name, arguments, obj=None, **kwargs):
else:
anonymous_parser.add_argument(*k.split())
anonymous_parser.set_defaults(sub_command=name)
return anonymous_parser

def add_env( # pylint: disable=too-many-arguments
self, env_name,
Expand Down Expand Up @@ -181,12 +212,12 @@ def add_env( # pylint: disable=too-many-arguments
'dest': dest,
'sub_commands': sub_commands}

def add_methods_as_subcommands(self, obj, name_pattern='.*'):
def add_methods_as_sub_commands(self, obj, name_pattern='.*'):
# type (Any, str) -> None
"""Add public methods as sub-commands
Args:
obj ([type]): Public methods of obj will be used
cls ([type]): Public methods of obj will be used
name_pattern (str, optional): Defaults to '.*'.
Method name should match the pattern.
"""
Expand All @@ -205,6 +236,11 @@ def add_methods_as_subcommands(self, obj, name_pattern='.*'):
if not inspect.ismethod(m_obj) and not inspect.isfunction(m_obj):
continue

if name == 'init_from_parsed_args':
# init_from_parsed_args initialize from parsed args.
# No need to be in sub-commands
continue

argspec = inspect.getargspec(m_obj)
sub_args = None
try:
Expand All @@ -229,7 +265,9 @@ def add_methods_as_subcommands(self, obj, name_pattern='.*'):
name,
sub_args,
obj,
help=obj.__doc__)
help=re.sub(
"\n.*$", "", m_obj.__doc__, flags=re.MULTILINE),
description=m_obj.__doc__)

def has_common_argument(self, option_string=None, dest=None):
# type: (str, str) -> bool
Expand Down Expand Up @@ -359,6 +397,13 @@ def run_sub_command(self, args=None):
"sub-command %s is not associated with any object" %
args.sub_command)
obj = self.sub_command_obj_dict[args.sub_command]
if inspect.isclass(obj):
cls = obj
if not hasattr(cls, 'init_from_parsed_args'):
raise NoSuchMethodError('init_from_parsed_args')
# New an object accordingto args
obj = getattr(cls, 'init_from_parsed_args')(args)

sub_cmd_obj = getattr(obj, args.sub_command)
argspec = inspect.getargspec(sub_cmd_obj)
arg_values = []
Expand All @@ -370,6 +415,11 @@ def run_sub_command(self, args=None):


if __name__ == '__main__':
if os.getenv("PY_DOCTEST", "0") == "1":
import doctest
test_result = doctest.testmod()
print(doctest.testmod(), file=sys.stderr)
sys.exit(0 if test_result.failed == 0 else 1)
print("Legend of log levels", file=sys.stderr)
ZanataArgParser('parser').parse_args(["-v", "DEBUG"])
logging.debug("debug")
Expand Down
143 changes: 91 additions & 52 deletions ZanataFunctions.py
Expand Up @@ -30,7 +30,19 @@

def read_env(filename):
# type (str) -> dict
"""Read environment variables by sourcing a bash file"""
"""Read environment variables by sourcing a bash file
Args:
filename (str): Bash file to be read
Returns:
[dict]: Dict whose key is environment variable name,
and value is variable value.
Examples:
>>> read_env('zanata-env.sh')['EXIT_OK']
u'0'
"""
proc = subprocess.Popen( # nosec
[BASH_CMD, '-c',
"source %s && set -o posix && set" % (filename)],
Expand All @@ -43,18 +55,18 @@ def read_env(filename):

ZANATA_ENV = read_env(ZANATA_ENV_FILE)
if 'WORK_ROOT' in os.environ:
WORK_ROOT = os.getenv('WORK_ROOT')
WORK_ROOT = str(os.environ.get('WORK_ROOT'))
elif ZANATA_ENV['WORK_ROOT']:
WORK_ROOT = ZANATA_ENV['WORK_ROOT']
WORK_ROOT = str(ZANATA_ENV['WORK_ROOT'])
else:
WORK_ROOT = os.getcwd
WORK_ROOT = os.getcwd()


class CLIException(Exception):
"""Exception from command line"""

def __init__(self, msg, level='ERROR'):
super(CLIException).__init__(type(self))
super(CLIException, self).__init__(type(self))
self.msg = "[%s] %s" % (level, msg)

def __str__(self):
Expand All @@ -75,16 +87,31 @@ def __init__(
# type: (str, str, str, str) -> None
self.user = user
self.token = token
url_parsed = urlparse.urlparse(url)

parsed = urlparse.urlsplit(url)
data = list(parsed)

# replace if user is specify
if user:
url_parsed.username = user
if token:
url_parsed.password = token
userrec = user
if token:
userrec += ":" + token
netloc = "%s@%s" % (userrec, parsed.hostname)
data[1] = netloc

self.url = url
self.auth_url = urlparse.urlunparse(url_parsed)
self.auth_url = urlparse.urlunparse(data)
self.remote = remote

@classmethod
def init_from_parsed_args(cls, args):
"""Init from command line arguments"""
kwargs = {}
for k in ['user', 'token', 'url', 'remote']:
if hasattr(args, k):
kwargs[k] = getattr(args, k)
return cls(**kwargs)

@staticmethod
def git_check_output(arg_list, **kwargs):
"""Run git command and return stdout as string
Expand All @@ -102,7 +129,7 @@ def git_check_output(arg_list, **kwargs):
"""
cmd_list = [GitHelper.GIT_CMD] + arg_list
logging.debug("Running command: %s", " ".join(cmd_list))
return subprocess.check_output(cmd_list, **kwargs)
return subprocess.check_output(cmd_list, **kwargs).rstrip()

@staticmethod
def branch_get_current():
Expand Down Expand Up @@ -182,6 +209,11 @@ class SshHost(object):

SCP_CMD = '/usr/bin/scp'
SSH_CMD = '/usr/bin/ssh'
RSYNC_CMD = '/usr/bin/rsync'
RSYNC_OPTIONS = [
'--cvs-exclude', '--recursive', '--verbose', '--links',
'--update', '--compress', '--exclude', '*.core', '--stats',
'--progress', '--archive', '--keep-dirlinks']

def __init__(self, host, ssh_user=None, identity_file=None):
# type (str, str, str) -> None
Expand Down Expand Up @@ -219,7 +251,7 @@ def add_parser(cls, arg_parser=None):
def init_from_parsed_args(cls, args):
"""Init from command line arguments"""
kwargs = {'host': args.host}
for k in ['ssh_user', 'identitity_file']:
for k in ['ssh_user', 'identity_file']:
if hasattr(args, k):
kwargs[k] = getattr(args, k)
return cls(**kwargs)
Expand Down Expand Up @@ -272,6 +304,30 @@ def scp_to_host(

subprocess.check_call(cmd_list) # nosec

def rsync(self, src, dest, options=None):
# type (str, str, List[Str]) -> None
"""Run rsync
Args:
src (str): src file/dir in rsync
dest (str): src file/dir in rsync
options (List[str], optional): Defaults to None.
List of rsync options.
"""
cmd_prefix = [SshHost.RSYNC_CMD] + SshHost.RSYNC_OPTIONS
if self.ssh_user:
ssh_cmd = "ssh -l {} {} {}".format(
self.ssh_user,
"" if not self.identity_file else "-i",
"" if not self.identity_file else self.identity_file)
cmd_prefix += ["-e", ssh_cmd]

if options:
cmd_prefix += options
full_cmd = cmd_prefix + [src, dest]
logging.debug(" ".join(full_cmd))
subprocess.check_call(full_cmd)


class UrlHelper(object):
"""URL helper functions"""
Expand Down Expand Up @@ -348,16 +404,21 @@ def mkdir_p(directory, mode=0o755):


def version_sort(version_list, reverse=False):
"""Sort the version
"""Sort the version from list
Arguments:
version_list {List[str]} -- List of versions
Keyword Arguments:
reverse {bool} -- Whether to reverse sort (default: {False})
Args:
version_list (List[str]): version str in a list
reverse (bool, optional): Defaults to False. Desending sort.
Returns:
List[str] -- Sorted list of versions
Examples:
>>> version_list = ['1.0.0', '10.0.0', '2.0.0', '1.0.0-rc-1' ]
>>> version_sort(version_list)
['1.0.0-rc-1', '1.0.0', '2.0.0', '10.0.0']
>>> version_sort(version_list, True)
['10.0.0', '2.0.0', '1.0.0', '1.0.0-rc-1']
"""
# Add -zfinal to final releases, so it can be sorted after rc
sorted_dirty_version = sorted(
Expand All @@ -384,47 +445,25 @@ def working_directory(directory):
os.chdir(curr_directory)


def _parse():
def main():
"""Run as command line program"""
parser = ZanataArgParser(__file__)
parser.add_sub_command(
'list-run', None,
help='list runable functions')
parser.add_sub_command(
'run',
[
('func_name', {
'type': str, 'default': '',
'help': 'Function name'}),
('func_args', {
'type': str,
'nargs': '*',
'help': 'Function arguments'})],
help='Run function')
parser.add_methods_as_sub_commands(GitHelper)
parser.add_sub_command(
'module-help', None,
help='Show Python Module help')
return parser.parse_all()

args = parser.parse_all()

def _run_as_cli():
import inspect
args = _parse()
if args.sub_command == 'module-help':
help(sys.modules[__name__])
elif args.sub_command == 'list-run':
cmd_list = inspect.getmembers(GitHelper, predicate=inspect.ismethod)
for cmd in cmd_list:
if cmd[0][0] == '_':
continue
print("%s:\n %s\n" % (cmd[0], cmd[1].__doc__))
print(inspect.getargspec(cmd[1]))
elif args.sub_command == 'run':
if hasattr(GitHelper, args.func_name):
g_helper = GitHelper()
print(getattr(g_helper, args.func_name)(*args.func_args))
else:
raise CLIException("No known func name %s" % args.func_name)
else:
parser.run_sub_command(args)


if __name__ == '__main__':
_run_as_cli()
if os.getenv("PY_DOCTEST", "0") == "1":
import doctest
test_result = doctest.testmod()
print(doctest.testmod(), file=sys.stderr)
sys.exit(0 if test_result.failed == 0 else 1)
main()

0 comments on commit c32bba7

Please sign in to comment.