Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/awscli/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 logging
14
import os
15
import shlex
16
import subprocess
17
18
from botocore.configloader import raw_config_parse
19
20
from awscli.commands import CLICommand
21
from awscli.compat import compat_shell_quote
22
from awscli.utils import emit_top_level_args_parsed_event
23
24
LOG = logging.getLogger(__name__)
25
26
27
class InvalidAliasException(Exception):
28
pass
29
30
31
class AliasLoader:
32
def __init__(
33
self,
34
alias_filename=os.path.expanduser(
35
os.path.join('~', '.aws', 'cli', 'alias')
36
),
37
):
38
"""Interface for loading and interacting with alias file
39
40
:param alias_filename: The name of the file to load aliases from.
41
This file must be an INI file.
42
"""
43
self._filename = alias_filename
44
self._aliases = None
45
46
def _build_aliases(self):
47
self._aliases = self._load_aliases()
48
self._cleanup_alias_values(self._aliases.get('toplevel', {}))
49
50
def _load_aliases(self):
51
if os.path.exists(self._filename):
52
return raw_config_parse(self._filename, parse_subsections=False)
53
return {'toplevel': {}}
54
55
def _cleanup_alias_values(self, aliases):
56
for alias in aliases:
57
# Beginning and end line separators should not be included
58
# in the internal representation of the alias value.
59
aliases[alias] = aliases[alias].strip()
60
61
def get_aliases(self):
62
if self._aliases is None:
63
self._build_aliases()
64
return self._aliases.get('toplevel', {})
65
66
67
class AliasCommandInjector:
68
def __init__(self, session, alias_loader):
69
"""Injects alias commands for a command table
70
71
:type session: botocore.session.Session
72
:param session: The botocore session
73
74
:type alias_loader: awscli.alias.AliasLoader
75
:param alias_loader: The alias loader to use
76
"""
77
self._session = session
78
self._alias_loader = alias_loader
79
80
def inject_aliases(self, command_table, parser):
81
for (
82
alias_name,
83
alias_value,
84
) in self._alias_loader.get_aliases().items():
85
if alias_value.startswith('!'):
86
alias_cmd = ExternalAliasCommand(alias_name, alias_value)
87
else:
88
service_alias_cmd_args = [
89
alias_name,
90
alias_value,
91
self._session,
92
command_table,
93
parser,
94
]
95
# If the alias name matches something already in the
96
# command table provide the command it is about
97
# to clobber as a possible reference that it will
98
# need to proxy to.
99
if alias_name in command_table:
100
service_alias_cmd_args.append(command_table[alias_name])
101
alias_cmd = ServiceAliasCommand(*service_alias_cmd_args)
102
command_table[alias_name] = alias_cmd
103
104
105
class BaseAliasCommand(CLICommand):
106
_UNDOCUMENTED = True
107
108
def __init__(self, alias_name, alias_value):
109
"""Base class for alias command
110
111
:type alias_name: string
112
:param alias_name: The name of the alias
113
114
:type alias_value: string
115
:param alias_value: The parsed value of the alias. This can be
116
retrieved from `AliasLoader.get_aliases()[alias_name]`
117
"""
118
self._alias_name = alias_name
119
self._alias_value = alias_value
120
121
def __call__(self, args, parsed_args):
122
raise NotImplementedError('__call__')
123
124
@property
125
def name(self):
126
return self._alias_name
127
128
@name.setter
129
def name(self, value):
130
self._alias_name = value
131
132
133
class ServiceAliasCommand(BaseAliasCommand):
134
UNSUPPORTED_GLOBAL_PARAMETERS = ('debug', 'profile')
135
136
def __init__(
137
self,
138
alias_name,
139
alias_value,
140
session,
141
command_table,
142
parser,
143
shadow_proxy_command=None,
144
):
145
"""Command for a `toplevel` subcommand alias
146
147
:type alias_name: string
148
:param alias_name: The name of the alias
149
150
:type alias_value: string
151
:param alias_value: The parsed value of the alias. This can be
152
retrieved from `AliasLoader.get_aliases()[alias_name]`
153
154
:type session: botocore.session.Session
155
:param session: The botocore session
156
157
:type command_table: dict
158
:param command_table: The command table containing all of the
159
possible service command objects that a particular alias could
160
redirect to.
161
162
:type parser: awscli.argparser.MainArgParser
163
:param parser: The parser to parse commands provided at the top level
164
of a CLI command which includes service commands and global
165
parameters. This is used to parse the service command and any
166
global parameters from the alias's value.
167
168
:type shadow_proxy_command: CLICommand
169
:param shadow_proxy_command: A built-in command that
170
potentially shadows the alias in name. If the alias
171
references this command in its value, the alias should proxy
172
to this command as opposed to proxy to itself in the command
173
table
174
"""
175
super().__init__(alias_name, alias_value)
176
self._session = session
177
self._command_table = command_table
178
self._parser = parser
179
self._shadow_proxy_command = shadow_proxy_command
180
181
def __call__(self, args, parsed_globals):
182
alias_args = self._get_alias_args()
183
parsed_alias_args, remaining = self._parser.parse_known_args(
184
alias_args
185
)
186
self._update_parsed_globals(parsed_alias_args, parsed_globals)
187
# Take any of the remaining arguments that were not parsed out and
188
# prepend them to the remaining args provided to the alias.
189
remaining.extend(args)
190
LOG.debug(
191
'Alias %r passing on arguments: %r to %r command',
192
self._alias_name,
193
remaining,
194
parsed_alias_args.command,
195
)
196
# Pass the update remaining args and global args to the service command
197
# the alias proxied to.
198
command = self._command_table[parsed_alias_args.command]
199
if self._shadow_proxy_command:
200
shadow_name = self._shadow_proxy_command.name
201
# Use the shadow command only if the aliases value
202
# uses that command indicating it needs to proxy over to
203
# a built-in command.
204
if shadow_name == parsed_alias_args.command:
205
LOG.debug(
206
'Using shadowed command object: %s for alias: %s',
207
self._shadow_proxy_command,
208
self._alias_name,
209
)
210
command = self._shadow_proxy_command
211
return command(remaining, parsed_globals)
212
213
def _get_alias_args(self):
214
try:
215
alias_args = shlex.split(self._alias_value)
216
except ValueError as e:
217
raise InvalidAliasException(
218
f'Value of alias "{self._alias_name}" could not be parsed. '
219
f'Received error: {e} when parsing:\n{self._alias_value}'
220
)
221
222
alias_args = [arg.strip(os.linesep) for arg in alias_args]
223
LOG.debug(
224
'Expanded subcommand alias %r with value: %r to: %r',
225
self._alias_name,
226
self._alias_value,
227
alias_args,
228
)
229
return alias_args
230
231
def _update_parsed_globals(self, parsed_alias_args, parsed_globals):
232
global_params_to_update = self._get_global_parameters_to_update(
233
parsed_alias_args
234
)
235
# Emit the top level args parsed event to ensure all possible
236
# customizations that typically get applied are applied to the
237
# global parameters provided in the alias before updating
238
# the original provided global parameter values
239
# and passing those onto subsequent commands.
240
emit_top_level_args_parsed_event(self._session, parsed_alias_args)
241
for param_name in global_params_to_update:
242
updated_param_value = getattr(parsed_alias_args, param_name)
243
setattr(parsed_globals, param_name, updated_param_value)
244
245
def _get_global_parameters_to_update(self, parsed_alias_args):
246
# Retrieve a list of global parameters that the newly parsed args
247
# from the alias will have to clobber from the originally provided
248
# parsed globals.
249
global_params_to_update = []
250
for parsed_param, value in vars(parsed_alias_args).items():
251
# To determine which parameters in the alias were global values
252
# compare the parsed alias parameters to the default as
253
# specified by the parser. If the parsed values from the alias
254
# differs from the default value in the parser,
255
# that global parameter must have been provided in the alias.
256
if self._parser.get_default(parsed_param) != value:
257
if parsed_param in self.UNSUPPORTED_GLOBAL_PARAMETERS:
258
raise InvalidAliasException(
259
f'Global parameter "--{parsed_param}" detected in alias '
260
f'"{self._alias_name}" which is not supported in '
261
'subcommand aliases.'
262
)
263
else:
264
global_params_to_update.append(parsed_param)
265
return global_params_to_update
266
267
268
class ExternalAliasCommand(BaseAliasCommand):
269
def __init__(self, alias_name, alias_value, invoker=subprocess.call):
270
"""Command for external aliases
271
272
Executes command external of CLI as opposed to being a proxy
273
to another command.
274
275
:type alias_name: string
276
:param alias_name: The name of the alias
277
278
:type alias_value: string
279
:param alias_value: The parsed value of the alias. This can be
280
retrieved from `AliasLoader.get_aliases()[alias_name]`
281
282
:type invoker: callable
283
:param invoker: Callable to run arguments of external alias. The
284
signature should match that of ``subprocess.call``
285
"""
286
self._alias_name = alias_name
287
self._alias_value = alias_value
288
self._invoker = invoker
289
290
def __call__(self, args, parsed_globals):
291
command_components = [self._alias_value[1:]]
292
command_components.extend(compat_shell_quote(a) for a in args)
293
command = ' '.join(command_components)
294
LOG.debug(
295
'Using external alias %r with value: %r to run: %r',
296
self._alias_name,
297
self._alias_value,
298
command,
299
)
300
return self._invoker(command, shell=True)
301
302