##// 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
@@ -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 19 ^RhodeCode\.egg-info$
20 20 ^rc\.ini$
21 21 ^fabfile.py
22 ^\.rhodecode$
@@ -59,6 +59,47 b' All responses from API will be `HTTP/1.0'
59 59 calling api *error* key from response will contain failure description
60 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 103 API METHODS
63 104 +++++++++++
64 105
@@ -22,6 +22,7 b' news'
22 22 - #465 mentions autocomplete inside comments boxes
23 23 - #469 added --update-only option to whoosh to re-index only given list
24 24 of repos in index
25 - rhodecode-api CLI client
25 26
26 27 fixes
27 28 +++++
@@ -1,9 +1,9 b''
1 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 7 repositories and send it to backup server using RSA key via ssh.
8 8
9 9 :created_on: Feb 28, 2010
@@ -39,7 +39,7 b' logging.basicConfig(level=logging.DEBUG,'
39 39 class BackupManager(object):
40 40 def __init__(self, repos_location, rsa_key, backup_server):
41 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 44 self.id_rsa_path = self.get_id_rsa(rsa_key)
45 45 self.repos_path = self.get_repos_path(repos_location)
@@ -57,15 +57,16 b' class JSONRPCError(BaseException):'
57 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 62 Generate a Response object with a JSON-RPC error body
63 63 """
64 64 from pylons.controllers.util import Response
65 resp = Response(body=json.dumps(dict(id=None, result=None, error=message)),
66 status=code,
67 content_type='application/json')
68 return resp
65 return Response(
66 body=json.dumps(dict(id=retid, result=None, error=message)),
67 status=code,
68 content_type='application/json'
69 )
69 70
70 71
71 72 class JSONRPCController(WSGIController):
@@ -94,9 +95,11 b' class JSONRPCController(WSGIController):'
94 95 Parse the request body as JSON, look up the method on the
95 96 controller and if it exists, dispatch to it.
96 97 """
98 self._req_id = None
97 99 if 'CONTENT_LENGTH' not in environ:
98 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 103 else:
101 104 length = environ['CONTENT_LENGTH'] or 0
102 105 length = int(environ['CONTENT_LENGTH'])
@@ -104,7 +107,8 b' class JSONRPCController(WSGIController):'
104 107
105 108 if length == 0:
106 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 113 raw_body = environ['wsgi.input'].read(length)
110 114
@@ -112,7 +116,8 b' class JSONRPCController(WSGIController):'
112 116 json_body = json.loads(urllib.unquote_plus(raw_body))
113 117 except ValueError, e:
114 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 121 % (e, urllib.unquote_plus(raw_body)))
117 122
118 123 # check AUTH based on API KEY
@@ -126,22 +131,26 b' class JSONRPCController(WSGIController):'
126 131 self._request_params)
127 132 )
128 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 137 # check if we can find this session using api_key
132 138 try:
133 139 u = User.get_by_api_key(self._req_api_key)
134 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 143 auth_u = AuthUser(u.user_id, self._req_api_key)
137 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 148 self._error = None
141 149 try:
142 150 self._func = self._find_method()
143 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 155 # now that we have a method, add self._req_params to
147 156 # self.kargs and dispatch control to WGIController
@@ -164,9 +173,12 b' class JSONRPCController(WSGIController):'
164 173 USER_SESSION_ATTR = 'apiuser'
165 174
166 175 if USER_SESSION_ATTR not in arglist:
167 return jsonrpc_error(message='This method [%s] does not support '
168 'authentication (missing %s param)' %
169 (self._func.__name__, USER_SESSION_ATTR))
176 return jsonrpc_error(
177 retid=self._req_id,
178 message='This method [%s] does not support '
179 'authentication (missing %s param)' % (
180 self._func.__name__, USER_SESSION_ATTR)
181 )
170 182
171 183 # get our arglist and check if we provided them as args
172 184 for arg, default in func_kwargs.iteritems():
@@ -179,6 +191,7 b' class JSONRPCController(WSGIController):'
179 191 # NotImplementedType (default_empty)
180 192 if (default == default_empty and arg not in self._request_params):
181 193 return jsonrpc_error(
194 retid=self._req_id,
182 195 message=(
183 196 'Missing non optional `%s` arg in JSON DATA' % arg
184 197 )
@@ -389,7 +389,7 b' class ApiController(JSONRPCController):'
389 389
390 390 repo = RepoModel().get_repo(repoid)
391 391 if repo is None:
392 raise JSONRPCError('unknown repository %s' % repo)
392 raise JSONRPCError('unknown repository "%s"' % (repo or repoid))
393 393
394 394 members = []
395 395 for user in repo.repo_to_perm:
@@ -14,7 +14,7 b''
14 14 <td>${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}</td>
15 15 <td>${h.action_parser(l)[0]()}
16 16 <div class="journal_action_params">
17 ${h.literal(h.action_parser(l)[1]())}
17 ${h.literal(h.action_parser(l)[1]())}
18 18 </div>
19 19 </td>
20 20 <td>
@@ -87,6 +87,9 b' setup('
87 87 zip_safe=False,
88 88 paster_plugins=['PasteScript', 'Pylons'],
89 89 entry_points="""
90 [console_scripts]
91 rhodecode-api = rhodecode.bin.rhodecode_api:main
92
90 93 [paste.app_factory]
91 94 main = rhodecode.config.middleware:make_app
92 95
General Comments 0
You need to be logged in to leave comments. Login now