##// END OF EJS Templates
created rhodecode-api binary script for working with api via cli...
marcink -
r2379:7ac09514 beta
parent child Browse files
Show More
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,216 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.bin.backup_manager
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 Api CLI client for RhodeCode
7
8 :created_on: Jun 3, 2012
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26 from __future__ import with_statement
27 import os
28 import sys
29 import random
30 import urllib2
31 import pprint
32 import argparse
33
34 try:
35 from rhodecode.lib.ext_json import json
36 except ImportError:
37 try:
38 import simplejson as json
39 except ImportError:
40 import json
41
42
43 CONFIG_NAME = '.rhodecode'
44
45
46 class RcConf(object):
47 """
48 RhodeCode config for API
49
50 conf = RcConf()
51 conf['key']
52
53 """
54
55 def __init__(self, autoload=True, autocreate=False, config=None):
56 self._conf_name = CONFIG_NAME
57 self._conf = {}
58 if autocreate:
59 self.make_config(config)
60 if autoload:
61 self._conf = self.load_config()
62
63 def __getitem__(self, key):
64 return self._conf[key]
65
66 def __nonzero__(self):
67 if self._conf:
68 return True
69 return False
70
71 def __eq__(self):
72 return self._conf.__eq__()
73
74 def __repr__(self):
75 return 'RcConf<%s>' % self._conf.__repr__()
76
77 def make_config(self, config):
78 """
79 Saves given config as a JSON dump in the _conf_name location
80
81 :param config:
82 :type config:
83 """
84 with open(self._conf_name, 'wb') as f:
85 json.dump(config, f, indent=4)
86 sys.stdout.write('Updated conf\n')
87
88 def update_config(self, new_config):
89 """
90 Reads the JSON config updates it's values with new_config and
91 saves it back as JSON dump
92
93 :param new_config:
94 """
95 config = {}
96 try:
97 with open(self._conf_name, 'rb') as conf:
98 config = json.load(conf)
99 except IOError, e:
100 sys.stderr.write(str(e) + '\n')
101
102 config.update(new_config)
103 self.make_config(config)
104
105 def load_config(self):
106 """
107 Loads config from file and returns loaded JSON object
108 """
109 try:
110 with open(self._conf_name, 'rb') as conf:
111 return json.load(conf)
112 except IOError, e:
113 #sys.stderr.write(str(e) + '\n')
114 pass
115
116
117 def api_call(apikey, apihost, method=None, **kw):
118 """
119 Api_call wrapper for RhodeCode
120
121 :param apikey:
122 :param apihost:
123 :param method:
124 """
125 def _build_data(random_id):
126 """
127 Builds API data with given random ID
128
129 :param random_id:
130 :type random_id:
131 """
132 return {
133 "id": random_id,
134 "api_key": apikey,
135 "method": method,
136 "args": kw
137 }
138
139 if not method:
140 raise Exception('please specify method name !')
141 id_ = random.randrange(1, 200)
142 req = urllib2.Request('%s/_admin/api' % apihost,
143 data=json.dumps(_build_data(id_)),
144 headers={'content-type': 'text/plain'})
145 print 'calling %s to %s' % (req.get_data(), apihost)
146 ret = urllib2.urlopen(req)
147 json_data = json.loads(ret.read())
148 id_ret = json_data['id']
149 _formatted_json = pprint.pformat(json_data)
150 if id_ret == id_:
151 print 'rhodecode said:\n%s' % (_formatted_json)
152 else:
153 raise Exception('something went wrong. '
154 'ID mismatch got %s, expected %s | %s' % (
155 id_ret, id_, _formatted_json))
156
157
158 def argparser(argv):
159 usage = ("rhodecode_api [-h] [--apikey APIKEY] [--apihost APIHOST] "
160 "_create_config or METHOD <key:val> <key2:val> ...")
161
162 parser = argparse.ArgumentParser(description='RhodeCode API cli',
163 usage=usage)
164
165 ## config
166 group = parser.add_argument_group('config')
167 group.add_argument('--apikey', help='api access key')
168 group.add_argument('--apihost', help='api host')
169
170 group = parser.add_argument_group('API')
171 group.add_argument('method', metavar='METHOD', type=str,
172 help='API method name to call followed by key:value attributes',
173 )
174
175 args, other = parser.parse_known_args()
176 return parser, args, other
177
178
179 def main(argv=None):
180 """
181 Main execution function for cli
182
183 :param argv:
184 :type argv:
185 """
186 if argv is None:
187 argv = sys.argv
188
189 conf = None
190 parser, args, other = argparser(argv)
191
192 api_credentials_given = (args.apikey and args.apihost)
193 if args.method == '_create_config':
194 if not api_credentials_given:
195 raise parser.error('_create_config requires --apikey and --apihost')
196 conf = RcConf(autocreate=True, config={'apikey': args.apikey,
197 'apihost': args.apihost})
198 sys.stdout.write('Create new config in %s\n' % CONFIG_NAME)
199
200 if not conf:
201 conf = RcConf(autoload=True)
202 if not conf:
203 if not api_credentials_given:
204 parser.error('Could not find config file and missing '
205 '--apikey or --apihost in params')
206
207 apikey = args.apikey or conf['apikey']
208 host = args.apihost or conf['apihost']
209 method = args.method
210 margs = dict(map(lambda s: s.split(':', 1), other))
211
212 api_call(apikey, host, method, **margs)
213 return 0
214
215 if __name__ == '__main__':
216 sys.exit(main(sys.argv))
@@ -19,3 +19,4 b' syntax: regexp'
19 ^RhodeCode\.egg-info$
19 ^RhodeCode\.egg-info$
20 ^rc\.ini$
20 ^rc\.ini$
21 ^fabfile.py
21 ^fabfile.py
22 ^\.rhodecode$
@@ -59,6 +59,47 b' All responses from API will be `HTTP/1.0'
59 calling api *error* key from response will contain failure description
59 calling api *error* key from response will contain failure description
60 and result will be null.
60 and result will be null.
61
61
62
63 API CLIENT
64 ++++++++++
65
66 From version 1.4 RhodeCode adds a binary script that allows to easily
67 communicate with API. After installing RhodeCode a `rhodecode-api` script
68 will be available.
69
70 To get started quickly simply run::
71
72 rhodecode-api _create_config --apikey=<youapikey> --apihost=<rhodecode host>
73
74 This will create a file named .config in the directory you executed it storing
75 json config file with credentials. You can skip this step and always provide
76 both of the arguments to be able to communicate with server
77
78
79 after that simply run any api command for example get_repo::
80
81 rhodecode-api get_repo
82
83 calling {"api_key": "<apikey>", "id": 75, "args": {}, "method": "get_repo"} to http://127.0.0.1:5000
84 rhodecode said:
85 {'error': 'Missing non optional `repoid` arg in JSON DATA',
86 'id': 75,
87 'result': None}
88
89 Ups looks like we forgot to add an argument
90
91 Let's try again now giving the repoid as parameters::
92
93 rhodecode-api get_repo repoid:rhodecode
94
95 calling {"api_key": "<apikey>", "id": 39, "args": {"repoid": "rhodecode"}, "method": "get_repo"} to http://127.0.0.1:5000
96 rhodecode said:
97 {'error': None,
98 'id': 39,
99 'result': <json data...>}
100
101
102
62 API METHODS
103 API METHODS
63 +++++++++++
104 +++++++++++
64
105
@@ -22,6 +22,7 b' news'
22 - #465 mentions autocomplete inside comments boxes
22 - #465 mentions autocomplete inside comments boxes
23 - #469 added --update-only option to whoosh to re-index only given list
23 - #469 added --update-only option to whoosh to re-index only given list
24 of repos in index
24 of repos in index
25 - rhodecode-api CLI client
25
26
26 fixes
27 fixes
27 +++++
28 +++++
@@ -1,9 +1,9 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.backup_manager
3 rhodecode.bin.backup_manager
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Mercurial repositories backup manager, it allows to backups all
6 Repositories backup manager, it allows to backups all
7 repositories and send it to backup server using RSA key via ssh.
7 repositories and send it to backup server using RSA key via ssh.
8
8
9 :created_on: Feb 28, 2010
9 :created_on: Feb 28, 2010
@@ -39,7 +39,7 b' logging.basicConfig(level=logging.DEBUG,'
39 class BackupManager(object):
39 class BackupManager(object):
40 def __init__(self, repos_location, rsa_key, backup_server):
40 def __init__(self, repos_location, rsa_key, backup_server):
41 today = datetime.datetime.now().weekday() + 1
41 today = datetime.datetime.now().weekday() + 1
42 self.backup_file_name = "mercurial_repos.%s.tar.gz" % today
42 self.backup_file_name = "rhodecode_repos.%s.tar.gz" % today
43
43
44 self.id_rsa_path = self.get_id_rsa(rsa_key)
44 self.id_rsa_path = self.get_id_rsa(rsa_key)
45 self.repos_path = self.get_repos_path(repos_location)
45 self.repos_path = self.get_repos_path(repos_location)
@@ -57,15 +57,16 b' class JSONRPCError(BaseException):'
57 return str(self.message)
57 return str(self.message)
58
58
59
59
60 def jsonrpc_error(message, code=None):
60 def jsonrpc_error(message, retid=None, code=None):
61 """
61 """
62 Generate a Response object with a JSON-RPC error body
62 Generate a Response object with a JSON-RPC error body
63 """
63 """
64 from pylons.controllers.util import Response
64 from pylons.controllers.util import Response
65 resp = Response(body=json.dumps(dict(id=None, result=None, error=message)),
65 return Response(
66 status=code,
66 body=json.dumps(dict(id=retid, result=None, error=message)),
67 content_type='application/json')
67 status=code,
68 return resp
68 content_type='application/json'
69 )
69
70
70
71
71 class JSONRPCController(WSGIController):
72 class JSONRPCController(WSGIController):
@@ -94,9 +95,11 b' class JSONRPCController(WSGIController):'
94 Parse the request body as JSON, look up the method on the
95 Parse the request body as JSON, look up the method on the
95 controller and if it exists, dispatch to it.
96 controller and if it exists, dispatch to it.
96 """
97 """
98 self._req_id = None
97 if 'CONTENT_LENGTH' not in environ:
99 if 'CONTENT_LENGTH' not in environ:
98 log.debug("No Content-Length")
100 log.debug("No Content-Length")
99 return jsonrpc_error(message="No Content-Length in request")
101 return jsonrpc_error(retid=self._req_id,
102 message="No Content-Length in request")
100 else:
103 else:
101 length = environ['CONTENT_LENGTH'] or 0
104 length = environ['CONTENT_LENGTH'] or 0
102 length = int(environ['CONTENT_LENGTH'])
105 length = int(environ['CONTENT_LENGTH'])
@@ -104,7 +107,8 b' class JSONRPCController(WSGIController):'
104
107
105 if length == 0:
108 if length == 0:
106 log.debug("Content-Length is 0")
109 log.debug("Content-Length is 0")
107 return jsonrpc_error(message="Content-Length is 0")
110 return jsonrpc_error(retid=self._req_id,
111 message="Content-Length is 0")
108
112
109 raw_body = environ['wsgi.input'].read(length)
113 raw_body = environ['wsgi.input'].read(length)
110
114
@@ -112,7 +116,8 b' class JSONRPCController(WSGIController):'
112 json_body = json.loads(urllib.unquote_plus(raw_body))
116 json_body = json.loads(urllib.unquote_plus(raw_body))
113 except ValueError, e:
117 except ValueError, e:
114 # catch JSON errors Here
118 # catch JSON errors Here
115 return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \
119 return jsonrpc_error(retid=self._req_id,
120 message="JSON parse error ERR:%s RAW:%r" \
116 % (e, urllib.unquote_plus(raw_body)))
121 % (e, urllib.unquote_plus(raw_body)))
117
122
118 # check AUTH based on API KEY
123 # check AUTH based on API KEY
@@ -126,22 +131,26 b' class JSONRPCController(WSGIController):'
126 self._request_params)
131 self._request_params)
127 )
132 )
128 except KeyError, e:
133 except KeyError, e:
129 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
134 return jsonrpc_error(retid=self._req_id,
135 message='Incorrect JSON query missing %s' % e)
130
136
131 # check if we can find this session using api_key
137 # check if we can find this session using api_key
132 try:
138 try:
133 u = User.get_by_api_key(self._req_api_key)
139 u = User.get_by_api_key(self._req_api_key)
134 if u is None:
140 if u is None:
135 return jsonrpc_error(message='Invalid API KEY')
141 return jsonrpc_error(retid=self._req_id,
142 message='Invalid API KEY')
136 auth_u = AuthUser(u.user_id, self._req_api_key)
143 auth_u = AuthUser(u.user_id, self._req_api_key)
137 except Exception, e:
144 except Exception, e:
138 return jsonrpc_error(message='Invalid API KEY')
145 return jsonrpc_error(retid=self._req_id,
146 message='Invalid API KEY')
139
147
140 self._error = None
148 self._error = None
141 try:
149 try:
142 self._func = self._find_method()
150 self._func = self._find_method()
143 except AttributeError, e:
151 except AttributeError, e:
144 return jsonrpc_error(message=str(e))
152 return jsonrpc_error(retid=self._req_id,
153 message=str(e))
145
154
146 # now that we have a method, add self._req_params to
155 # now that we have a method, add self._req_params to
147 # self.kargs and dispatch control to WGIController
156 # self.kargs and dispatch control to WGIController
@@ -164,9 +173,12 b' class JSONRPCController(WSGIController):'
164 USER_SESSION_ATTR = 'apiuser'
173 USER_SESSION_ATTR = 'apiuser'
165
174
166 if USER_SESSION_ATTR not in arglist:
175 if USER_SESSION_ATTR not in arglist:
167 return jsonrpc_error(message='This method [%s] does not support '
176 return jsonrpc_error(
168 'authentication (missing %s param)' %
177 retid=self._req_id,
169 (self._func.__name__, USER_SESSION_ATTR))
178 message='This method [%s] does not support '
179 'authentication (missing %s param)' % (
180 self._func.__name__, USER_SESSION_ATTR)
181 )
170
182
171 # get our arglist and check if we provided them as args
183 # get our arglist and check if we provided them as args
172 for arg, default in func_kwargs.iteritems():
184 for arg, default in func_kwargs.iteritems():
@@ -179,6 +191,7 b' class JSONRPCController(WSGIController):'
179 # NotImplementedType (default_empty)
191 # NotImplementedType (default_empty)
180 if (default == default_empty and arg not in self._request_params):
192 if (default == default_empty and arg not in self._request_params):
181 return jsonrpc_error(
193 return jsonrpc_error(
194 retid=self._req_id,
182 message=(
195 message=(
183 'Missing non optional `%s` arg in JSON DATA' % arg
196 'Missing non optional `%s` arg in JSON DATA' % arg
184 )
197 )
@@ -389,7 +389,7 b' class ApiController(JSONRPCController):'
389
389
390 repo = RepoModel().get_repo(repoid)
390 repo = RepoModel().get_repo(repoid)
391 if repo is None:
391 if repo is None:
392 raise JSONRPCError('unknown repository %s' % repo)
392 raise JSONRPCError('unknown repository "%s"' % (repo or repoid))
393
393
394 members = []
394 members = []
395 for user in repo.repo_to_perm:
395 for user in repo.repo_to_perm:
@@ -14,7 +14,7 b''
14 <td>${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}</td>
14 <td>${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}</td>
15 <td>${h.action_parser(l)[0]()}
15 <td>${h.action_parser(l)[0]()}
16 <div class="journal_action_params">
16 <div class="journal_action_params">
17 ${h.literal(h.action_parser(l)[1]())}
17 ${h.literal(h.action_parser(l)[1]())}
18 </div>
18 </div>
19 </td>
19 </td>
20 <td>
20 <td>
@@ -87,6 +87,9 b' setup('
87 zip_safe=False,
87 zip_safe=False,
88 paster_plugins=['PasteScript', 'Pylons'],
88 paster_plugins=['PasteScript', 'Pylons'],
89 entry_points="""
89 entry_points="""
90 [console_scripts]
91 rhodecode-api = rhodecode.bin.rhodecode_api:main
92
90 [paste.app_factory]
93 [paste.app_factory]
91 main = rhodecode.config.middleware:make_app
94 main = rhodecode.config.middleware:make_app
92
95
General Comments 0
You need to be logged in to leave comments. Login now