Skip to content

Commit 65d4917

Browse files
committed
Merge branch 'profile-support'
* profile-support: Also add .profile under docs for dot commands Update README with new profile functionality Add .profile dot command Add --profile argument
2 parents 060192f + a4f68de commit 65d4917

File tree

5 files changed

+172
-2
lines changed

5 files changed

+172
-2
lines changed

README.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,33 @@ you can try::
9595
TABLENAMES Second
9696
TABLENAMES Third
9797

98+
Profiles
99+
--------
100+
101+
The aws-shell supports AWS CLI profiles. You have two options to use
102+
profiles. First, you can provide a profile when you start the aws-shell::
103+
104+
$ aws-shell --profile prod
105+
aws>
106+
107+
When you do this all the server side completion as well as CLI commands
108+
you run will automatically use the ``prod`` profile.
109+
110+
You can also change the current profile while you're in the aws-shell::
111+
112+
$ aws-shell
113+
aws> .profile demo
114+
Current shell profile changed to: demo
115+
116+
You can also check what profile you've configured in the aws-shell using::
117+
118+
aws> .profile
119+
Current shell profile: demo
120+
121+
After changing your profile using the ``.profile`` dot command, all
122+
server side completion as well as CLI commands will automatically use
123+
the new profile you've configured.
124+
98125

99126
Features
100127
========
@@ -218,6 +245,21 @@ variable before defaulting to ``notepad`` on Windows and
218245
aws> dynamodb list-tables
219246
aws> .edit
220247

248+
Changing Profiles with .profile
249+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
250+
251+
You can change the current AWS CLI profile used by the aws-shell
252+
by using the ``.profile`` dot command. If you run the ``.profile``
253+
command with no arguments, the currently configured shell profile
254+
will be printed.
255+
256+
::
257+
258+
aws> .profile demo
259+
Current shell profile changed to: demo
260+
aws> .profile
261+
Current shell profile: demo
262+
221263

222264
Executing Shell Commands
223265
------------------------

awsshell/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import unicode_literals, print_function
22

33
import json
4+
import argparse
45
import threading
56

67
from awsshell import shellcomplete
@@ -28,6 +29,11 @@ def load_index(filename):
2829

2930

3031
def main():
32+
parser = argparse.ArgumentParser()
33+
parser.add_argument('-p', '--profile', help='The profile name to use '
34+
'when starting the AWS Shell.')
35+
args = parser.parse_args()
36+
3137
indexer = completion.CompletionIndex()
3238
try:
3339
index_str = indexer.load_index(utils.AWSCLI_VERSION)
@@ -59,6 +65,8 @@ def main():
5965
model_completer = autocomplete.AWSCLIModelCompleter(index_data)
6066
completer = shellcomplete.AWSShellCompleter(model_completer)
6167
shell = app.create_aws_shell(completer, model_completer, doc_data)
68+
if args.profile:
69+
shell.profile = args.profile
6270
shell.run()
6371

6472

awsshell/app.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,45 @@ def run(self, command, application):
8282
p.communicate()
8383

8484

85+
class ProfileHandler(object):
86+
USAGE = (
87+
'.profile # Print the current profile\n'
88+
'.profile <name> # Change the current profile\n'
89+
)
90+
91+
def __init__(self, output=sys.stdout, err=sys.stderr):
92+
self._output = output
93+
self._err = err
94+
95+
def run(self, command, application):
96+
"""Get or set the profile.
97+
98+
If .profile is called with no args, the current profile
99+
is displayed. If the .profile command is called with a
100+
single arg, then the current profile for the application
101+
will be set to the new value.
102+
"""
103+
if len(command) == 1:
104+
profile = application.profile
105+
if profile is None:
106+
self._output.write(
107+
"Current shell profile: no profile configured\n"
108+
"You can change profiles using: .profile profile-name\n")
109+
else:
110+
self._output.write("Current shell profile: %s\n" % profile)
111+
elif len(command) == 2:
112+
new_profile_name = command[1]
113+
application.profile = new_profile_name
114+
self._output.write("Current shell profile changed to: %s\n" %
115+
new_profile_name)
116+
else:
117+
self._err.write("Usage:\n%s\n" % self.USAGE)
118+
119+
85120
class DotCommandHandler(object):
86121
HANDLER_CLASSES = {
87122
'edit': EditHandler,
123+
'profile': ProfileHandler,
88124
}
89125

90126
def __init__(self, output=sys.stdout, err=sys.stderr):
@@ -162,6 +198,8 @@ def __init__(self, completer, model_completer, docs):
162198
self.refresh_cli = False
163199
self.key_manager = None
164200
self._dot_cmd = DotCommandHandler()
201+
self._env = os.environ.copy()
202+
self._profile = None
165203

166204
# These attrs come from the config file.
167205
self.config_obj = None
@@ -233,7 +271,7 @@ def run(self):
233271
initial_document=Document(self.current_docs,
234272
cursor_position=0))
235273
self.cli.request_redraw()
236-
p = subprocess.Popen(full_cmd, shell=True)
274+
p = subprocess.Popen(full_cmd, shell=True, env=self._env)
237275
p.communicate()
238276

239277
def stop_input_and_refresh_cli(self):
@@ -386,3 +424,27 @@ def create_cli_interface(self, display_completions_in_columns):
386424
display_completions_in_columns)
387425
cli = CommandLineInterface(application=app, eventloop=loop)
388426
return cli
427+
428+
@property
429+
def profile(self):
430+
return self._profile
431+
432+
@profile.setter
433+
def profile(self, new_profile_name):
434+
# There's only two things that need to know about new profile
435+
# changes.
436+
#
437+
# First, the actual command runner. If we want
438+
# to use a different profile, it should ensure that the CLI
439+
# commands that get run use the new profile (via the
440+
# AWS_DEFAULT_PROFILE env var).
441+
#
442+
# Second, we also need to let the server side autocompleter know.
443+
#
444+
# Given this is easy to manage by hand, I don't think
445+
# it's worth adding an event system or observers just yet.
446+
# If this gets hard to manage, the complexity of those systems
447+
# would be worth it.
448+
self._env['AWS_DEFAULT_PROFILE'] = new_profile_name
449+
self.completer.change_profile(new_profile_name)
450+
self._profile = new_profile_name

awsshell/shellcomplete.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111
"""
1212
import os
1313
import logging
14+
15+
import botocore.session
1416
from prompt_toolkit.completion import Completer, Completion
17+
1518
from awsshell import fuzzy
1619

1720

@@ -33,7 +36,6 @@ def __init__(self, completer, server_side_completer=None):
3336
self._server_side_completer = server_side_completer
3437

3538
def _create_server_side_completer(self, session=None):
36-
import botocore.session
3739
from awsshell.resource import index
3840
if session is None:
3941
session = botocore.session.Session()
@@ -48,6 +50,11 @@ def _create_server_side_completer(self, session=None):
4850
completer = index.ServerSideCompleter(client_creator, describer)
4951
return completer
5052

53+
def change_profile(self, profile_name):
54+
"""Change the profile used for server side completions."""
55+
self._server_side_completer = self._create_server_side_completer(
56+
session=botocore.session.Session(profile=profile_name))
57+
5158
@property
5259
def completer(self):
5360
return self._completer

tests/unit/test_app.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44

55
from awsshell import app
6+
from awsshell import shellcomplete
67
from awsshell import compat
78

89

@@ -38,7 +39,57 @@ def test_edit_handler():
3839
assert command_run[0] == 'my-editor'
3940

4041

42+
def test_profile_handler_prints_profile():
43+
shell = mock.Mock(spec=app.AWSShell)
44+
shell.profile = 'myprofile'
45+
stdout = compat.StringIO()
46+
handler = app.ProfileHandler(stdout)
47+
handler.run(['.profile'], shell)
48+
assert stdout.getvalue().strip() == 'Current shell profile: myprofile'
49+
50+
51+
def test_profile_handler_when_no_profile_configured():
52+
shell = mock.Mock(spec=app.AWSShell)
53+
shell.profile = None
54+
stdout = compat.StringIO()
55+
handler = app.ProfileHandler(stdout)
56+
handler.run(['.profile'], shell)
57+
assert stdout.getvalue() == (
58+
'Current shell profile: no profile configured\n'
59+
'You can change profiles using: .profile profile-name\n'
60+
)
61+
62+
63+
def test_profile_command_changes_profile():
64+
shell = mock.Mock(spec=app.AWSShell)
65+
shell.profile = 'myprofile'
66+
stdout = compat.StringIO()
67+
handler = app.ProfileHandler(stdout)
68+
69+
handler.run(['.profile', 'newprofile'], shell)
70+
71+
assert shell.profile == 'newprofile'
72+
73+
74+
def test_profile_prints_error_on_bad_syntax():
75+
stderr = compat.StringIO()
76+
handler = app.ProfileHandler(None, stderr)
77+
handler.run(['.profile', 'a', 'b', 'c'], None)
78+
79+
# We don't really care about the exact usage message here,
80+
# we just want to ensure usage was written to stderr.
81+
assert 'Usage' in stderr.getvalue()
82+
83+
4184
def test_prints_error_message_on_unknown_dot_command(errstream):
4285
handler = app.DotCommandHandler(err=errstream)
4386
handler.handle_cmd(".unknown foo bar", None)
4487
assert errstream.getvalue() == "Unknown dot command: .unknown\n"
88+
89+
90+
def test_delegates_to_complete_changing_profile():
91+
completer = mock.Mock(spec=shellcomplete.AWSShellCompleter)
92+
shell = app.AWSShell(completer, mock.Mock(), mock.Mock())
93+
shell.profile = 'mynewprofile'
94+
assert completer.change_profile.call_args == mock.call('mynewprofile')
95+
assert shell.profile == 'mynewprofile'

0 commit comments

Comments
 (0)