Mercurial > kallithea
changeset 4189:9793473d74be kallithea-2.2.5-rebrand
Rename helper tools (and fix inconsistent naming)
author | Bradley M. Kuhn <bkuhn@sfconservancy.org> |
---|---|
date | Wed, 02 Jul 2014 19:04:39 -0400 |
parents | 6c0215b29750 |
children | 99ad9d0af1a3 |
files | docs/api/api.rst kallithea/bin/kallithea_api.py kallithea/bin/kallithea_backup.py kallithea/bin/kallithea_config.py kallithea/bin/kallithea_gist.py kallithea/bin/ldap_sync.py kallithea/bin/rhodecode_api.py kallithea/bin/rhodecode_backup.py kallithea/bin/rhodecode_config.py kallithea/bin/rhodecode_gist.py kallithea/tests/scripts/create_rc.sh setup.py |
diffstat | 12 files changed, 589 insertions(+), 589 deletions(-) [+] |
line wrap: on
line diff
--- a/docs/api/api.rst Wed Jul 02 19:04:33 2014 -0400 +++ b/docs/api/api.rst Wed Jul 02 19:04:39 2014 -0400 @@ -79,12 +79,12 @@ ++++++++++ From version 1.4 RhodeCode adds a script that allows to easily -communicate with API. After installing RhodeCode a `rhodecode-api` script +communicate with API. After installing RhodeCode a `kallithea-api` script will be available. To get started quickly simply run:: - rhodecode-api _create_config --apikey=<youapikey> --apihost=<your.kallithea.server> + kallithea-api _create_config --apikey=<youapikey> --apihost=<your.kallithea.server> This will create a file named .config in the directory you executed it storing json config file with credentials. You can skip this step and always provide @@ -93,7 +93,7 @@ after that simply run any api command for example get_repo:: - rhodecode-api get_repo + kallithea-api get_repo calling {"api_key": "<apikey>", "id": 75, "args": {}, "method": "get_repo"} to http://127.0.0.1:5000 rhodecode said: @@ -105,7 +105,7 @@ Let's try again now giving the repoid as parameters:: - rhodecode-api get_repo repoid:rhodecode + kallithea-api get_repo repoid:rhodecode calling {"api_key": "<apikey>", "id": 39, "args": {"repoid": "rhodecode"}, "method": "get_repo"} to http://127.0.0.1:5000 rhodecode said:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/bin/kallithea_api.py Wed Jul 02 19:04:39 2014 -0400 @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +kallithea.bin.api +~~~~~~~~~~~~~~~~~ + +Api CLI client for RhodeCode + +:created_on: Jun 3, 2012 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +from __future__ import with_statement +import sys +import argparse + +from kallithea.bin.base import json, api_call, RcConf, FORMAT_JSON, FORMAT_PRETTY + + +def argparser(argv): + usage = ( + "kallithea-api [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] " + "[--config=CONFIG] [--save-config] " + "METHOD <key:val> <key2:val> ...\n" + "Create config file: kallithea-api --apikey=<key> --apihost=http://your.kallithea.server --save-config" + ) + + parser = argparse.ArgumentParser(description='RhodeCode API cli', + usage=usage) + + ## config + group = parser.add_argument_group('config') + group.add_argument('--apikey', help='api access key') + group.add_argument('--apihost', help='api host') + group.add_argument('--config', help='config file') + group.add_argument('--save-config', action='store_true', help='save the given config into a file') + + group = parser.add_argument_group('API') + group.add_argument('method', metavar='METHOD', nargs='?', type=str, default=None, + help='API method name to call followed by key:value attributes', + ) + group.add_argument('--format', dest='format', type=str, + help='output format default: `%s` can ' + 'be also `%s`' % (FORMAT_PRETTY, FORMAT_JSON), + default=FORMAT_PRETTY + ) + args, other = parser.parse_known_args() + return parser, args, other + + +def main(argv=None): + """ + Main execution function for cli + + :param argv: + """ + if argv is None: + argv = sys.argv + + conf = None + parser, args, other = argparser(argv) + + api_credentials_given = (args.apikey and args.apihost) + if args.save_config: + if not api_credentials_given: + raise parser.error('--save-config requires --apikey and --apihost') + conf = RcConf(config_location=args.config, + autocreate=True, config={'apikey': args.apikey, + 'apihost': args.apihost}) + sys.exit() + + if not conf: + conf = RcConf(config_location=args.config, autoload=True) + if not conf: + if not api_credentials_given: + parser.error('Could not find config file and missing ' + '--apikey or --apihost in params') + + apikey = args.apikey or conf['apikey'] + apihost = args.apihost or conf['apihost'] + method = args.method + + # if we don't have method here it's an error + if not method: + parser.error('Please specify method name') + + try: + margs = dict(map(lambda s: s.split(':', 1), other)) + except Exception: + sys.stderr.write('Error parsing arguments \n') + sys.exit() + if args.format == FORMAT_PRETTY: + print 'Calling method %s => %s' % (method, apihost) + + json_resp = api_call(apikey, apihost, method, **margs) + error_prefix = '' + if json_resp['error']: + error_prefix = 'ERROR:' + json_data = json_resp['error'] + else: + json_data = json_resp['result'] + if args.format == FORMAT_JSON: + print json.dumps(json_data) + elif args.format == FORMAT_PRETTY: + print 'Server response \n%s%s' % ( + error_prefix, json.dumps(json_data, indent=4, sort_keys=True) + ) + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/bin/kallithea_backup.py Wed Jul 02 19:04:39 2014 -0400 @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +kallithea.bin.backup_manager +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Repositories backup manager, it allows to backups all +repositories and send it to backup server using RSA key via ssh. + +:created_on: Feb 28, 2010 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +import os +import sys + +import logging +import tarfile +import datetime +import subprocess + +logging.basicConfig(level=logging.DEBUG, + format="%(asctime)s %(levelname)-5.5s %(message)s") + + +class BackupManager(object): + def __init__(self, repos_location, rsa_key, backup_server): + today = datetime.datetime.now().weekday() + 1 + self.backup_file_name = "repos.%s.tar.gz" % today + + self.id_rsa_path = self.get_id_rsa(rsa_key) + self.repos_path = self.get_repos_path(repos_location) + self.backup_server = backup_server + + self.backup_file_path = '/tmp' + + logging.info('starting backup for %s', self.repos_path) + logging.info('backup target %s', self.backup_file_path) + + def get_id_rsa(self, rsa_key): + if not os.path.isfile(rsa_key): + logging.error('Could not load id_rsa key file in %s', rsa_key) + sys.exit() + return rsa_key + + def get_repos_path(self, path): + if not os.path.isdir(path): + logging.error('Wrong location for repositories in %s', path) + sys.exit() + return path + + def backup_repos(self): + bckp_file = os.path.join(self.backup_file_path, self.backup_file_name) + tar = tarfile.open(bckp_file, "w:gz") + + for dir_name in os.listdir(self.repos_path): + logging.info('backing up %s', dir_name) + tar.add(os.path.join(self.repos_path, dir_name), dir_name) + tar.close() + logging.info('finished backup of mercurial repositories') + + def transfer_files(self): + params = { + 'id_rsa_key': self.id_rsa_path, + 'backup_file': os.path.join(self.backup_file_path, + self.backup_file_name), + 'backup_server': self.backup_server + } + cmd = ['scp', '-l', '40000', '-i', '%(id_rsa_key)s' % params, + '%(backup_file)s' % params, + '%(backup_server)s' % params] + + subprocess.call(cmd) + logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4]) + + def rm_file(self): + logging.info('Removing file %s', self.backup_file_name) + os.remove(os.path.join(self.backup_file_path, self.backup_file_name)) + +if __name__ == "__main__": + + repo_location = '/home/repo_path' + backup_server = 'root@192.168.1.100:/backups/mercurial' + rsa_key = '/home/id_rsa' + + B_MANAGER = BackupManager(repo_location, rsa_key, backup_server) + B_MANAGER.backup_repos() + B_MANAGER.transfer_files() + B_MANAGER.rm_file()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/bin/kallithea_config.py Wed Jul 02 19:04:39 2014 -0400 @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +kallithea.bin.kallithea_config +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +configuration generator for RhodeCode + +:created_on: Jun 18, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + + +from __future__ import with_statement +import os +import sys +import uuid +import argparse +from mako.template import Template +TMPL = 'template.ini.mako' +here = os.path.dirname(os.path.abspath(__file__)) + +def argparser(argv): + usage = ( + "kallithea-config [-h] [--filename=FILENAME] [--template=TEMPLATE] \n" + "VARS optional specify extra template variable that will be available in " + "template. Use comma separated key=val format eg.\n" + "key1=val1,port=5000,host=127.0.0.1,elements='a\,b\,c'\n" + ) + + parser = argparse.ArgumentParser( + description='RhodeCode CONFIG generator with variable replacement', + usage=usage + ) + + ## config + group = parser.add_argument_group('CONFIG') + group.add_argument('--filename', help='Output ini filename.') + group.add_argument('--template', help='Mako template file to use instead of ' + 'the default builtin template') + group.add_argument('--raw', help='Store given mako template as raw without ' + 'parsing. Use this to create custom template ' + 'initially', action='store_true') + group.add_argument('--show-defaults', help='Show all default variables for ' + 'builtin template', action='store_true') + args, other = parser.parse_known_args() + return parser, args, other + + +def _escape_split(text, sep): + """ + Allows for escaping of the separator: e.g. arg='foo\, bar' + + It should be noted that the way bash et. al. do command line parsing, those + single quotes are required. a shameless ripoff from fabric project. + + """ + escaped_sep = r'\%s' % sep + + if escaped_sep not in text: + return text.split(sep) + + before, _, after = text.partition(escaped_sep) + startlist = before.split(sep) # a regular split is fine here + unfinished = startlist[-1] + startlist = startlist[:-1] + + # recurse because there may be more escaped separators + endlist = _escape_split(after, sep) + + # finish building the escaped value. we use endlist[0] becaue the first + # part of the string sent in recursion is the rest of the escaped value. + unfinished += sep + endlist[0] + + return startlist + [unfinished] + endlist[1:] # put together all the parts + +def _run(argv): + parser, args, other = argparser(argv) + if not len(sys.argv) > 1: + print parser.print_help() + sys.exit(0) + # defaults that can be overwritten by arguments + tmpl_stored_args = { + 'http_server': 'waitress', + 'lang': 'en', + 'database_engine': 'sqlite', + 'host': '127.0.0.1', + 'port': 5000, + 'error_aggregation_service': None, + } + if other: + # parse arguments, we assume only first is correct + kwargs = {} + for el in _escape_split(other[0], ','): + kv = _escape_split(el, '=') + if len(kv) == 2: + k, v = kv + kwargs[k] = v + # update our template stored args + tmpl_stored_args.update(kwargs) + + # use default that cannot be replaced + tmpl_stored_args.update({ + 'uuid': lambda: uuid.uuid4().hex, + 'here': os.path.abspath(os.curdir), + }) + if args.show_defaults: + for k,v in tmpl_stored_args.iteritems(): + print '%s=%s' % (k, v) + sys.exit(0) + try: + # built in template + tmpl_file = os.path.join(here, TMPL) + if args.template: + tmpl_file = args.template + + with open(tmpl_file, 'rb') as f: + tmpl_data = f.read() + if args.raw: + tmpl = tmpl_data + else: + tmpl = Template(tmpl_data).render(**tmpl_stored_args) + with open(args.filename, 'wb') as f: + f.write(tmpl) + print 'Wrote new config file in %s' % (os.path.abspath(args.filename)) + + except Exception: + from mako import exceptions + print exceptions.text_error_template().render() + +def main(argv=None): + """ + Main execution function for cli + + :param argv: + """ + if argv is None: + argv = sys.argv + + try: + return _run(argv) + except Exception: + raise + + +if __name__ == '__main__': + sys.exit(main(sys.argv))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kallithea/bin/kallithea_gist.py Wed Jul 02 19:04:39 2014 -0400 @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +kallithea.bin.kallithea_gist +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Gist CLI client for RhodeCode + +:created_on: May 9, 2013 +:author: marcink +:copyright: (c) 2013 RhodeCode GmbH. +:license: GPLv3, see LICENSE for more details. +""" + +from __future__ import with_statement +import os +import sys +import stat +import argparse +import fileinput + +from kallithea.bin.base import json, api_call, RcConf, FORMAT_JSON, FORMAT_PRETTY + + +def argparser(argv): + usage = ( + "kallithea-gist [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] " + "[--config=CONFIG] [--save-config] [GIST OPTIONS] " + "[filename or stdin use - for terminal stdin ]\n" + "Create config file: kallithea-gist --apikey=<key> --apihost=http://your.kallithea.server --save-config" + ) + + parser = argparse.ArgumentParser(description='RhodeCode Gist cli', + usage=usage) + + ## config + group = parser.add_argument_group('config') + group.add_argument('--apikey', help='api access key') + group.add_argument('--apihost', help='api host') + group.add_argument('--config', help='config file path DEFAULT: ~/.config/kallithea') + group.add_argument('--save-config', action='store_true', + help='save the given config into a file') + + group = parser.add_argument_group('GIST') + group.add_argument('-p', '--private', action='store_true', + help='create private Gist') + group.add_argument('-f', '--filename', + help='set uploaded gist filename, ' + 'also defines syntax highlighting') + group.add_argument('-d', '--description', help='Gist description') + group.add_argument('-l', '--lifetime', metavar='MINUTES', + help='gist lifetime in minutes, -1 (DEFAULT) is forever') + group.add_argument('--format', dest='format', type=str, + help='output format DEFAULT: `%s` can ' + 'be also `%s`' % (FORMAT_PRETTY, FORMAT_JSON), + default=FORMAT_PRETTY + ) + args, other = parser.parse_known_args() + return parser, args, other + + +def _run(argv): + conf = None + parser, args, other = argparser(argv) + + api_credentials_given = (args.apikey and args.apihost) + if args.save_config: + if not api_credentials_given: + raise parser.error('--save-config requires --apikey and --apihost') + conf = RcConf(config_location=args.config, + autocreate=True, config={'apikey': args.apikey, + 'apihost': args.apihost}) + sys.exit() + + if not conf: + conf = RcConf(config_location=args.config, autoload=True) + if not conf: + if not api_credentials_given: + parser.error('Could not find config file and missing ' + '--apikey or --apihost in params') + + apikey = args.apikey or conf['apikey'] + host = args.apihost or conf['apihost'] + DEFAULT_FILENAME = 'gistfile1.txt' + if other: + # skip multifiles for now + filename = other[0] + if filename == '-': + filename = DEFAULT_FILENAME + gist_content = '' + for line in fileinput.input('-'): + gist_content += line + else: + with open(filename, 'rb') as f: + gist_content = f.read() + + else: + filename = DEFAULT_FILENAME + gist_content = None + # little bit hacky but cross platform check where the + # stdin comes from we skip the terminal case it can be handled by '-' + mode = os.fstat(0).st_mode + if stat.S_ISFIFO(mode): + # "stdin is piped" + gist_content = sys.stdin.read() + elif stat.S_ISREG(mode): + # "stdin is redirected" + gist_content = sys.stdin.read() + else: + # "stdin is terminal" + pass + + # make sure we don't upload binary stuff + if gist_content and '\0' in gist_content: + raise Exception('Error: binary files upload is not possible') + + filename = os.path.basename(args.filename or filename) + if gist_content: + files = { + filename: { + 'content': gist_content, + 'lexer': None + } + } + + margs = dict( + lifetime=args.lifetime, + description=args.description, + gist_type='private' if args.private else 'public', + files=files + ) + + json_data = api_call(apikey, host, 'create_gist', **margs)['result'] + if args.format == FORMAT_JSON: + print json.dumps(json_data) + elif args.format == FORMAT_PRETTY: + print json_data + print 'Created %s gist %s' % (json_data['gist']['type'], + json_data['gist']['url']) + return 0 + + +def main(argv=None): + """ + Main execution function for cli + + :param argv: + """ + if argv is None: + argv = sys.argv + + try: + return _run(argv) + except Exception, e: + print e + return 1 + + +if __name__ == '__main__': + sys.exit(main(sys.argv))
--- a/kallithea/bin/ldap_sync.py Wed Jul 02 19:04:33 2014 -0400 +++ b/kallithea/bin/ldap_sync.py Wed Jul 02 19:04:39 2014 -0400 @@ -45,7 +45,7 @@ """ Request and response don't have the same UUID. """ -class RhodecodeResponseError(Exception): +class ResponseError(Exception): """ Response has an error, something went wrong with request execution. """ @@ -57,7 +57,7 @@ """ User is not a member of the target group. """ -class RhodecodeAPI(object): +class API(object): def __init__(self, url, key): self.url = url @@ -72,7 +72,7 @@ "args": args } - def rhodecode_api_post(self, method, args): + def post(self, method, args): """Send a generic API post to Rhodecode. This will generate the UUID for validation check after the @@ -92,7 +92,7 @@ raise InvalidResponseIDError("UUID does not match.") if response["error"] is not None: - raise RhodecodeResponseError(response["error"]) + raise ResponseError(response["error"]) return response["result"] @@ -102,7 +102,7 @@ "group_name": name, "active": str(active) } - self.rhodecode_api_post("create_user_group", args) + self.post("create_user_group", args) def add_membership(self, group, username): """Add specific user to a group.""" @@ -110,7 +110,7 @@ "usersgroupid": group, "userid": username } - result = self.rhodecode_api_post("add_user_to_user_group", args) + result = self.post("add_user_to_user_group", args) if not result["success"]: raise UserAlreadyInGroupError("User %s already in group %s." % (username, group)) @@ -121,7 +121,7 @@ "usersgroupid": group, "userid": username } - result = self.rhodecode_api_post("remove_user_from_user_group", args) + result = self.post("remove_user_from_user_group", args) if not result["success"]: raise UserNotInGroupError("User %s not in group %s." % (username, group)) @@ -129,7 +129,7 @@ def get_group_members(self, name): """Get the list of member usernames from a user group.""" args = {"usersgroupid": name} - members = self.rhodecode_api_post("get_user_group", args)['members'] + members = self.post("get_user_group", args)['members'] member_list = [] for member in members: member_list.append(member["username"]) @@ -138,12 +138,12 @@ def get_group(self, name): """Return group info.""" args = {"usersgroupid": name} - return self.rhodecode_api_post("get_user_group", args) + return self.post("get_user_group", args) def get_user(self, username): """Return user info.""" args = {"userid": username} - return self.rhodecode_api_post("get_user", args) + return self.post("get_user", args) class LdapClient(object): @@ -202,7 +202,7 @@ config.get("default", "ldap_user"), config.get("default", "ldap_key"), config.get("default", "base_dn")) - self.rhodocode_api = RhodecodeAPI(config.get("default", "api_url"), + self.rhodocode_api = API(config.get("default", "api_url"), config.get("default", "api_key")) def update_groups_from_ldap(self): @@ -211,7 +211,7 @@ groups = self.ldap_client.get_groups() for group in groups: try: - self.rhodecode_api.create_repo_group(group) + self.kallithea_api.create_repo_group(group) added += 1 except Exception: existing += 1 @@ -219,25 +219,25 @@ return added, existing def update_memberships_from_ldap(self, group): - """Update memberships in rhodecode based on the LDAP groups.""" + """Update memberships based on the LDAP groups.""" groups = self.ldap_client.get_groups() group_users = self.ldap_client.get_group_users(groups, group) # Delete memberships first from each group which are not part # of the group any more. - rhodecode_members = self.rhodecode_api.get_group_members(group) - for rhodecode_member in rhodecode_members: - if rhodecode_member not in group_users: + members = self.kallithea_api.get_group_members(group) + for member in members: + if member not in group_users: try: self.rhodocode_api.remove_membership(group, - rhodecode_member) + member) except UserNotInGroupError: pass # Add memberships. for member in group_users: try: - self.rhodecode_api.add_membership(group, member) + self.kallithea_api.add_membership(group, member) except UserAlreadyInGroupError: # TODO: handle somehow maybe.. pass @@ -252,5 +252,5 @@ # How should we handle this.. Either sync users as well at this step, # or just ignore those who don't exist. If we want the second case, # we need to find a way to recognize the right exception (we always get - # RhodecodeResponseError with no error code so maybe by return msg (?) + # ResponseError with no error code so maybe by return msg (?) sync.update_memberships_from_ldap(gr)
--- a/kallithea/bin/rhodecode_api.py Wed Jul 02 19:04:33 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,124 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -""" -kallithea.bin.api -~~~~~~~~~~~~~~~~~ - -Api CLI client for RhodeCode - -:created_on: Jun 3, 2012 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH. -:license: GPLv3, see LICENSE for more details. -""" - -from __future__ import with_statement -import sys -import argparse - -from kallithea.bin.base import json, api_call, RcConf, FORMAT_JSON, FORMAT_PRETTY - - -def argparser(argv): - usage = ( - "rhodecode-api [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] " - "[--config=CONFIG] [--save-config] " - "METHOD <key:val> <key2:val> ...\n" - "Create config file: rhodecode-api --apikey=<key> --apihost=http://your.kallithea.server --save-config" - ) - - parser = argparse.ArgumentParser(description='RhodeCode API cli', - usage=usage) - - ## config - group = parser.add_argument_group('config') - group.add_argument('--apikey', help='api access key') - group.add_argument('--apihost', help='api host') - group.add_argument('--config', help='config file') - group.add_argument('--save-config', action='store_true', help='save the given config into a file') - - group = parser.add_argument_group('API') - group.add_argument('method', metavar='METHOD', nargs='?', type=str, default=None, - help='API method name to call followed by key:value attributes', - ) - group.add_argument('--format', dest='format', type=str, - help='output format default: `%s` can ' - 'be also `%s`' % (FORMAT_PRETTY, FORMAT_JSON), - default=FORMAT_PRETTY - ) - args, other = parser.parse_known_args() - return parser, args, other - - -def main(argv=None): - """ - Main execution function for cli - - :param argv: - """ - if argv is None: - argv = sys.argv - - conf = None - parser, args, other = argparser(argv) - - api_credentials_given = (args.apikey and args.apihost) - if args.save_config: - if not api_credentials_given: - raise parser.error('--save-config requires --apikey and --apihost') - conf = RcConf(config_location=args.config, - autocreate=True, config={'apikey': args.apikey, - 'apihost': args.apihost}) - sys.exit() - - if not conf: - conf = RcConf(config_location=args.config, autoload=True) - if not conf: - if not api_credentials_given: - parser.error('Could not find config file and missing ' - '--apikey or --apihost in params') - - apikey = args.apikey or conf['apikey'] - apihost = args.apihost or conf['apihost'] - method = args.method - - # if we don't have method here it's an error - if not method: - parser.error('Please specify method name') - - try: - margs = dict(map(lambda s: s.split(':', 1), other)) - except Exception: - sys.stderr.write('Error parsing arguments \n') - sys.exit() - if args.format == FORMAT_PRETTY: - print 'Calling method %s => %s' % (method, apihost) - - json_resp = api_call(apikey, apihost, method, **margs) - error_prefix = '' - if json_resp['error']: - error_prefix = 'ERROR:' - json_data = json_resp['error'] - else: - json_data = json_resp['result'] - if args.format == FORMAT_JSON: - print json.dumps(json_data) - elif args.format == FORMAT_PRETTY: - print 'Server response \n%s%s' % ( - error_prefix, json.dumps(json_data, indent=4, sort_keys=True) - ) - return 0 - -if __name__ == '__main__': - sys.exit(main(sys.argv))
--- a/kallithea/bin/rhodecode_backup.py Wed Jul 02 19:04:33 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -""" -kallithea.bin.backup_manager -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Repositories backup manager, it allows to backups all -repositories and send it to backup server using RSA key via ssh. - -:created_on: Feb 28, 2010 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH. -:license: GPLv3, see LICENSE for more details. -""" - -import os -import sys - -import logging -import tarfile -import datetime -import subprocess - -logging.basicConfig(level=logging.DEBUG, - format="%(asctime)s %(levelname)-5.5s %(message)s") - - -class BackupManager(object): - def __init__(self, repos_location, rsa_key, backup_server): - today = datetime.datetime.now().weekday() + 1 - self.backup_file_name = "rhodecode_repos.%s.tar.gz" % today - - self.id_rsa_path = self.get_id_rsa(rsa_key) - self.repos_path = self.get_repos_path(repos_location) - self.backup_server = backup_server - - self.backup_file_path = '/tmp' - - logging.info('starting backup for %s', self.repos_path) - logging.info('backup target %s', self.backup_file_path) - - def get_id_rsa(self, rsa_key): - if not os.path.isfile(rsa_key): - logging.error('Could not load id_rsa key file in %s', rsa_key) - sys.exit() - return rsa_key - - def get_repos_path(self, path): - if not os.path.isdir(path): - logging.error('Wrong location for repositories in %s', path) - sys.exit() - return path - - def backup_repos(self): - bckp_file = os.path.join(self.backup_file_path, self.backup_file_name) - tar = tarfile.open(bckp_file, "w:gz") - - for dir_name in os.listdir(self.repos_path): - logging.info('backing up %s', dir_name) - tar.add(os.path.join(self.repos_path, dir_name), dir_name) - tar.close() - logging.info('finished backup of mercurial repositories') - - def transfer_files(self): - params = { - 'id_rsa_key': self.id_rsa_path, - 'backup_file': os.path.join(self.backup_file_path, - self.backup_file_name), - 'backup_server': self.backup_server - } - cmd = ['scp', '-l', '40000', '-i', '%(id_rsa_key)s' % params, - '%(backup_file)s' % params, - '%(backup_server)s' % params] - - subprocess.call(cmd) - logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4]) - - def rm_file(self): - logging.info('Removing file %s', self.backup_file_name) - os.remove(os.path.join(self.backup_file_path, self.backup_file_name)) - -if __name__ == "__main__": - - repo_location = '/home/repo_path' - backup_server = 'root@192.168.1.100:/backups/mercurial' - rsa_key = '/home/id_rsa' - - B_MANAGER = BackupManager(repo_location, rsa_key, backup_server) - B_MANAGER.backup_repos() - B_MANAGER.transfer_files() - B_MANAGER.rm_file()
--- a/kallithea/bin/rhodecode_config.py Wed Jul 02 19:04:33 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,160 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -""" -kallithea.bin.rhodecode_config -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -configuration generator for RhodeCode - -:created_on: Jun 18, 2013 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH. -:license: GPLv3, see LICENSE for more details. -""" - - -from __future__ import with_statement -import os -import sys -import uuid -import argparse -from mako.template import Template -TMPL = 'template.ini.mako' -here = os.path.dirname(os.path.abspath(__file__)) - -def argparser(argv): - usage = ( - "rhodecode-config [-h] [--filename=FILENAME] [--template=TEMPLATE] \n" - "VARS optional specify extra template variable that will be available in " - "template. Use comma separated key=val format eg.\n" - "key1=val1,port=5000,host=127.0.0.1,elements='a\,b\,c'\n" - ) - - parser = argparse.ArgumentParser( - description='RhodeCode CONFIG generator with variable replacement', - usage=usage - ) - - ## config - group = parser.add_argument_group('CONFIG') - group.add_argument('--filename', help='Output ini filename.') - group.add_argument('--template', help='Mako template file to use instead of ' - 'the default builtin template') - group.add_argument('--raw', help='Store given mako template as raw without ' - 'parsing. Use this to create custom template ' - 'initially', action='store_true') - group.add_argument('--show-defaults', help='Show all default variables for ' - 'builtin template', action='store_true') - args, other = parser.parse_known_args() - return parser, args, other - - -def _escape_split(text, sep): - """ - Allows for escaping of the separator: e.g. arg='foo\, bar' - - It should be noted that the way bash et. al. do command line parsing, those - single quotes are required. a shameless ripoff from fabric project. - - """ - escaped_sep = r'\%s' % sep - - if escaped_sep not in text: - return text.split(sep) - - before, _, after = text.partition(escaped_sep) - startlist = before.split(sep) # a regular split is fine here - unfinished = startlist[-1] - startlist = startlist[:-1] - - # recurse because there may be more escaped separators - endlist = _escape_split(after, sep) - - # finish building the escaped value. we use endlist[0] becaue the first - # part of the string sent in recursion is the rest of the escaped value. - unfinished += sep + endlist[0] - - return startlist + [unfinished] + endlist[1:] # put together all the parts - -def _run(argv): - parser, args, other = argparser(argv) - if not len(sys.argv) > 1: - print parser.print_help() - sys.exit(0) - # defaults that can be overwritten by arguments - tmpl_stored_args = { - 'http_server': 'waitress', - 'lang': 'en', - 'database_engine': 'sqlite', - 'host': '127.0.0.1', - 'port': 5000, - 'error_aggregation_service': None, - } - if other: - # parse arguments, we assume only first is correct - kwargs = {} - for el in _escape_split(other[0], ','): - kv = _escape_split(el, '=') - if len(kv) == 2: - k, v = kv - kwargs[k] = v - # update our template stored args - tmpl_stored_args.update(kwargs) - - # use default that cannot be replaced - tmpl_stored_args.update({ - 'uuid': lambda: uuid.uuid4().hex, - 'here': os.path.abspath(os.curdir), - }) - if args.show_defaults: - for k,v in tmpl_stored_args.iteritems(): - print '%s=%s' % (k, v) - sys.exit(0) - try: - # built in template - tmpl_file = os.path.join(here, TMPL) - if args.template: - tmpl_file = args.template - - with open(tmpl_file, 'rb') as f: - tmpl_data = f.read() - if args.raw: - tmpl = tmpl_data - else: - tmpl = Template(tmpl_data).render(**tmpl_stored_args) - with open(args.filename, 'wb') as f: - f.write(tmpl) - print 'Wrote new config file in %s' % (os.path.abspath(args.filename)) - - except Exception: - from mako import exceptions - print exceptions.text_error_template().render() - -def main(argv=None): - """ - Main execution function for cli - - :param argv: - """ - if argv is None: - argv = sys.argv - - try: - return _run(argv) - except Exception: - raise - - -if __name__ == '__main__': - sys.exit(main(sys.argv))
--- a/kallithea/bin/rhodecode_gist.py Wed Jul 02 19:04:33 2014 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,171 +0,0 @@ -# -*- coding: utf-8 -*- -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -""" -kallithea.bin.rhodecode_gist -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Gist CLI client for RhodeCode - -:created_on: May 9, 2013 -:author: marcink -:copyright: (c) 2013 RhodeCode GmbH. -:license: GPLv3, see LICENSE for more details. -""" - -from __future__ import with_statement -import os -import sys -import stat -import argparse -import fileinput - -from kallithea.bin.base import json, api_call, RcConf, FORMAT_JSON, FORMAT_PRETTY - - -def argparser(argv): - usage = ( - "rhodecode-gist [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] " - "[--config=CONFIG] [--save-config] [GIST OPTIONS] " - "[filename or stdin use - for terminal stdin ]\n" - "Create config file: rhodecode-gist --apikey=<key> --apihost=http://your.kallithea.server --save-config" - ) - - parser = argparse.ArgumentParser(description='RhodeCode Gist cli', - usage=usage) - - ## config - group = parser.add_argument_group('config') - group.add_argument('--apikey', help='api access key') - group.add_argument('--apihost', help='api host') - group.add_argument('--config', help='config file path DEFAULT: ~/.config/kallithea') - group.add_argument('--save-config', action='store_true', - help='save the given config into a file') - - group = parser.add_argument_group('GIST') - group.add_argument('-p', '--private', action='store_true', - help='create private Gist') - group.add_argument('-f', '--filename', - help='set uploaded gist filename, ' - 'also defines syntax highlighting') - group.add_argument('-d', '--description', help='Gist description') - group.add_argument('-l', '--lifetime', metavar='MINUTES', - help='gist lifetime in minutes, -1 (DEFAULT) is forever') - group.add_argument('--format', dest='format', type=str, - help='output format DEFAULT: `%s` can ' - 'be also `%s`' % (FORMAT_PRETTY, FORMAT_JSON), - default=FORMAT_PRETTY - ) - args, other = parser.parse_known_args() - return parser, args, other - - -def _run(argv): - conf = None - parser, args, other = argparser(argv) - - api_credentials_given = (args.apikey and args.apihost) - if args.save_config: - if not api_credentials_given: - raise parser.error('--save-config requires --apikey and --apihost') - conf = RcConf(config_location=args.config, - autocreate=True, config={'apikey': args.apikey, - 'apihost': args.apihost}) - sys.exit() - - if not conf: - conf = RcConf(config_location=args.config, autoload=True) - if not conf: - if not api_credentials_given: - parser.error('Could not find config file and missing ' - '--apikey or --apihost in params') - - apikey = args.apikey or conf['apikey'] - host = args.apihost or conf['apihost'] - DEFAULT_FILENAME = 'gistfile1.txt' - if other: - # skip multifiles for now - filename = other[0] - if filename == '-': - filename = DEFAULT_FILENAME - gist_content = '' - for line in fileinput.input('-'): - gist_content += line - else: - with open(filename, 'rb') as f: - gist_content = f.read() - - else: - filename = DEFAULT_FILENAME - gist_content = None - # little bit hacky but cross platform check where the - # stdin comes from we skip the terminal case it can be handled by '-' - mode = os.fstat(0).st_mode - if stat.S_ISFIFO(mode): - # "stdin is piped" - gist_content = sys.stdin.read() - elif stat.S_ISREG(mode): - # "stdin is redirected" - gist_content = sys.stdin.read() - else: - # "stdin is terminal" - pass - - # make sure we don't upload binary stuff - if gist_content and '\0' in gist_content: - raise Exception('Error: binary files upload is not possible') - - filename = os.path.basename(args.filename or filename) - if gist_content: - files = { - filename: { - 'content': gist_content, - 'lexer': None - } - } - - margs = dict( - lifetime=args.lifetime, - description=args.description, - gist_type='private' if args.private else 'public', - files=files - ) - - json_data = api_call(apikey, host, 'create_gist', **margs)['result'] - if args.format == FORMAT_JSON: - print json.dumps(json_data) - elif args.format == FORMAT_PRETTY: - print json_data - print 'Created %s gist %s' % (json_data['gist']['type'], - json_data['gist']['url']) - return 0 - - -def main(argv=None): - """ - Main execution function for cli - - :param argv: - """ - if argv is None: - argv = sys.argv - - try: - return _run(argv) - except Exception, e: - print e - return 1 - - -if __name__ == '__main__': - sys.exit(main(sys.argv))
--- a/kallithea/tests/scripts/create_rc.sh Wed Jul 02 19:04:33 2014 -0400 +++ b/kallithea/tests/scripts/create_rc.sh Wed Jul 02 19:04:39 2014 -0400 @@ -6,12 +6,12 @@ echo "run those after running server" paster serve rc.ini --pid-file=rc.pid --daemon sleep 3 -rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo1 password:qweqwe email:demo1@example.com -rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo2 password:qweqwe email:demo2@example.com -rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo3 password:qweqwe email:demo3@example.com -rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user_group group_name:demo12 -rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_user_group usergroupid:demo12 userid:demo1 -rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_user_group usergroupid:demo12 userid:demo2 +kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo1 password:qweqwe email:demo1@example.com +kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo2 password:qweqwe email:demo2@example.com +kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo3 password:qweqwe email:demo3@example.com +kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user_group group_name:demo12 +kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_user_group usergroupid:demo12 userid:demo1 +kallithea-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_user_group usergroupid:demo12 userid:demo2 echo "killing server" kill `cat rc.pid` rm rc.pid
--- a/setup.py Wed Jul 02 19:04:33 2014 -0400 +++ b/setup.py Wed Jul 02 19:04:39 2014 -0400 @@ -158,9 +158,9 @@ paster_plugins=['PasteScript', 'Pylons'], entry_points=""" [console_scripts] - rhodecode-api = kallithea.bin.rhodecode_api:main - rhodecode-gist = kallithea.bin.rhodecode_gist:main - rhodecode-config = kallithea.bin.rhodecode_config:main + kallithea-api = kallithea.bin.kallithea_api:main + kallithea-gist = kallithea.bin.kallithea_gist:main + kallithea-config = kallithea.bin.kallithea_config:main [paste.app_factory] main = kallithea.config.middleware:make_app