Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add narg kvp to configuration/deployment create and create inline json wiki page #610

Merged
merged 10 commits into from
Jan 18, 2023
Merged
14 changes: 9 additions & 5 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ unreleased
* Modification of how route testing works for testing all route sources. If `az iot hub message-route test` is called
without specifying a route name or type, all types will be tested rather than only DeviceMessage routes.

* Addition of new parameters is introduced in `az iot hub configuration create` and
`az iot edge deployment create` to let user insert labels and metrics in key-value pair style

Elsie4ever marked this conversation as resolved.
Show resolved Hide resolved

**Digital Twins updates**

Expand All @@ -48,6 +51,12 @@ unreleased
If an output path is specified, this command will also create tar files containing each device's certificate bundle, an IoT Edge
`config.toml` config file and an installation script to configure a target Edge device with these settings.


**IoT DPS updates**

* Removed file extension restriction for attached certificates in individual enrollments and enrollment groups creation/update commands,
and added suggested certificate format in `--help` docs.

0.18.3
+++++++++++++++

Expand All @@ -69,11 +78,6 @@ unreleased

* Fixed an issue with enrollement group certificate encoding

**IoT DPS updates**

* Removed file extension restriction for attached certificates in individual enrollments and enrollment groups creation/update commands,
and added suggested certificate format in `--help` docs.

0.18.2
+++++++++++++++

Expand Down
17 changes: 15 additions & 2 deletions azext_iot/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@
text: >
az iot hub configuration create -c {config_name} -n {iothub_name} --content device_content.json
--target-condition "tags.building=9 and tags.environment='test'" --priority 3
- name: Create a device configuration with labels and provide user metrics inline (bash syntax example)
- name: Create a device configuration with labels and provide user metrics inline (bash syntax example).
text: >
az iot hub configuration create -c {config_name} -n {iothub_name} --content device_content.json
--target-condition "tags.building=9" --labels '{"key0":"value0", "key1":"value1"}' --priority 10
Expand All @@ -758,12 +758,17 @@
az iot hub configuration create -c {config_name} -n {iothub_name} --content module_content.json
--target-condition "from devices.modules where tags.building=9" --labels "{\\"key0\\":\\"value0\\", \\"key1\\":\\"value1\\"}"
--metrics "{\\"metrics\\": {\\"queries\\": {\\"mymetric\\": \\"select moduleId from devices.modules where tags.location='US'\\"}}}"
- name: Create a module configuration with content and user metrics inline (powershell syntax example)
- name: Create a module configuration with content and user metrics inline (powershell syntax example).
text: >
az iot hub configuration create -c {config_name} -n {iothub_name}
--content '{\\"moduleContent\\": {\\"properties.desired.chillerWaterSettings\\": {\\"temperature\\": 38, \\"pressure\\": 78}}}'
--target-condition "from devices.modules where tags.building=9" --priority 1
--metrics '{\\"metrics\\": {\\"queries\\": {\\"mymetric\\":\\"select moduleId from devices.modules where tags.location=''US''\\"}}}'
- name: Create a device configuration with an alternative input style of labels and metrics (shell agnostic).
text: >
az iot hub configuration create -c {config_name} -n {iothub_name} --content device_content.json
--target-condition "from devices.modules where tags.building=9" --custom-labels key0="value0" key1="value1" --priority 10
--custom-metric-queries mymetric1="select deviceId from devices where tags.location='US'" mymetric2="select *"
"""

helps[
Expand Down Expand Up @@ -1096,6 +1101,14 @@
--target-condition "tags.building=9 and tags.environment='test'"
--metrics metrics_content.json
--layered
- name: Create a layered deployment with an alternative input style of labels and metrics (shell agnostic)
text: >
az iot edge deployment create -d {deployment_name} -n {iothub_name}
--content layered_modules_content.json
--target-condition "tags.building=9 and tags.environment='test'"
--custom-labels key0="value0" key1="value1"
--custom-metric-queries mymetric1="select deviceId from devices where tags.location='US'" mymetric2="select *"
--layered
"""

helps[
Expand Down
24 changes: 22 additions & 2 deletions azext_iot/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,22 @@ def load_arguments(self, _):
" from the supplied symmetric key without further validation. All other command parameters aside from"
" duration will be ignored. Supported connection string types: Iot Hub, Device, Module."
)
context.argument(
"custom_metric_queries",
nargs="+",
options_list=["--custom-metric-queries", "--cmq"],
help="An alternative input style (space separated key=value pairs) for --metrics and intended to replace "
"it in the future."
'For example: metric1="select deviceId from devices where tags.location=''US''" metric2="select *"',
)
context.argument(
"custom_labels",
nargs="+",
options_list=["--custom-labels", "--cl"],
help="An alternative input style (space separated key=value pairs) for --labels and intended to replace "
"it in the future."
'For example: key1=value1 key2="this is my value"',
)

with self.argument_context("iot hub") as context:
context.argument(
Expand Down Expand Up @@ -805,12 +821,14 @@ def load_arguments(self, _):
context.argument(
"metrics",
options_list=["--metrics", "-m"],
help="Device configuration metric definitions. Provide file path or raw json.",
help="Device configuration metric definitions. Provide file path or raw json."
"Using --custom-metric-queries instead of --metrics is recommended.",
)
context.argument(
"labels",
options_list=["--labels", "--lab"],
help="Map of labels to be applied to target configuration. "
"Using --custom-labels instead of --labels is recommended."
'Format example: {"key0":"value0", "key1":"value1"}',
)
context.argument(
Expand Down Expand Up @@ -845,12 +863,14 @@ def load_arguments(self, _):
context.argument(
"metrics",
options_list=["--metrics", "-m"],
help="IoT Edge deployment metric definitions. Provide file path or raw json.",
help="IoT Edge deployment metric definitions. Provide file path or raw json."
"Using --custom-metric-queries instead of --metrics is recommended.",
)
context.argument(
"labels",
options_list=["--labels", "--lab"],
help="Map of labels to be applied to target deployment. "
"Using --custom-labels instead of --labels is recommended."
'Use the following format: \'{"key0":"value0", "key1":"value1"}\'',
)
context.argument(
Expand Down
9 changes: 2 additions & 7 deletions azext_iot/deviceupdate/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,13 +842,8 @@ def load_deviceupdate_help():
`--related-file`. Review examples and parameter descriptions for details on how
to fully utilize the operation.

- For bash inline json format use '{"key":"value"}' and \\ (backslash) for command continuation.
- For powershell inline json format use '{\\"key\\":\\"value\\"}' and ` (tilde) for command continuation.
- For cmd inline json format use \"{\\"key\\":\\"value\\"}\" and ^ (caret) for command continuation.
- For file based json input use "@/path/to/file". File based input avoids shell quotation issues.

For a detailed explanation of shell quoting rules please goto
https://learn.microsoft.com/en-us/cli/azure/use-cli-effectively
Read more about using quotation marks and escape characters in different shells here:
https://aka.ms/aziotcli-json

examples:
- name: Initialize a minimum content import manifest. Inline json optimized for `bash`.
Expand Down
15 changes: 15 additions & 0 deletions azext_iot/operations/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
)
from azext_iot.iothub.providers.discovery import IotHubDiscovery
from azext_iot.common.utility import (
assemble_nargs_to_dict,
handle_service_exception,
read_file_content,
init_monitoring,
Expand Down Expand Up @@ -1188,6 +1189,8 @@ def iot_edge_deployment_create(
cmd,
config_id,
content,
custom_labels=None,
custom_metric_queries=None,
hub_name=None,
target_condition="",
priority=0,
Expand All @@ -1205,6 +1208,8 @@ def iot_edge_deployment_create(
cmd=cmd,
config_id=config_id,
content=content,
custom_labels=custom_labels,
custom_metric_queries=custom_metric_queries,
hub_name=hub_name,
target_condition=target_condition,
priority=priority,
Expand All @@ -1221,6 +1226,8 @@ def iot_hub_configuration_create(
cmd,
config_id,
content,
custom_labels=None,
custom_metric_queries=None,
hub_name=None,
target_condition="",
priority=0,
Expand All @@ -1234,6 +1241,8 @@ def iot_hub_configuration_create(
cmd=cmd,
config_id=config_id,
content=content,
custom_labels=custom_labels,
custom_metric_queries=custom_metric_queries,
hub_name=hub_name,
target_condition=target_condition,
priority=priority,
Expand All @@ -1251,6 +1260,8 @@ def _iot_hub_configuration_create(
config_id,
content,
config_type,
custom_labels=None,
custom_metric_queries=None,
hub_name=None,
target_condition="",
priority=0,
Expand Down Expand Up @@ -1302,9 +1313,13 @@ def _iot_hub_configuration_create(
"metrics json must include the '{}' property".format(metrics_key)
)
metrics = metrics[metrics_key]
elif custom_metric_queries:
metrics = assemble_nargs_to_dict(custom_metric_queries)

if labels:
labels = process_json_arg(labels, argument_name="labels")
elif custom_labels:
labels = assemble_nargs_to_dict(custom_labels)

config_content = ConfigurationContent(**processed_content)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"mymetric1": "select deviceId from devices where tags.location='US'",
"mymetric2": "select *"
}
82 changes: 79 additions & 3 deletions azext_iot/tests/iothub/configurations/test_iot_config_int.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
__file__, "test_edge_deployment_malformed.json"
)
generic_metrics_path = get_context_path(__file__, "test_config_generic_metrics.json")
kvp_metrics_path = get_context_path(__file__, "test_config_kvp_metrics.json")
adm_content_module_path = get_context_path(__file__, "test_adm_module_content.json")
adm_content_device_path = get_context_path(__file__, "test_adm_device_content.json")

Expand Down Expand Up @@ -83,7 +84,7 @@ def test_edge_set_modules(self):

def test_edge_deployments(self):
for auth_phase in DATAPLANE_AUTH_TYPES:
config_count = 5
config_count = 6
config_ids = self.generate_config_names(config_count)

self.kwargs["generic_metrics"] = read_file_content(generic_metrics_path)
Expand All @@ -96,6 +97,8 @@ def test_edge_deployments(self):
edge_content_malformed_path
)
self.kwargs["labels"] = '{"key0": "value0"}'
self.kwargs["custom_labels"] = '{"key0": "value0", "key1": "value1"}'
self.kwargs["custom_metric_queries"] = read_file_content(kvp_metrics_path)

priority = random.randint(1, 10)
condition = "tags.building=9 and tags.environment='test'"
Expand Down Expand Up @@ -165,6 +168,41 @@ def test_edge_deployments(self):
],
)

# Metrics + labels using narg kvp parameters. Configurations must be lowercase and will be lower()'ed.
# Note: $schema is included as a nested property in the sample content.
self.cmd(
self.set_cmd_auth_type(
"""iot edge deployment create -d {} --pri {} --tc \"{}\" --cl {} -k '{}'
--cmq {} -n {} -g {}""".format(
config_ids[5],
priority,
condition,
"key0=value0 key1=value1",
edge_content_path,
'mymetric1="select deviceId from devices where tags.location=\'US\'" mymetric2="select *"',
self.entity_name,
self.entity_rg
),
auth_type=auth_phase
),
checks=[
self.check("id", config_ids[5]),
self.check("priority", priority),
self.check("targetCondition", condition),
self.check("labels", json.loads(self.kwargs["custom_labels"])),
self.check(
"content.modulesContent",
json.loads(self.kwargs["edge_content"])["content"][
"modulesContent"
],
),
self.check(
"metrics.queries",
json.loads(self.kwargs["custom_metric_queries"]),
),
],
)

# Layered deployment with content + metrics from file.
# No labels, target-condition or priority
self.cmd(
Expand Down Expand Up @@ -433,7 +471,7 @@ def test_edge_deployments(self):
self.tearDown()

def test_device_configurations(self):
config_count = 3
config_count = 4
config_ids = self.generate_config_names(config_count)
edge_config_ids = self.generate_config_names(1, True)

Expand All @@ -442,7 +480,8 @@ def test_device_configurations(self):
self.kwargs["adm_content_module"] = read_file_content(adm_content_module_path)
self.kwargs["edge_content"] = read_file_content(edge_content_path)
self.kwargs["labels"] = '{"key0": "value0"}'

self.kwargs["custom_labels"] = '{"key0": "value0", "key1": "this is value"}'
self.kwargs["custom_metric_queries"] = read_file_content(kvp_metrics_path)
priority = random.randint(1, 10)
condition = "tags.building=9 and tags.environment='test'"

Expand Down Expand Up @@ -514,6 +553,43 @@ def test_device_configurations(self):
],
)

# Metrics + labels using narg kvp parameters.
# Configurations must be lowercase and will be lower()'ed.
# Note: $schema is included as a nested property in the sample content.
self.cmd(
self.set_cmd_auth_type(
"""iot hub configuration create -c {} --pri {} --tc \"{}\" --cl {}
-k '{}' --cmq {} -n {} -g {}"""
.format(
config_ids[3].upper(),
priority,
module_condition,
'key0=value0 key1="this is value"',
adm_content_module_path,
'mymetric1="select deviceId from devices where tags.location=\'US\'" mymetric2="select *"',
self.entity_name,
self.entity_rg
),
auth_type=auth_phase,
),
checks=[
self.check("id", config_ids[3].lower()),
self.check("priority", priority),
self.check("targetCondition", module_condition),
self.check("labels", json.loads(self.kwargs["custom_labels"])),
self.check(
"content.moduleContent",
json.loads(self.kwargs["adm_content_module"])["content"][
"moduleContent"
],
),
self.check(
"metrics.queries",
json.loads(self.kwargs["custom_metric_queries"]),
),
],
)

# Device content + metrics from file.
# Configurations must be lowercase and will be lower()'ed.
# No labels, target-condition or priority
Expand Down
Loading