Skip to content

Commit 4aa4c78

Browse files
committed
Add ability to set properties in subsections
This change adds new parameters to the `aws configure set`` command to specify a sub-section for setting a property. These parameters are analogous to the existing `--profile` parameter. A parameter will be added for each sub-section type and take a value of the subsection name. Following is the generic pattern for the `aws configure set` command: ``` aws configure set --<sub-section-type> <sub-section-name> \ <property> <value> ``` For example, the following command should set the property `sso_region` to the value `us-west-2` in the `sso-session` sub-section named `my-sso-session`: ``` aws configure set --sso-session my-sso-session \ sso_region us-west-2 ``` Following is an example setting a nested property in a sub-section: ``` aws configure set \ --<sub-section-type> <sub-section-name> \ <nested-section>.<property> value ``` The only sub-section types allowed are `services` and `sso-session`.
1 parent 9fcb197 commit 4aa4c78

4 files changed

Lines changed: 180 additions & 1 deletion

File tree

awscli/customizations/configure/__init__.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,22 @@
1010
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
13+
import re
1314
import string
1415

1516
from awscli.compat import shlex
17+
from awscli.customizations.exceptions import ParamValidationError
18+
from awscli.customizations.utils import validate_mutually_exclusive
1619

1720
NOT_SET = '<not set>'
1821
PREDEFINED_SECTION_NAMES = 'plugins'
22+
# A map between the command line parameter name and the Python object name
23+
# For allowed sub-section types
24+
SUBSECTION_TYPE_ALLOWLIST = {
25+
'sso-session': 'sso_session',
26+
'services': 'services',
27+
}
28+
1929
_WHITESPACE = ' \t'
2030

2131

@@ -53,3 +63,27 @@ def get_section_header(section_type, section_name):
5363
if any(c in _WHITESPACE for c in section_name):
5464
section_name = shlex.quote(section_name)
5565
return f'{section_type} {section_name}'
66+
67+
68+
def get_subsection_from_args(args):
69+
# Validate mutual exclusivity of sub-section type parameters
70+
groups = [[param] for param in SUBSECTION_TYPE_ALLOWLIST.values()]
71+
validate_mutually_exclusive(args, *groups)
72+
73+
subsection_name = None
74+
subsection_type = None
75+
76+
for section_type, param_name in SUBSECTION_TYPE_ALLOWLIST.items():
77+
if hasattr(args, param_name):
78+
param_value = getattr(args, param_name)
79+
if param_value is not None:
80+
if not re.match(r"^\w", param_value):
81+
raise ParamValidationError(
82+
f"aws: [ERROR]: Invalid value for --{section_type}."
83+
)
84+
subsection_name = param_value
85+
subsection_type = section_type
86+
break
87+
88+
return (subsection_type, subsection_name)
89+

awscli/customizations/configure/set.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
from awscli.customizations.commands import BasicCommand
1616
from awscli.customizations.configure.writer import ConfigFileWriter
1717

18-
from . import PREDEFINED_SECTION_NAMES, profile_to_section
18+
from . import (
19+
PREDEFINED_SECTION_NAMES,
20+
get_section_header,
21+
get_subsection_from_args,
22+
profile_to_section
23+
)
1924

2025

2126
class ConfigureSetCommand(BasicCommand):
@@ -41,6 +46,20 @@ class ConfigureSetCommand(BasicCommand):
4146
'cli_type_name': 'string',
4247
'positional_arg': True,
4348
},
49+
{
50+
'name': 'sso-session',
51+
'help_text': 'The name of the SSO session sub-section to configure.',
52+
'action': 'store',
53+
'cli_type_name': 'string',
54+
'group_name': 'subsection',
55+
},
56+
{
57+
'name': 'services',
58+
'help_text': 'The name of the services sub-section to configure.',
59+
'action': 'store',
60+
'cli_type_name': 'string',
61+
'group_name': 'subsection',
62+
},
4463
]
4564
# Any variables specified in this list will be written to
4665
# the ~/.aws/credentials file instead of ~/.aws/config.
@@ -60,10 +79,37 @@ def _get_config_file(self, path):
6079
config_path = self._session.get_config_variable(path)
6180
return os.path.expanduser(config_path)
6281

82+
def _set_subsection_property(self, section_type, section_name, varname, value):
83+
if '.' in varname:
84+
parts = varname.split('.')
85+
if len(parts) > 2:
86+
return 0
87+
88+
varname = parts[0]
89+
value = {parts[1]: value}
90+
91+
# Build update dict
92+
updated_config = {
93+
'__section__': get_section_header(section_type, section_name),
94+
varname: value
95+
}
96+
97+
# Write to config file
98+
config_filename = self._get_config_file('config_file')
99+
self._config_writer.update_config(updated_config, config_filename)
100+
101+
return 0
102+
63103
def _run_main(self, args, parsed_globals):
64104
varname = args.varname
65105
value = args.value
66106
profile = 'default'
107+
108+
section_type, section_name = get_subsection_from_args(args)
109+
if section_type is not None:
110+
return self._set_subsection_property(section_type, section_name, varname, value)
111+
112+
# Not in a sub-section, continue with previous profile logic.
67113
# Before handing things off to the config writer,
68114
# we need to find out three things:
69115
# 1. What section we're writing to (profile).

tests/functional/configure/test_configure.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,80 @@ def test_get_nested_attribute(self):
299299
)
300300
self.assertEqual(stdout, "")
301301

302+
def test_set_with_subsection_no_name_provided(self):
303+
_, stderr, _ = self.run_cmd(
304+
[
305+
"configure",
306+
"set",
307+
"--sso-session",
308+
'',
309+
"space",
310+
"test",
311+
],
312+
expected_rc=252
313+
)
314+
self.assertIn("Invalid value for --sso-session", stderr)
315+
316+
def test_set_deeply_nested_property_in_subsection_does_nothing(self):
317+
self.set_config_file_contents(
318+
"[services my-services]\n" "ec2 =\n" " endpoint_url = localhost\n"
319+
)
320+
321+
self.run_cmd(
322+
[
323+
"configure",
324+
"set",
325+
"--services",
326+
'my-services',
327+
"s3.express.endpoint_url",
328+
"localhost",
329+
],
330+
expected_rc=0
331+
)
332+
self.assertEqual(
333+
"[services my-services]\n" "ec2 =\n" " endpoint_url = localhost\n",
334+
self.get_config_file_contents(),
335+
)
336+
337+
def test_set_with_two_subsections_specified_results_in_error(self):
338+
_, stderr, _ = self.run_cmd(
339+
[
340+
"configure",
341+
"set",
342+
"--sso-session",
343+
'my-sso',
344+
"--services",
345+
'my-services',
346+
"space",
347+
"test",
348+
],
349+
expected_rc=252
350+
)
351+
self.assertIn(
352+
"cannot be specified when one of the following keys are also specified:",
353+
stderr
354+
)
355+
356+
def test_set_updates_existing_property_in_subsection(self):
357+
self.set_config_file_contents(
358+
"[sso-session my-sso-session]\n" "sso_region = us-west-2\n"
359+
)
360+
361+
self.run_cmd(
362+
[
363+
"configure",
364+
"set",
365+
"--sso-session",
366+
'my-sso-session',
367+
"sso_region",
368+
"eu-central-1",
369+
],
370+
expected_rc=0
371+
)
372+
self.assertEqual(
373+
"[sso-session my-sso-session]\n" "sso_region = eu-central-1\n",
374+
self.get_config_file_contents(),
375+
)
302376

303377
class TestConfigureHasArgTable(unittest.TestCase):
304378
def test_configure_command_has_arg_table(self):

tests/unit/customizations/configure/test_set.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,28 @@ def test_configure_set_with_profile_with_tab_dotted(self):
205205
{'__section__': "profile 'some\tprofile'", 'region': 'us-west-2'},
206206
'myconfigfile',
207207
)
208+
209+
def test_set_top_level_property_in_subsection(self):
210+
set_command = ConfigureSetCommand(self.session, self.config_writer)
211+
set_command(
212+
args=['sso_region', 'us-west-2', '--sso-session', 'my-session'],
213+
parsed_globals=None,
214+
)
215+
self.config_writer.update_config.assert_called_with(
216+
{'__section__': 'sso-session my-session', 'sso_region': 'us-west-2'},
217+
'myconfigfile',
218+
)
219+
220+
def test_set_nested_property_in_subsection(self):
221+
set_command = ConfigureSetCommand(self.session, self.config_writer)
222+
set_command(
223+
args=['--services', 'my-services', 's3.endpoint_url', 'http://localhost:4566'],
224+
parsed_globals=None,
225+
)
226+
self.config_writer.update_config.assert_called_with(
227+
{
228+
'__section__': 'services my-services',
229+
's3': {'endpoint_url': 'http://localhost:4566'},
230+
},
231+
'myconfigfile',
232+
)

0 commit comments

Comments
 (0)