Skip to content

Commit baacf4f

Browse files
tiransimo5
authored andcommitted
More improvements for custodia-cli error reporting
* Print command and name but not secret value * Show host or unquoted path to socket * Print inner exception for ConnectionError. It contains useful information, e.g. TLS / cert errors. $ PYTHONPATH=$(pwd) python -m custodia.cli ls / ERROR: Custodia command 'ls /' failed. Failed to connect to Unix socket '/var/run/custodia/custodia.sock': ('Connection aborted.', error(2, 'No such file or directory')) $ PYTHONPATH=$(pwd) python -m custodia.cli --server http://localhost ls / ERROR: Custodia command 'ls /' failed. Failed to connect to 'localhost' (http): HTTPConnectionPool(host='localhost', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<requests.packages.urllib3.connection.HTTPConnection object at 0x7fcb9aea2e10>: Failed to establish a new connection: [Errno 111] Connection refused',)) $ PYTHONPATH=$(pwd) python3 -m custodia.cli --server https://wrong.host.badssl.com/ ls / ERROR: Custodia command 'ls /' failed. Failed to connect to 'wrong.host.badssl.com' (https): hostname 'wrong.host.badssl.com' doesn't match either of '*.badssl.com', 'badssl.com' Closes: #131 Signed-off-by: Christian Heimes <[email protected]> Reviewed-by: Raildo Mascena <[email protected]> Closes: #144
1 parent 2260706 commit baacf4f

File tree

1 file changed

+93
-31
lines changed

1 file changed

+93
-31
lines changed

custodia/cli/__init__.py

+93-31
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,43 @@
11
# Copyright (C) 2016 Custodia Project Contributors - see LICENSE file
2+
from __future__ import absolute_import, print_function
3+
24
import argparse
35
import logging
46
import operator
57
import os
6-
import sys
78
import traceback
89

9-
try:
10-
# pylint: disable=import-error
11-
from urllib import quote as url_escape
12-
except ImportError:
13-
# pylint: disable=import-error,no-name-in-module
14-
from urllib.parse import quote as url_escape
15-
1610
import pkg_resources
1711

18-
1912
from requests.exceptions import ConnectionError
2013
from requests.exceptions import HTTPError as RequestsHTTPError
2114

15+
import six
16+
2217
from custodia import log
2318
from custodia.client import CustodiaSimpleClient
19+
from custodia.compat import unquote, url_escape, urlparse
20+
21+
if six.PY2:
22+
from StringIO import StringIO
23+
else:
24+
from io import StringIO
25+
26+
try:
27+
from json import JSONDecodeError
28+
except ImportError:
29+
# Python <= 3.4 has no JSONDecodeError
30+
JSONDecodeError = ValueError
2431

2532

2633
log.warn_provisional(__name__)
2734

35+
# exit codes
36+
E_HTTP_ERROR = 1
37+
E_CONNECTION_ERROR = 2
38+
E_JSON_ERROR = 3
39+
E_OTHER = 100
40+
2841

2942
main_parser = argparse.ArgumentParser(
3043
prog='custodia-cli',
@@ -113,44 +126,56 @@ def handle_name_value(args):
113126
parser_create_container.add_argument('name', type=str, help='key')
114127
parser_create_container.set_defaults(
115128
func=handle_name,
116-
command='create_container')
129+
command='create_container',
130+
sub='mkdir',
131+
)
117132

118133
parser_delete_container = subparsers.add_parser(
119134
'rmdir',
120135
help='Delete a container')
121136
parser_delete_container.add_argument('name', type=str, help='key')
122137
parser_delete_container.set_defaults(
123138
func=handle_name,
124-
command='delete_container')
139+
command='delete_container',
140+
sub='rmdir',
141+
)
125142

126143
parser_list_container = subparsers.add_parser(
127144
'ls', help='List content of a container')
128145
parser_list_container.add_argument('name', type=str, help='key')
129146
parser_list_container.set_defaults(
130147
func=handle_name,
131-
command='list_container')
148+
command='list_container',
149+
sub='ls',
150+
)
132151

133152
parser_get_secret = subparsers.add_parser(
134153
'get', help='Get secret')
135154
parser_get_secret.add_argument('name', type=str, help='key')
136155
parser_get_secret.set_defaults(
137156
func=handle_name,
138-
command='get_secret')
157+
command='get_secret',
158+
sub='get',
159+
)
139160

140161
parser_set_secret = subparsers.add_parser(
141162
'set', help='Set secret')
142163
parser_set_secret.add_argument('name', type=str, help='key')
143164
parser_set_secret.add_argument('value', type=str, help='value')
144165
parser_set_secret.set_defaults(
145166
command='set_secret',
146-
func=handle_name_value)
167+
func=handle_name_value,
168+
sub='set'
169+
)
147170

148171
parser_del_secret = subparsers.add_parser(
149172
'del', help='Delete a secret')
150173
parser_del_secret.add_argument('name', type=str, help='key')
151174
parser_del_secret.set_defaults(
152175
func=handle_name,
153-
command='del_secret')
176+
command='del_secret',
177+
sub='del',
178+
)
154179

155180

156181
# plugins
@@ -176,7 +201,49 @@ def handle_plugins(args):
176201
'plugins', help='List plugins')
177202
parser_plugins.set_defaults(
178203
func=handle_plugins,
179-
command='plugins')
204+
command='plugins',
205+
sub='plugins',
206+
name=None,
207+
)
208+
209+
210+
def error_message(args, exc):
211+
out = StringIO()
212+
parts = urlparse(args.server)
213+
214+
if args.debug:
215+
traceback.print_exc(file=out)
216+
out.write('\n')
217+
218+
out.write("ERROR: Custodia command '{args.sub} {args.name}' failed.\n")
219+
if args.verbose:
220+
out.write("Custodia server '{args.server}'.\n")
221+
222+
if isinstance(exc, RequestsHTTPError):
223+
errcode = E_HTTP_ERROR
224+
out.write("{exc.__class__.__name__}: {exc}\n")
225+
elif isinstance(exc, ConnectionError):
226+
errcode = E_CONNECTION_ERROR
227+
if parts.scheme == 'http+unix':
228+
out.write("Failed to connect to Unix socket '{unix_path}':\n")
229+
else:
230+
out.write("Failed to connect to '{parts.netloc}' "
231+
"({parts.scheme}):\n")
232+
# ConnectionError always contains an inner exception
233+
out.write(" {exc.args[0]}\n")
234+
elif isinstance(exc, JSONDecodeError):
235+
errcode = E_JSON_ERROR
236+
out.write("Server returned invalid JSON response:\n")
237+
out.write(" {exc}\n")
238+
else:
239+
errcode = E_OTHER
240+
out.write("{exc.__class__.__name__}: {exc}\n")
241+
242+
msg = out.getvalue()
243+
if not msg.endswith('\n'):
244+
msg += '\n'
245+
return errcode, msg.format(args=args, exc=exc, parts=parts,
246+
unix_path=unquote(parts.netloc))
180247

181248

182249
def main():
@@ -206,23 +273,18 @@ def main():
206273
if args.certfile:
207274
args.client_conn.set_client_cert(args.certfile, args.keyfile)
208275
args.client_conn.headers['CUSTODIA_CERT_AUTH'] = 'true'
276+
209277
try:
210278
result = args.func(args)
211-
except RequestsHTTPError as e:
212-
return main_parser.exit(1, str(e))
213-
except ConnectionError:
214-
connection_error_msg = "Unable to connect to the server via " \
215-
"{}".format(args.server)
216-
return main_parser.exit(2, connection_error_msg)
217-
except Exception as e: # pylint: disable=broad-except
218-
if args.verbose:
219-
traceback.print_exc(file=sys.stderr)
220-
return main_parser.exit(100, str(e))
221-
if result is not None:
222-
if isinstance(result, list):
223-
print('\n'.join(result))
224-
else:
225-
print(result)
279+
except BaseException as e:
280+
errcode, msg = error_message(args, e)
281+
main_parser.exit(errcode, msg)
282+
else:
283+
if result is not None:
284+
if isinstance(result, list):
285+
print('\n'.join(result))
286+
else:
287+
print(result)
226288

227289

228290
if __name__ == '__main__':

0 commit comments

Comments
 (0)