Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/tests/unit/test_alias.py
1566 views
1
# Copyright 2016 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 os
14
import subprocess
15
16
from botocore.session import Session
17
18
from awscli.testutils import unittest
19
from awscli.testutils import mock
20
from awscli.testutils import FileCreator
21
from awscli.alias import InvalidAliasException
22
from awscli.alias import AliasLoader
23
from awscli.alias import AliasCommandInjector
24
from awscli.alias import BaseAliasCommand
25
from awscli.alias import ServiceAliasCommand
26
from awscli.alias import ExternalAliasCommand
27
from awscli.argparser import MainArgParser
28
from awscli.commands import CLICommand
29
30
31
class FakeParsedArgs(object):
32
def __init__(self, **kwargs):
33
self.__dict__.update(kwargs)
34
35
def __repr__(self):
36
return '%s(%s)' % (self.__class__.__name__, self.__dict__)
37
38
def __eq__(self, other):
39
return self.__dict__ == other.__dict__
40
41
def __contains__(self, key):
42
return key in self.__dict__
43
44
45
class TestAliasLoader(unittest.TestCase):
46
def setUp(self):
47
self.files = FileCreator()
48
self.alias_file = self.files.create_file('alias', '[toplevel]\n')
49
50
def tearDown(self):
51
self.files.remove_all()
52
53
def test_get_aliases_non_existent_file(self):
54
nonexistent_file = os.path.join(self.files.rootdir, 'no-exists')
55
alias_interface = AliasLoader(nonexistent_file)
56
self.assertEqual(alias_interface.get_aliases(), {})
57
58
def test_get_aliases_empty_file(self):
59
alias_interface = AliasLoader(self.alias_file)
60
self.assertEqual(alias_interface.get_aliases(), {})
61
62
def test_get_aliases_missing_toplevel(self):
63
with open(self.alias_file, 'w') as f:
64
f.write('[unrelated-section]\n')
65
alias_interface = AliasLoader(self.alias_file)
66
self.assertEqual(alias_interface.get_aliases(), {})
67
68
def test_get_aliases(self):
69
with open(self.alias_file, 'a+') as f:
70
f.write('my-alias = my-alias-value')
71
alias_interface = AliasLoader(self.alias_file)
72
self.assertEqual(
73
alias_interface.get_aliases(), {'my-alias': 'my-alias-value'})
74
75
def test_get_aliases_with_alias_that_includes_parameter(self):
76
with open(self.alias_file, 'a+') as f:
77
f.write('my-alias = my-alias-value --my-parameter my-value')
78
alias_interface = AliasLoader(self.alias_file)
79
self.assertEqual(
80
alias_interface.get_aliases(),
81
{'my-alias': 'my-alias-value --my-parameter my-value'})
82
83
def test_get_aliases_with_alias_that_includes_newlines(self):
84
with open(self.alias_file, 'a+') as f:
85
f.write('my-alias = my-alias-value\n')
86
alias_interface = AliasLoader(self.alias_file)
87
self.assertEqual(
88
alias_interface.get_aliases(), {'my-alias': 'my-alias-value'})
89
90
def test_get_aliases_with_alias_that_is_indented(self):
91
with open(self.alias_file, 'a+') as f:
92
f.write('my-alias = \n my-alias-value\n')
93
alias_interface = AliasLoader(self.alias_file)
94
self.assertEqual(
95
alias_interface.get_aliases(), {'my-alias': 'my-alias-value'})
96
97
def test_get_aliases_with_multiple_lines(self):
98
with open(self.alias_file, 'a+') as f:
99
f.write(
100
'my-alias = \n'
101
' my-alias-value \\ \n'
102
' --parameter foo\n'
103
)
104
alias_interface = AliasLoader(self.alias_file)
105
self.assertEqual(
106
alias_interface.get_aliases(),
107
# The backslash and newline should be preserved, but
108
# still have the beginning and end newlines removed.
109
{'my-alias': 'my-alias-value \\\n--parameter foo'})
110
111
def test_get_aliases_with_multiple_aliases(self):
112
with open(self.alias_file, 'a+') as f:
113
f.write('my-alias = my-alias-value\n')
114
f.write('my-second-alias = my-second-alias-value\n')
115
alias_interface = AliasLoader(self.alias_file)
116
self.assertEqual(
117
alias_interface.get_aliases(),
118
{'my-alias': 'my-alias-value',
119
'my-second-alias': 'my-second-alias-value'})
120
121
122
class TestAliasCommandInjector(unittest.TestCase):
123
def setUp(self):
124
self.files = FileCreator()
125
self.alias_file = self.files.create_file('alias', '[toplevel]\n')
126
self.alias_loader = AliasLoader(self.alias_file)
127
self.session = mock.Mock(spec=Session)
128
self.alias_cmd_injector = AliasCommandInjector(
129
self.session, self.alias_loader)
130
self.command_table = {}
131
self.parser = MainArgParser(
132
command_table=self.command_table,
133
version_string='version',
134
description='description',
135
argument_table={}
136
)
137
138
def tearDown(self):
139
self.files.remove_all()
140
141
def test_service_alias_command(self):
142
with open(self.alias_file, 'a+') as f:
143
f.write('my-alias = my-alias-value\n')
144
145
self.alias_cmd_injector.inject_aliases(
146
self.command_table, self.parser)
147
self.assertIn('my-alias', self.command_table)
148
self.assertIsInstance(
149
self.command_table['my-alias'], ServiceAliasCommand)
150
151
def test_external_alias_command(self):
152
with open(self.alias_file, 'a+') as f:
153
f.write('my-alias = !my-alias-value\n')
154
155
self.alias_cmd_injector.inject_aliases(
156
self.command_table, self.parser)
157
self.assertIn('my-alias', self.command_table)
158
self.assertIsInstance(
159
self.command_table['my-alias'], ExternalAliasCommand)
160
161
def test_clobbers_builtins(self):
162
builtin_cmd = mock.Mock(spec=CLICommand)
163
self.command_table['builtin'] = builtin_cmd
164
165
with open(self.alias_file, 'a+') as f:
166
f.write('builtin = my-alias-value\n')
167
168
self.alias_cmd_injector.inject_aliases(
169
self.command_table, self.parser)
170
self.assertIn('builtin', self.command_table)
171
self.assertIsInstance(
172
self.command_table['builtin'], ServiceAliasCommand)
173
174
def test_shadow_proxy_command(self):
175
builtin_cmd = mock.Mock(spec=CLICommand)
176
builtin_cmd.name = 'builtin'
177
self.command_table['builtin'] = builtin_cmd
178
179
with open(self.alias_file, 'a+') as f:
180
f.write('builtin = builtin\n')
181
182
self.alias_cmd_injector.inject_aliases(
183
self.command_table, self.parser)
184
185
self.command_table['builtin'](
186
[], FakeParsedArgs(command='builtin'))
187
# The builtin command should be passed to the alias
188
# command when added to the table.
189
builtin_cmd.assert_called_with(
190
[], FakeParsedArgs(command='builtin'))
191
192
193
class TestBaseAliasCommand(unittest.TestCase):
194
def test_name(self):
195
alias_cmd = BaseAliasCommand('alias-name', 'alias-value')
196
self.assertEqual(alias_cmd.name, 'alias-name')
197
alias_cmd.name = 'new-alias-name'
198
self.assertEqual(alias_cmd.name, 'new-alias-name')
199
200
201
class TestServiceAliasCommand(unittest.TestCase):
202
def setUp(self):
203
self.alias_name = 'myalias'
204
self.session = mock.Mock(spec=Session)
205
206
def create_command_table(self, services):
207
command_table = {}
208
for service in services:
209
command_table[service] = mock.Mock(spec=CLICommand)
210
return command_table
211
212
def create_parser(self, command_table, extra_params=None):
213
parser = MainArgParser(
214
command_table=command_table,
215
version_string='version',
216
description='description',
217
argument_table={}
218
)
219
if extra_params:
220
for extra_param in extra_params:
221
parser.add_argument('--'+extra_param)
222
return parser
223
224
def test_alias_with_only_service_command(self):
225
alias_value = 'myservice'
226
227
command_table = self.create_command_table([alias_value])
228
parser = self.create_parser(command_table)
229
alias_cmd = ServiceAliasCommand(
230
self.alias_name, alias_value, self.session, command_table, parser)
231
232
alias_cmd([], FakeParsedArgs(command=self.alias_name))
233
command_table['myservice'].assert_called_with(
234
[], FakeParsedArgs(command='myservice'))
235
236
def tests_alias_with_shadow_proxy_command(self):
237
alias_value = 'some-service'
238
self.alias_name = alias_value
239
240
shadow_proxy_command = mock.Mock(spec=CLICommand)
241
shadow_proxy_command.name = alias_value
242
243
command_table = {}
244
parser = self.create_parser(command_table)
245
246
alias_cmd = ServiceAliasCommand(
247
self.alias_name, alias_value, self.session, command_table,
248
parser, shadow_proxy_command)
249
command_table[self.alias_name] = alias_cmd
250
251
alias_cmd([], FakeParsedArgs(command=self.alias_name))
252
shadow_proxy_command.assert_called_with(
253
[], FakeParsedArgs(command=alias_value))
254
255
def test_alias_with_shadow_proxy_command_no_match(self):
256
alias_value = 'some-other-command'
257
self.alias_name = 'some-service'
258
259
shadow_proxy_command = mock.Mock(spec=CLICommand)
260
shadow_proxy_command.name = 'some-service'
261
262
command_table = self.create_command_table([alias_value])
263
parser = self.create_parser(command_table)
264
265
alias_cmd = ServiceAliasCommand(
266
self.alias_name, alias_value, self.session, command_table,
267
parser, shadow_proxy_command)
268
command_table[self.alias_name] = alias_cmd
269
270
alias_cmd([], FakeParsedArgs(command=self.alias_name))
271
command_table[alias_value].assert_called_with(
272
[], FakeParsedArgs(command=alias_value))
273
# Even though it was provided, it should not be called because
274
# the alias value did not match the shadow command name.
275
self.assertFalse(shadow_proxy_command.called)
276
277
def test_alias_with_operation_command(self):
278
alias_value = 'myservice myoperation'
279
280
command_table = self.create_command_table(['myservice'])
281
parser = self.create_parser(command_table)
282
alias_cmd = ServiceAliasCommand(
283
self.alias_name, alias_value, self.session, command_table, parser)
284
285
parsed_globals = FakeParsedArgs(command=self.alias_name)
286
alias_cmd([], parsed_globals)
287
command_table['myservice'].assert_called_with(
288
['myoperation'], FakeParsedArgs(command='myservice'))
289
290
def test_alias_then_help_command(self):
291
alias_value = 'myservice myoperation'
292
293
command_table = self.create_command_table(['myservice'])
294
parser = self.create_parser(command_table)
295
alias_cmd = ServiceAliasCommand(
296
self.alias_name, alias_value, self.session, command_table, parser)
297
298
parsed_globals = FakeParsedArgs(command=self.alias_name)
299
alias_cmd(['help'], parsed_globals)
300
command_table['myservice'].assert_called_with(
301
['myoperation', 'help'], FakeParsedArgs(command='myservice'))
302
303
def test_alias_then_additional_parameters(self):
304
alias_value = 'myservice myoperation'
305
306
command_table = self.create_command_table(['myservice'])
307
parser = self.create_parser(command_table)
308
alias_cmd = ServiceAliasCommand(
309
self.alias_name, alias_value, self.session, command_table, parser)
310
311
parsed_globals = FakeParsedArgs(command=self.alias_name)
312
alias_cmd(['--parameter', 'val'], parsed_globals)
313
command_table['myservice'].assert_called_with(
314
['myoperation', '--parameter', 'val'],
315
FakeParsedArgs(command='myservice')
316
)
317
318
def test_alias_with_operation_and_parameters(self):
319
alias_value = 'myservice myoperation --my-parameter val'
320
321
command_table = self.create_command_table(['myservice'])
322
parser = self.create_parser(command_table)
323
alias_cmd = ServiceAliasCommand(
324
self.alias_name, alias_value, self.session, command_table, parser)
325
326
alias_cmd([], FakeParsedArgs(command=self.alias_name))
327
command_table['myservice'].assert_called_with(
328
['myoperation', '--my-parameter', 'val'],
329
FakeParsedArgs(command='myservice')
330
)
331
332
def test_alias_with_operation_and_global_parameters(self):
333
alias_value = 'myservice myoperation --global-param val'
334
335
command_table = self.create_command_table(['myservice'])
336
parser = self.create_parser(
337
command_table, extra_params=['global-param'])
338
alias_cmd = ServiceAliasCommand(
339
self.alias_name, alias_value, self.session, command_table, parser)
340
341
alias_cmd([], FakeParsedArgs(command=self.alias_name))
342
command_table['myservice'].assert_called_with(
343
['myoperation'],
344
FakeParsedArgs(command='myservice', global_param='val')
345
)
346
347
def test_maintains_global_defaults_when_missing_from_alias(self):
348
alias_value = 'myservice myoperation'
349
350
command_table = self.create_command_table(['myservice'])
351
parser = self.create_parser(command_table)
352
parser.add_argument('--global-with-default', default='default')
353
alias_cmd = ServiceAliasCommand(
354
self.alias_name, alias_value, self.session, command_table, parser)
355
alias_cmd(
356
[],
357
FakeParsedArgs(
358
command=self.alias_name, global_with_default='default')
359
)
360
command_table['myservice'].assert_called_with(
361
['myoperation'],
362
FakeParsedArgs(
363
command='myservice', global_with_default='default')
364
)
365
366
def test_sets_global_parameters_when_differs_from_defaults(self):
367
alias_value = 'myservice myoperation --global-with-default non-default'
368
369
command_table = self.create_command_table(['myservice'])
370
parser = self.create_parser(command_table)
371
parser.add_argument('--global-with-default', default='default')
372
alias_cmd = ServiceAliasCommand(
373
self.alias_name, alias_value, self.session, command_table, parser)
374
375
alias_cmd([], FakeParsedArgs(command=self.alias_name))
376
command_table['myservice'].assert_called_with(
377
['myoperation'],
378
FakeParsedArgs(
379
command='myservice', global_with_default='non-default')
380
)
381
382
def test_global_parameters_can_be_emitted_and_modified(self):
383
alias_value = 'myservice myoperation --global-param val'
384
385
command_table = self.create_command_table(['myservice'])
386
parser = self.create_parser(
387
command_table, extra_params=['global-param'])
388
alias_cmd = ServiceAliasCommand(
389
self.alias_name, alias_value, self.session, command_table, parser)
390
391
def replace_global_param_value_with_foo(event_name, **kwargs):
392
parsed_args = kwargs['parsed_args']
393
parsed_args.global_param = 'foo'
394
395
self.session.emit.side_effect = replace_global_param_value_with_foo
396
alias_cmd([], FakeParsedArgs(command=self.alias_name))
397
self.session.emit.assert_called_with(
398
'top-level-args-parsed', parsed_args=mock.ANY,
399
session=self.session)
400
401
command_table['myservice'].assert_called_with(
402
['myoperation'],
403
FakeParsedArgs(command='myservice', global_param='foo')
404
)
405
406
def test_properly_handles_multiple_spaces(self):
407
alias_value = (
408
'myservice myoperation --my-parameter val'
409
)
410
411
command_table = self.create_command_table(['myservice'])
412
parser = self.create_parser(command_table)
413
alias_cmd = ServiceAliasCommand(
414
self.alias_name, alias_value, self.session, command_table, parser)
415
416
alias_cmd([], FakeParsedArgs(command=self.alias_name))
417
command_table['myservice'].assert_called_with(
418
['myoperation', '--my-parameter', 'val'],
419
FakeParsedArgs(command='myservice')
420
)
421
422
def test_properly_parses_aliases_broken_by_multiple_lines(self):
423
alias_value = (
424
'myservice myoperation \\'
425
'\n--my-parameter val'
426
)
427
428
command_table = self.create_command_table(['myservice'])
429
parser = self.create_parser(command_table)
430
alias_cmd = ServiceAliasCommand(
431
self.alias_name, alias_value, self.session, command_table, parser)
432
433
alias_cmd([], FakeParsedArgs(command=self.alias_name))
434
command_table['myservice'].assert_called_with(
435
['myoperation', '--my-parameter', 'val'],
436
FakeParsedArgs(command='myservice')
437
)
438
439
def test_properly_preserves_quoted_values(self):
440
alias_value = (
441
'myservice myoperation --my-parameter \' \n$""\''
442
)
443
444
command_table = self.create_command_table(['myservice'])
445
parser = self.create_parser(command_table)
446
alias_cmd = ServiceAliasCommand(
447
self.alias_name, alias_value, self.session, command_table, parser)
448
449
alias_cmd([], FakeParsedArgs(command=self.alias_name))
450
command_table['myservice'].assert_called_with(
451
['myoperation', '--my-parameter', ' \n$""'],
452
FakeParsedArgs(command='myservice')
453
)
454
455
def test_errors_when_service_command_is_invalid(self):
456
alias_value = 'non-existent-service myoperation'
457
458
command_table = self.create_command_table(['myservice'])
459
parser = self.create_parser(command_table)
460
alias_cmd = ServiceAliasCommand(
461
self.alias_name, alias_value, self.session, command_table, parser)
462
463
with self.assertRaises(SystemExit):
464
# Even though we catch the system exit, a message will always
465
# be forced to screen because it happened at system exit.
466
# The patch is to ensure it does not get displayed.
467
with mock.patch('sys.stderr'):
468
alias_cmd([], FakeParsedArgs(command=self.alias_name))
469
470
def test_errors_when_no_service_command(self):
471
alias_value = '--global-param=val'
472
473
command_table = self.create_command_table(['myservice'])
474
parser = self.create_parser(
475
command_table, extra_params=['global-param'])
476
alias_cmd = ServiceAliasCommand(
477
self.alias_name, alias_value, self.session, command_table, parser)
478
479
with self.assertRaises(SystemExit):
480
# Even though we catch the system exit, a message will always
481
# be forced to screen because it happened at system exit.
482
# The patch is to ensure it does not get displayed.
483
with mock.patch('sys.stderr'):
484
alias_cmd([], FakeParsedArgs(command=self.alias_name))
485
486
def test_errors_when_shell_cannot_parse_alias(self):
487
# Ending with a escape character that does not escape anything
488
# is invalid and cannot be properly parsed.
489
alias_value = (
490
'myservice myoperation \\'
491
)
492
493
command_table = self.create_command_table(['myservice'])
494
parser = self.create_parser(command_table)
495
alias_cmd = ServiceAliasCommand(
496
self.alias_name, alias_value, self.session, command_table, parser)
497
498
with self.assertRaises(InvalidAliasException):
499
alias_cmd([], FakeParsedArgs(command=self.alias_name))
500
501
def test_errors_when_unsupported_global_parameter_in_alias(self):
502
# Unsupported global parameters are: --profile and --debug
503
alias_value = (
504
'myservice myoperation --profile value'
505
)
506
507
command_table = self.create_command_table(['myservice'])
508
parser = self.create_parser(
509
command_table, extra_params=['profile'])
510
alias_cmd = ServiceAliasCommand(
511
self.alias_name, alias_value, self.session, command_table, parser)
512
513
with self.assertRaises(InvalidAliasException):
514
alias_cmd([], FakeParsedArgs(command=self.alias_name))
515
516
517
class TestExternalAliasCommand(unittest.TestCase):
518
def setUp(self):
519
self.subprocess_call = mock.Mock(spec=subprocess.call)
520
521
def test_run_external_command(self):
522
alias_value = '!ls'
523
alias_cmd = ExternalAliasCommand(
524
'alias-name', alias_value, invoker=self.subprocess_call)
525
alias_cmd([], FakeParsedArgs(command='alias-name'))
526
self.subprocess_call.assert_called_with('ls', shell=True)
527
528
def test_external_command_returns_rc_of_subprocess_call(self):
529
alias_value = '!ls'
530
alias_cmd = ExternalAliasCommand(
531
'alias-name', alias_value, invoker=self.subprocess_call)
532
self.subprocess_call.return_value = 1
533
self.assertEqual(
534
alias_cmd([], FakeParsedArgs(command='alias-name')), 1)
535
536
def test_external_command_uses_literal_alias_value(self):
537
alias_value = (
538
'!f () {\n'
539
' ls .\n'
540
'}; f'
541
)
542
alias_cmd = ExternalAliasCommand(
543
'alias-name', alias_value, invoker=self.subprocess_call)
544
alias_cmd([], FakeParsedArgs(command='alias-name'))
545
self.subprocess_call.assert_called_with(alias_value[1:], shell=True)
546
547
def test_external_command_then_additional_args(self):
548
alias_value = '!f () { ls "$1" }; f'
549
alias_cmd = ExternalAliasCommand(
550
'alias-name', alias_value, invoker=self.subprocess_call)
551
alias_cmd(['extra'], FakeParsedArgs(command='alias-name'))
552
self.subprocess_call.assert_called_with(
553
'f () { ls "$1" }; f extra', shell=True)
554
555