Skip to content

Commit dc2611d

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 dc2611d

4 files changed

Lines changed: 180 additions & 2 deletions

File tree

awscli/customizations/configure/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@
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 string
1413

1514
from awscli.compat import shlex
1615

1716
NOT_SET = '<not set>'
1817
PREDEFINED_SECTION_NAMES = 'plugins'
18+
# A map between the command line parameter name and the Python object name
19+
# For allowed sub-section types
20+
SUBSECTION_TYPE_ALLOWLIST = {
21+
'sso-session': 'sso_session',
22+
'services': 'services',
23+
}
24+
1925
_WHITESPACE = ' \t'
2026

2127

awscli/customizations/configure/set.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,19 @@
1111
# ANY KIND, either express or implied. See the License for the specific
1212
# language governing permissions and limitations under the License.
1313
import os
14+
import re
1415

1516
from awscli.customizations.commands import BasicCommand
1617
from awscli.customizations.configure.writer import ConfigFileWriter
18+
from awscli.customizations.exceptions import ParamValidationError
19+
from awscli.customizations.utils import validate_mutually_exclusive
1720

18-
from . import PREDEFINED_SECTION_NAMES, profile_to_section
21+
from . import (
22+
PREDEFINED_SECTION_NAMES,
23+
SUBSECTION_TYPE_ALLOWLIST,
24+
get_section_header,
25+
profile_to_section,
26+
)
1927

2028

2129
class ConfigureSetCommand(BasicCommand):
@@ -41,6 +49,20 @@ class ConfigureSetCommand(BasicCommand):
4149
'cli_type_name': 'string',
4250
'positional_arg': True,
4351
},
52+
{
53+
'name': 'sso-session',
54+
'help_text': 'The name of the SSO session sub-section to configure.',
55+
'action': 'store',
56+
'cli_type_name': 'string',
57+
'group_name': 'subsection',
58+
},
59+
{
60+
'name': 'services',
61+
'help_text': 'The name of the services sub-section to configure.',
62+
'action': 'store',
63+
'cli_type_name': 'string',
64+
'group_name': 'subsection',
65+
},
4466
]
4567
# Any variables specified in this list will be written to
4668
# the ~/.aws/credentials file instead of ~/.aws/config.
@@ -60,10 +82,60 @@ def _get_config_file(self, path):
6082
config_path = self._session.get_config_variable(path)
6183
return os.path.expanduser(config_path)
6284

85+
def _get_subsection_from_args(self, args):
86+
# Validate mutual exclusivity of sub-section type parameters
87+
groups = [[param] for param in SUBSECTION_TYPE_ALLOWLIST.values()]
88+
validate_mutually_exclusive(args, *groups)
89+
90+
subsection_name = None
91+
subsection_type = None
92+
93+
for section_type, param_name in SUBSECTION_TYPE_ALLOWLIST.items():
94+
if hasattr(args, param_name):
95+
param_value = getattr(args, param_name)
96+
if param_value is not None:
97+
if not re.match(r"[\w\d_\-/.%@:\+]+", param_value):
98+
raise ParamValidationError(
99+
f"aws: [ERROR]: Invalid value for --{section_type}."
100+
)
101+
subsection_name = param_value
102+
subsection_type = section_type
103+
break
104+
105+
return (subsection_type, subsection_name)
106+
107+
108+
def _set_subsection_property(self, section_type, section_name, varname, value):
109+
if '.' in varname:
110+
parts = varname.split('.')
111+
if len(parts) > 2:
112+
return 0
113+
114+
varname = parts[0]
115+
value = {parts[1]: value}
116+
117+
# Build update dict
118+
updated_config = {
119+
'__section__': get_section_header(section_type, section_name),
120+
varname: value
121+
}
122+
123+
# Write to config file
124+
config_filename = self._get_config_file('config_file')
125+
self._config_writer.update_config(updated_config, config_filename)
126+
127+
return 0
128+
63129
def _run_main(self, args, parsed_globals):
64130
varname = args.varname
65131
value = args.value
66132
profile = 'default'
133+
134+
section_type, section_name = self._get_subsection_from_args(args)
135+
if section_type is not None:
136+
return self._set_subsection_property(section_type, section_name, varname, value)
137+
138+
# Not in a sub-section, continue with previous profile logic.
67139
# Before handing things off to the config writer,
68140
# we need to find out three things:
69141
# 1. What section we're writing to (profile).

tests/functional/configure/test_configure.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,81 @@ 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+
)
376+
302377

303378
class TestConfigureHasArgTable(unittest.TestCase):
304379
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)