Skip to content

Commit 1498cdf

Browse files
committed
Add customization to allow for "types" of top level args
The two included are: * parsing the jmespath --query arg * resolving the verify ssl argument (including env var lookup) This is code that otherwise every plugin would have to call, so it makes sense to do this as part of the parsing process. This is just like argparse's "type" arg, except plugins can more easily hook into this.
1 parent 10dc4d9 commit 1498cdf

9 files changed

Lines changed: 158 additions & 24 deletions

File tree

awscli/clidriver.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313
import sys
14-
import os
1514
import logging
1615

1716
import botocore.session
@@ -212,7 +211,8 @@ def _show_error(self, msg):
212211
sys.stderr.write('\n')
213212

214213
def _handle_top_level_args(self, args):
215-
self.session.emit('top-level-args-parsed', parsed_args=args)
214+
self.session.emit(
215+
'top-level-args-parsed', parsed_args=args, session=self.session)
216216
if args.profile:
217217
self.session.profile = args.profile
218218
if args.debug:
@@ -429,8 +429,6 @@ def __call__(self, args, parsed_globals):
429429
if remaining:
430430
raise UnknownArgumentError(
431431
"Unknown options: %s" % ', '.join(remaining))
432-
service_name = self._service_object.endpoint_prefix
433-
operation_name = self._operation_object.name
434432
event = 'operation-args-parsed.%s.%s' % (self._parent_name,
435433
self._name)
436434
self._emit(event, parsed_args=parsed_args,
@@ -482,8 +480,6 @@ def _create_argument_table(self):
482480
self._operation_object)
483481
arg_object.add_to_arg_table(argument_table)
484482
LOG.debug(argument_table)
485-
service_name = self._service_object.endpoint_prefix
486-
operation_name = self._operation_object.name
487483
self._emit('building-argument-table.%s.%s' % (self._parent_name,
488484
self._name),
489485
operation=self._operation_object,
@@ -518,11 +514,10 @@ def invoke(self, operation_object, parameters, parsed_globals):
518514
# for credentials so we can give a good error message.
519515
if not self._session.get_credentials():
520516
raise NoCredentialsError()
521-
verify = self._resolve_verify_var(parsed_globals.no_verify_ssl)
522517
endpoint = operation_object.service.get_endpoint(
523518
region_name=parsed_globals.region,
524519
endpoint_url=parsed_globals.endpoint_url,
525-
verify=verify)
520+
verify=parsed_globals.verify_ssl)
526521
if operation_object.can_paginate and parsed_globals.paginate:
527522
pages = operation_object.paginate(endpoint, **parameters)
528523
self._display_response(operation_object, pages,
@@ -534,14 +529,6 @@ def invoke(self, operation_object, parameters, parsed_globals):
534529
parsed_globals)
535530
return 0
536531

537-
def _resolve_verify_var(self, no_verify_ssl):
538-
verify = None
539-
if no_verify_ssl:
540-
verify = False
541-
else:
542-
verify = os.environ.get('AWS_CA_BUNDLE')
543-
return verify
544-
545532
def _display_response(self, operation, response, args):
546533
output = args.output
547534
if output is None:

awscli/customizations/datapipeline/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from awscli.argprocess import uri_param
77
from awscli.customizations.commands import BasicCommand
88
from awscli.customizations.datapipeline import translator
9-
from awscli.customizations.utils import validate_mutually_exclusive_handler
109

1110

1211
HELP_TEXT = """\
@@ -230,7 +229,7 @@ def _set_session_objects(self, parsed_globals):
230229
self.endpoint = self.service.get_endpoint(
231230
region_name=parsed_globals.region,
232231
endpoint_url=parsed_globals.endpoint_url,
233-
verify=parsed_globals.no_verify_ssl)
232+
verify=parsed_globals.verify_ssl)
234233

235234
def _parse_type_args(self, parsed_args):
236235
# TODO: give good error messages!
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
import sys
14+
import os
15+
16+
import jmespath
17+
18+
19+
ARGUMENT_RENAMES = {
20+
# Mapping of original arg to renamed arg.
21+
# The key is <service>.<operation>.argname
22+
# The first part of the key is used for event registration
23+
# so if you wanted to rename something for an entire service you
24+
# could say 'ec2.*.dry-run': 'renamed-arg-name', or if you wanted
25+
# to rename across all services you could say '*.*.dry-run': 'new-name'.
26+
'ec2.create-image.no-no-reboot': 'reboot',
27+
'ec2.*.no-egress': 'ingress',
28+
'ec2.*.no-disable-api-termination': 'enable-api-termination',
29+
}
30+
31+
32+
def register_parse_global_args(cli):
33+
cli.register('top-level-args-parsed', resolve_types)
34+
35+
36+
def resolve_types(parsed_args, **kwargs):
37+
# This emulates the "type" arg from argparse, but does so in a way
38+
# that plugins can also hook into this process.
39+
_resolve_arg(parsed_args, 'query')
40+
_resolve_arg(parsed_args, 'verify_ssl')
41+
42+
43+
def _resolve_arg(parsed_args, name):
44+
value = getattr(parsed_args, name, None)
45+
if value is not None:
46+
new_value = getattr(sys.modules[__name__], '_resolve_%s' % name)(value)
47+
setattr(parsed_args, name, new_value)
48+
49+
50+
def _resolve_query(value):
51+
try:
52+
return jmespath.compile(value)
53+
except Exception as e:
54+
raise ValueError("Bad value for --query %s: %s" % (value, str(e)))
55+
56+
57+
def _resolve_verify_ssl(value):
58+
verify = None
59+
if not value:
60+
verify = False
61+
else:
62+
verify = os.environ.get('AWS_CA_BUNDLE')
63+
return verify

awscli/data/cli.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"help": "<p>Override command's default URL with the given URL.</p>"
1212
},
1313
"no-verify-ssl": {
14-
"action": "store_true",
14+
"action": "store_false",
15+
"dest": "verify_ssl",
1516
"help": "<p>Override default behavior of verifying SSL certificates.</p>"
1617
},
1718
"no-paginate": {

awscli/formatter.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ def __call__(self, operation, response, stream=None):
5757
try:
5858
self._remove_request_id(response_data)
5959
if self._args.query is not None:
60-
expression = jmespath.compile(self._args.query)
61-
response_data = expression.search(response_data)
60+
response_data = self._args.query.search(response_data)
6261
self._format_response(operation, response_data, stream)
6362
finally:
6463
# flush is needed to avoid the "close failed in file object

awscli/handlers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from awscli.customizations.toplevelbool import register_bool_params
4343
from awscli.customizations.ec2protocolarg import register_protocol_args
4444
from awscli.customizations import datapipeline
45+
from awscli.customizations.globalargs import register_parse_global_args
4546

4647

4748
def awscli_initialize(event_handlers):
@@ -67,6 +68,7 @@ def awscli_initialize(event_handlers):
6768
ec2_add_count)
6869
event_handlers.register('building-argument-table.ec2.get-password-data',
6970
ec2_add_priv_launch_key)
71+
register_parse_global_args(event_handlers)
7072
register_pagination(event_handlers)
7173
register_secgroup(event_handlers)
7274
register_bundleinstance(event_handlers)

tests/unit/customizations/datapipeline/test_commands.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
1212
# ANY KIND, either express or implied. See the License for the specific
1313
# language governing permissions and limitations under the License.
14-
import tempfile
1514
import copy
1615
import mock
1716

@@ -183,7 +182,7 @@ class FakeParsedArgs(object):
183182
def __init__(self, **kwargs):
184183
self.endpoint_url = None
185184
self.region = None
186-
self.no_verify_ssl = None
185+
self.verify_ssl = None
187186
self.__dict__.update(kwargs)
188187

189188

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
from tests import unittest
14+
import six
15+
import mock
16+
17+
from awscli.customizations import globalargs
18+
19+
20+
class FakeParsedArgs(object):
21+
def __init__(self, **kwargs):
22+
self.__dict__.update(kwargs)
23+
24+
def __getattr__(self, arg):
25+
return None
26+
27+
28+
class TestGlobalArgsCustomization(unittest.TestCase):
29+
30+
def test_parse_query(self):
31+
parsed_args = FakeParsedArgs(query='foo.bar')
32+
globalargs.resolve_types(parsed_args)
33+
# Assert that it looks like a jmespath parsed expression.
34+
self.assertFalse(isinstance(parsed_args.query, six.string_types))
35+
self.assertTrue(hasattr(parsed_args.query, 'search'))
36+
37+
def test_parse_query_error_message(self):
38+
# Invalid jmespath expression.
39+
parsed_args = FakeParsedArgs(query='foo.bar.')
40+
with self.assertRaises(ValueError):
41+
globalargs.resolve_types(parsed_args)
42+
globalargs.resolve_types('query')
43+
44+
def test_parse_verify_ssl_default_value(self):
45+
with mock.patch('os.environ', {}):
46+
parsed_args = FakeParsedArgs(verify_ssl=True)
47+
globalargs.resolve_types(parsed_args)
48+
# None, so that botocore can apply it's default logic.
49+
self.assertIsNone(parsed_args.verify_ssl)
50+
51+
def test_parse_verify_ssl_verify_turned_off(self):
52+
with mock.patch('os.environ', {}):
53+
parsed_args = FakeParsedArgs(verify_ssl=False)
54+
globalargs.resolve_types(parsed_args)
55+
self.assertFalse(parsed_args.verify_ssl)
56+
57+
58+
def test_os_environ_overrides_cert_bundle(self):
59+
environ = {
60+
'AWS_CA_BUNDLE': '/path/to/bundle.pem',
61+
}
62+
with mock.patch('os.environ', environ):
63+
parsed_args = FakeParsedArgs(verify_ssl=True)
64+
globalargs.resolve_types(parsed_args)
65+
self.assertEqual(parsed_args.verify_ssl, '/path/to/bundle.pem')

tests/unit/test_clidriver.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@
6262
"metavar": "endpoint_url"
6363
},
6464
"no-verify-ssl": {
65-
"action": "store_true",
65+
"action": "store_false",
66+
"dest": "verify_ssl",
6667
"help": "Override default behavior of verifying SSL certificates"
6768
},
6869
"no-paginate": {
@@ -494,5 +495,23 @@ def test_invoke_with_no_credentials(self):
494495
caller.invoke(None, None, None)
495496

496497

498+
class TestVerifyArgument(BaseAWSCommandParamsTest):
499+
def setUp(self):
500+
super(TestVerifyArgument, self).setUp()
501+
self.driver.session.register('top-level-args-parsed', self.record_args)
502+
self.recorded_args = None
503+
504+
def record_args(self, parsed_args, **kwargs):
505+
self.recorded_args = parsed_args
506+
507+
def test_no_verify_argument(self):
508+
self.assert_params_for_cmd('s3api list-buckets --no-verify-ssl'.split())
509+
self.assertFalse(self.recorded_args.verify_ssl)
510+
511+
def test_verify_argument_is_none_by_default(self):
512+
self.assert_params_for_cmd('s3api list-buckets'.split())
513+
self.assertIsNone(self.recorded_args.verify_ssl)
514+
515+
497516
if __name__ == '__main__':
498517
unittest.main()

0 commit comments

Comments
 (0)