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

[Draft] Adds nested Edge hierarchy creation command #580

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ __pycache__/

# Virtual environment
env/
env27/
env36/
env2*/
env3*/
.python-version

# PTVS analysis
Expand Down
18 changes: 18 additions & 0 deletions azext_iot/common/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,24 @@ def process_json_arg(content: str, argument_name: str = "content", preserve_orde
)


def process_yaml_arg(path: str) -> dict:
"""Primary processor of yaml file input"""

if not os.path.exists(path):
raise FileOperationError(f"YAML file not found - Please ensure the path '{path}' is correct.")

try:
import yaml
with open(path, "rb") as f:
return yaml.load(f, Loader=yaml.SafeLoader)
except Exception as ex:
raise InvalidArgumentValueError(
"Failed to parse yaml for file '{}' with exception:\n{}".format(
path, ex
)
)


def shell_safe_json_parse(json_or_dict_string, preserve_order=False):
"""Allows the passing of JSON or Python dictionary strings. This is needed because certain
JSON strings in CMD shell are not received in main's argv. This allows the user to specify
Expand Down
29 changes: 29 additions & 0 deletions azext_iot/iothub/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,32 @@ def load_iothub_help():
type: command
short-summary: Upload a local file as a device to a pre-configured blob storage container.
"""

helps["iot edge hierarchy"] = """
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should double-check the official terminology for this topic to ensure alignment...I've seen both "hierarchy" and "topology" being used.

type: group
short-summary: Commands to deploy and manage nested IoT Edge device hierarchies.
"""

helps["iot edge hierarchy create"] = """
type: command
short-summary: |
Deploy nested edge hierarchies to an IoT Hub. This can be done with inline
device arguments or by supplying a nested edge configuration file in YAML or JSON format.
Review examples and parameter descriptions for details on how to fully utilize the operation.

examples:
- name: Delete all existing devices in the Hub, and create a simple nested edge device hierarchy
containing 2 parent devices with 1 child device each. Also specifies module deployments
to configure the devices and visualizes the operations as they progress.
text: |
az iot edge hierarchy create -n {hub_name} --clean --visualize
--device id=parent1 deployment=/path/to/parentDeployment1.json
--device id=child1 parent=parent1 deployment=/path/to/child_deployment1.json
--device id=parent2 deployment=/path/to/parentDeployment2.json
--device id=child2 parent=parent2 deployment=/path/to/child_deployment2.json

- name: Create a new nested edge device hierarchy (without deleting existing devices)
from a configuration file without any output / visualization.
text: >
az iot edge hierarchy create -n {hub_name} --cfg path/to/config_yml_or_json
"""
16 changes: 14 additions & 2 deletions azext_iot/iothub/command_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
device_messaging_ops = CliCommandType(
operations_tmpl="azext_iot.iothub.commands_device_messaging#{}"
)
device_identity_ops = CliCommandType(
operations_tmpl="azext_iot.iothub.commands_device_identity#{}"
)


def load_iothub_commands(self, _):
Expand All @@ -28,12 +31,16 @@ def load_iothub_commands(self, _):
cmd_group.command("list", "job_list")
cmd_group.command("cancel", "job_cancel")

with self.command_group("iot hub digital-twin", command_type=pnp_runtime_ops) as cmd_group:
with self.command_group(
"iot hub digital-twin", command_type=pnp_runtime_ops
) as cmd_group:
cmd_group.command("invoke-command", "invoke_device_command")
cmd_group.show_command("show", "get_digital_twin")
cmd_group.command("update", "patch_digital_twin")

with self.command_group("iot device", command_type=device_messaging_ops) as cmd_group:
with self.command_group(
"iot device", command_type=device_messaging_ops
) as cmd_group:
cmd_group.command("send-d2c-message", "iot_device_send_message")
cmd_group.command("simulate", "iot_simulate_device", is_experimental=True)
cmd_group.command("upload-file", "iot_device_upload_file")
Expand All @@ -47,3 +54,8 @@ def load_iothub_commands(self, _):
cmd_group.command("receive", "iot_c2d_message_receive")
cmd_group.command("send", "iot_c2d_message_send")
cmd_group.command("purge", "iot_c2d_message_purge")

with self.command_group(
"iot edge hierarchy", command_type=device_identity_ops
) as cmd_group:
cmd_group.command("create", "iot_edge_hierarchy_create", is_experimental=True)
59 changes: 59 additions & 0 deletions azext_iot/iothub/commands_device_identity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# coding=utf-8
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from typing import List, Optional
from azext_iot.iothub.providers.device_identity import DeviceIdentityProvider
from knack.log import get_logger

logger = get_logger(__name__)


def iot_edge_hierarchy_create(
cmd,
devices: Optional[List[List[str]]] = None,
config_file: Optional[str] = None,
visualize: Optional[bool] = False,
clean: Optional[bool] = False,
hub_name: Optional[str] = None,
resource_group_name: Optional[str] = None,
login: Optional[str] = None,
auth_type_dataplane: Optional[str] = None,
):
device_identity_provider = DeviceIdentityProvider(
cmd=cmd,
hub_name=hub_name,
rg=resource_group_name,
login=login,
auth_type_dataplane=auth_type_dataplane,
)
return device_identity_provider.create_edge_hierarchy(
devices=devices,
config_file=config_file,
clean=clean,
visualize=visualize,
)


def iot_delete_devices(
cmd,
device_ids: List[str],
confirm: Optional[bool] = True,
hub_name: Optional[str] = None,
resource_group_name: Optional[str] = None,
login: Optional[str] = None,
auth_type_dataplane: Optional[str] = None,
):
device_identity_provider = DeviceIdentityProvider(
cmd=cmd,
hub_name=hub_name,
rg=resource_group_name,
login=login,
auth_type_dataplane=auth_type_dataplane,
)
return device_identity_provider.delete_device_identities(
device_ids=device_ids,
confirm=confirm
)
28 changes: 28 additions & 0 deletions azext_iot/iothub/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,31 @@ def load_iothub_arguments(self, _):
options_list=["--content-type", "--ct"],
help="MIME Type of file.",
)

with self.argument_context("iot edge hierarchy") as context:
context.argument(
"devices",
options_list=["--device", "-d"],
nargs="+",
action="append",
help="Space-separated key=value pairs corresponding to properties of the edge device to create. "
"--device can be used 1 or more times. "
)
context.argument(
"clean",
options_list=["--clean", "-c"],
arg_type=get_three_state_flag(),
help="Deletes all devices in target hub before creating new hierarchy."
)
context.argument(
"visualize",
options_list=["--visualize", "--vis", "-v"],
arg_type=get_three_state_flag(),
help="Shows visualizations of device hierarchy and progress of various tasks "
"(device creation, setting parents, updating configs, etc)."
)
context.argument(
"config_file",
options_list=["--config-file", "--config", "--cfg"],
help="Path to device hierarchy config file"
)
Loading