Skip to content

Commit dd24e80

Browse files
authored
[Config] az config: Add new config command module (Azure#14436)
1 parent fa4a5f8 commit dd24e80

15 files changed

+328
-5
lines changed

doc/sphinx/azhelpgen/doc_source_map.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"az": "src/azure-cli/azure/cli/command_modules/profile/_help.py",
3+
"config": "src/azure-cli/azure/cli/command_modules/config/_help.py",
34
"configure": "src/azure-cli/azure/cli/command_modules/configure/_help.py",
45
"feedback": "src/azure-cli/azure/cli/command_modules/feedback/_help.py",
56
"login": "src/azure-cli/azure/cli/command_modules/profile/_help.py",

linter_exclusions.yml

+15
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,21 @@ cloud update:
8989
cloud_name:
9090
rule_exclusions:
9191
- no_parameter_defaults_for_update_commands
92+
config set:
93+
parameters:
94+
key_value:
95+
rule_exclusions:
96+
- no_positional_parameters
97+
config get:
98+
parameters:
99+
key:
100+
rule_exclusions:
101+
- no_positional_parameters
102+
config unset:
103+
parameters:
104+
key:
105+
rule_exclusions:
106+
- no_positional_parameters
92107
cosmosdb collection create:
93108
parameters:
94109
db_resource_group_name:

src/azure-cli-core/azure/cli/core/util.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,7 @@ def _log_response(response, **kwargs):
895895
return response
896896

897897

898-
class ConfiguredDefaultSetter:
898+
class ScopedConfig:
899899

900900
def __init__(self, cli_config, use_local_config=None):
901901
self.use_local_config = use_local_config
@@ -912,6 +912,9 @@ def __exit__(self, exc_type, exc_val, exc_tb):
912912
setattr(self.cli_config, 'use_local_config', self.original_use_local_config)
913913

914914

915+
ConfiguredDefaultSetter = ScopedConfig
916+
917+
915918
def _ssl_context():
916919
if sys.version_info < (3, 4) or (in_cloud_console() and platform.system() == 'Windows'):
917920
try:

src/azure-cli-core/setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
'colorama~=0.4.1',
5757
'humanfriendly>=4.7,<9.0',
5858
'jmespath',
59-
'knack==0.7.1',
59+
'knack==0.7.2',
6060
'msal~=1.0.0',
6161
'msal-extensions~=0.1.3',
6262
'msrest>=0.4.4',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from azure.cli.core import AzCommandsLoader
7+
8+
import azure.cli.command_modules.config._help # pylint: disable=unused-import
9+
10+
11+
class ConfigCommandsLoader(AzCommandsLoader):
12+
13+
def __init__(self, cli_ctx=None):
14+
super(ConfigCommandsLoader, self).__init__(cli_ctx=cli_ctx)
15+
16+
def load_command_table(self, args):
17+
from azure.cli.command_modules.config.commands import load_command_table
18+
load_command_table(self, args)
19+
return self.command_table
20+
21+
def load_arguments(self, command):
22+
from azure.cli.command_modules.config._params import load_arguments
23+
load_arguments(self, command)
24+
25+
26+
COMMAND_LOADER_CLS = ConfigCommandsLoader
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from knack.help_files import helps # pylint: disable=unused-import
7+
# pylint: disable=line-too-long, too-many-lines
8+
9+
helps['config'] = """
10+
type: group
11+
short-summary: Manage Azure CLI configuration.
12+
"""
13+
14+
helps['config set'] = """
15+
type: command
16+
short-summary: Set a configuration.
17+
long-summary: |
18+
For available configuration options, see https://docs.microsoft.com/en-us/cli/azure/azure-cli-configuration.
19+
By default without specifying --local, the configuration will be saved to `~/.azure/config`.
20+
examples:
21+
- name: Disable color with `core.no_color`.
22+
text: az config set core.no_color=true
23+
- name: Hide warnings and only show errors with `core.only_show_errors`.
24+
text: az config set core.only_show_errors=true
25+
- name: Turn on client-side telemetry.
26+
text: az config set core.collect_telemetry=true
27+
- name: Turn on file logging and set its location.
28+
text: |-
29+
az config set logging.enable_log_file=true
30+
az config set logging.log_dir=~/az-logs
31+
- name: Set the default location to `westus2` and default resource group to `myRG`.
32+
text: az config set defaults.location=westus2 defaults.group=MyResourceGroup
33+
- name: Set the default resource group to `myRG` on a local scope.
34+
text: az config set defaults.group=myRG --local
35+
"""
36+
37+
helps['config get'] = """
38+
type: command
39+
short-summary: Get a configuration.
40+
examples:
41+
- name: Get all configurations.
42+
text: az config get
43+
- name: Get configurations in `core` section.
44+
text: az config get core
45+
- name: Get the configuration of key `core.no_color`.
46+
text: az config get core.no_color
47+
"""
48+
49+
helps['config unset'] = """
50+
type: command
51+
short-summary: Unset a configuration.
52+
examples:
53+
- name: Unset the configuration of key `core.no_color`.
54+
text: az config unset core.no_color
55+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
# pylint: disable=line-too-long
7+
8+
9+
def load_arguments(self, _):
10+
with self.argument_context('config') as c:
11+
c.ignore('_subscription') # ignore the global subscription param
12+
13+
with self.argument_context('config set') as c:
14+
c.positional('key_value', nargs='+', help="Space-separated configurations in the form of <section>.<key>=<value>.")
15+
c.argument('local', action='store_true', help='Set as a local configuration in the working directory.')
16+
17+
with self.argument_context('config get') as c:
18+
c.positional('key', nargs='?', help='The configuration to get. '
19+
'If not provided, all sections and configurations will be listed. '
20+
'If `section` is provided, all configurations under the specified section will be listed. '
21+
'If `<section>.<key>` is provided, only the corresponding configuration is shown.')
22+
c.argument('local', action='store_true',
23+
help='Include local configuration. Scan from the working directory up to the root drive, then the global configuration '
24+
'and return the first occurrence.')
25+
26+
with self.argument_context('config unset') as c:
27+
c.positional('key', nargs='+', help='The configuration to unset, in the form of <section>.<key>.')
28+
c.argument('local', action='store_true',
29+
help='Include local configuration. Scan from the working directory up to the root drive, then the global configuration '
30+
'and unset the first occurrence.')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from azure.cli.core.commands import CliCommandType
7+
8+
9+
def load_command_table(self, _):
10+
11+
config_custom = CliCommandType(operations_tmpl='azure.cli.command_modules.config.custom#{}')
12+
13+
with self.command_group('config', config_custom, is_experimental=True) as g:
14+
g.command('set', 'config_set')
15+
g.command('get', 'config_get')
16+
g.command('unset', 'config_unset')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
from knack.log import get_logger
7+
from knack.util import CLIError
8+
9+
from azure.cli.core.util import ScopedConfig
10+
11+
logger = get_logger(__name__)
12+
13+
14+
def _normalize_config_value(value):
15+
if value:
16+
value = '' if value in ["''", '""'] else value
17+
return value
18+
19+
20+
def config_set(cmd, key_value=None, local=False):
21+
if key_value:
22+
with ScopedConfig(cmd.cli_ctx.config, local):
23+
for kv in key_value:
24+
# core.no_color=true
25+
parts = kv.split('=', 1)
26+
if len(parts) == 1:
27+
raise CLIError('usage error: [section].[name]=[value] ...')
28+
key = parts[0]
29+
value = parts[1]
30+
31+
# core.no_color
32+
parts = key.split('.', 1)
33+
if len(parts) == 1:
34+
raise CLIError('usage error: [section].[name]=[value] ...')
35+
section = parts[0]
36+
name = parts[1]
37+
38+
cmd.cli_ctx.config.set_value(section, name, _normalize_config_value(value))
39+
40+
41+
def config_get(cmd, key=None, local=False):
42+
# No arg. List all sections and all items
43+
if not key:
44+
with ScopedConfig(cmd.cli_ctx.config, local):
45+
sections = cmd.cli_ctx.config.sections()
46+
result = {}
47+
for section in sections:
48+
items = cmd.cli_ctx.config.items(section)
49+
result[section] = items
50+
return result
51+
52+
parts = key.split('.', 1)
53+
if len(parts) == 1:
54+
# Only section is provided
55+
section = key
56+
name = None
57+
else:
58+
# section.name
59+
section = parts[0]
60+
name = parts[1]
61+
62+
with ScopedConfig(cmd.cli_ctx.config, local):
63+
items = cmd.cli_ctx.config.items(section)
64+
65+
if not name:
66+
# Only section
67+
return items
68+
69+
# section.option
70+
try:
71+
return next(x for x in items if x['name'] == name)
72+
except StopIteration:
73+
raise CLIError("Configuration '{}' is not set.".format(key))
74+
75+
76+
def config_unset(cmd, key=None, local=False):
77+
for k in key:
78+
# section.name
79+
parts = k.split('.', 1)
80+
81+
if len(parts) == 1:
82+
raise CLIError("usage error: [section].[name]")
83+
84+
section = parts[0]
85+
name = parts[1]
86+
87+
with ScopedConfig(cmd.cli_ctx.config, local):
88+
cmd.cli_ctx.config.remove_option(section, name)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
import os
6+
import tempfile
7+
import unittest
8+
from unittest.mock import MagicMock
9+
10+
from azure.cli.testsdk import ScenarioTest, LocalContextScenarioTest
11+
from knack.util import CLIError
12+
13+
14+
class ConfigTest(ScenarioTest):
15+
16+
def test_config(self):
17+
18+
# [test_section1]
19+
# test_option1 = test_value1
20+
#
21+
# [test_section2]
22+
# test_option21 = test_value21
23+
# test_option22 = test_value22
24+
25+
# C:\Users\{username}\AppData\Local\Temp
26+
tempdir = tempfile.gettempdir()
27+
original_path = os.getcwd()
28+
os.chdir(tempdir)
29+
print("Using temp dir: {}".format(tempdir))
30+
31+
global_test_args = {"source": os.path.expanduser(os.path.join('~', '.azure', 'config')), "flag": ""}
32+
local_test_args = {"source": os.path.join(tempdir, '.azure', 'config'), "flag": " --local"}
33+
34+
for args in (global_test_args, local_test_args):
35+
test_option1_expected = {'name': 'test_option1', 'source': args["source"], 'value': 'test_value1'}
36+
test_option21_expected = {'name': 'test_option21', 'source': args["source"], 'value': 'test_value21'}
37+
test_option22_expected = {'name': 'test_option22', 'source': args["source"], 'value': 'test_value22'}
38+
39+
test_section1_expected = [test_option1_expected]
40+
test_section2_expected = [test_option21_expected, test_option22_expected]
41+
42+
# 1. set
43+
# Test setting one option
44+
self.cmd('config set test_section1.test_option1=test_value1' + args['flag'])
45+
# Test setting multiple options
46+
self.cmd('config set test_section2.test_option21=test_value21 test_section2.test_option22=test_value22' + args['flag'])
47+
48+
# 2. get
49+
# 2.1 Test get all sections
50+
output = self.cmd('config get' + args['flag']).get_output_in_json()
51+
self.assertListEqual(output['test_section1'], test_section1_expected)
52+
self.assertListEqual(output['test_section2'], test_section2_expected)
53+
54+
# 2.2 Test get one section
55+
output = self.cmd('config get test_section1' + args['flag']).get_output_in_json()
56+
self.assertListEqual(output, test_section1_expected)
57+
output = self.cmd('config get test_section2' + args['flag']).get_output_in_json()
58+
self.assertListEqual(output, test_section2_expected)
59+
60+
# 2.3 Test get one item
61+
output = self.cmd('config get test_section1.test_option1' + args['flag']).get_output_in_json()
62+
self.assertDictEqual(output, test_option1_expected)
63+
output = self.cmd('config get test_section2.test_option21' + args['flag']).get_output_in_json()
64+
self.assertDictEqual(output, test_option21_expected)
65+
output = self.cmd('config get test_section2.test_option22' + args['flag']).get_output_in_json()
66+
self.assertDictEqual(output, test_option22_expected)
67+
68+
with self.assertRaises(CLIError):
69+
self.cmd('config get test_section1.test_option22' + args['flag'])
70+
71+
# 3. unset
72+
# Test unsetting one option
73+
self.cmd('config unset test_section1.test_option1' + args['flag'])
74+
# Test unsetting multiple options
75+
self.cmd('config unset test_section2.test_option21 test_section2.test_option22' + args['flag'])
76+
77+
os.chdir(original_path)
78+
79+
80+
if __name__ == '__main__':
81+
unittest.main()

src/azure-cli/requirements.py3.Darwin.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ isodate==0.6.0
9999
Jinja2==2.10.1
100100
jmespath==0.9.5
101101
jsmin==2.2.2
102-
knack==0.7.1
102+
knack==0.7.2
103103
MarkupSafe==1.1.1
104104
mock==4.0.2
105105
msrestazure==0.6.3

src/azure-cli/requirements.py3.Linux.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ isodate==0.6.0
9999
Jinja2==2.10.1
100100
jmespath==0.9.5
101101
jsmin==2.2.2
102-
knack==0.7.1
102+
knack==0.7.2
103103
MarkupSafe==1.1.1
104104
mock==4.0.2
105105
msrest==0.6.9

src/azure-cli/requirements.py3.windows.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ isodate==0.6.0
9797
Jinja2==2.10.1
9898
jmespath==0.9.5
9999
jsmin==2.2.2
100-
knack==0.7.1
100+
knack==0.7.2
101101
MarkupSafe==1.1.1
102102
mock==4.0.2
103103
msrest==0.6.9

0 commit comments

Comments
 (0)