From e8b1a7d7d541d921454ba68f46fb48be425ee758 Mon Sep 17 00:00:00 2001 From: prbans Date: Wed, 15 Apr 2020 21:06:42 -0700 Subject: [PATCH 01/13] Adding az cli commands for central devices --- azext_iot/__init__.py | 31 ++++++------ azext_iot/central/providers/__init__.py | 3 +- .../central/providers/device_provider.py | 48 +++++-------------- azext_iot/central/services/device.py | 40 ++++++++++++++-- azext_iot/central/services/device_template.py | 40 +++++++++++++++- 5 files changed, 103 insertions(+), 59 deletions(-) diff --git a/azext_iot/__init__.py b/azext_iot/__init__.py index 4d2cc95d4..fd4538567 100644 --- a/azext_iot/__init__.py +++ b/azext_iot/__init__.py @@ -11,50 +11,47 @@ import azext_iot._help # noqa: F401 -iothub_ops = CliCommandType( - operations_tmpl='azext_iot.operations.hub#{}' -) +iothub_ops = CliCommandType(operations_tmpl="azext_iot.operations.hub#{}") -iothub_ops_job = CliCommandType( - operations_tmpl='azext_iot.iothub.job_commands#{}' -) +iothub_ops_job = CliCommandType(operations_tmpl="azext_iot.iothub.job_commands#{}") iothub_ops_device = CliCommandType( - operations_tmpl='azext_iot.iothub.device_commands#{}' + operations_tmpl="azext_iot.iothub.device_commands#{}" ) iotdps_ops = CliCommandType( - operations_tmpl='azext_iot.operations.dps#{}', - client_factory=iot_service_provisioning_factory + operations_tmpl="azext_iot.operations.dps#{}", + client_factory=iot_service_provisioning_factory, ) -iotcentral_ops = CliCommandType( - operations_tmpl='azext_iot.operations.central#{}' -) +iotcentral_ops = CliCommandType(operations_tmpl="azext_iot.operations.central#{}") iotdigitaltwin_ops = CliCommandType( - operations_tmpl='azext_iot.operations.digitaltwin#{}' + operations_tmpl="azext_iot.operations.digitaltwin#{}" ) -iotpnp_ops = CliCommandType( - operations_tmpl='azext_iot.operations.pnp#{}' -) +iotpnp_ops = CliCommandType(operations_tmpl="azext_iot.operations.pnp#{}") class IoTExtCommandsLoader(AzCommandsLoader): - def __init__(self, cli_ctx=None): super(IoTExtCommandsLoader, self).__init__(cli_ctx=cli_ctx) def load_command_table(self, args): from azext_iot.commands import load_command_table + load_command_table(self, args) from azext_iot.iothub.command_bindings import load_iothub_commands + load_iothub_commands(self, args) + from azext_iot.central.command_map import load_central_commands + + load_central_commands(self, args) return self.command_table def load_arguments(self, command): from azext_iot._params import load_arguments + load_arguments(self, command) diff --git a/azext_iot/central/providers/__init__.py b/azext_iot/central/providers/__init__.py index 21fd79ac1..aba5db625 100644 --- a/azext_iot/central/providers/__init__.py +++ b/azext_iot/central/providers/__init__.py @@ -5,5 +5,6 @@ # -------------------------------------------------------------------------------------------- from .device_provider import CentralDeviceProvider +from .device_template_provider import CentralDeviceTemplateProvider -__all__ = ["CentralDeviceProvider"] +__all__ = ["CentralDeviceProvider", "CentralDeviceTemplateProvider"] diff --git a/azext_iot/central/providers/device_provider.py b/azext_iot/central/providers/device_provider.py index 330caec21..397d27f14 100644 --- a/azext_iot/central/providers/device_provider.py +++ b/azext_iot/central/providers/device_provider.py @@ -24,44 +24,9 @@ def __init__(self, cmd, app_id, token=None): self._cmd = cmd self._app_id = app_id self._token = token - self._device_templates = {} self._devices = {} - def get_device_template( - self, device_id, central_dns_suffix="azureiotcentral.com", - ): - device = self.get_device(device_id, central_dns_suffix) - device_template_urn = device["instanceOf"] - - if not device_template_urn: - raise CLIError( - "No device template urn found for device '{}'".format(device_id) - ) - - # get or add to cache - if ( - device_template_urn not in self._device_templates - or not self._device_templates.get(device_template_urn) - ): - self._device_templates[ - device_template_urn - ] = central_services.device_template.get_device_template( - self._cmd, - device_template_urn, - self._app_id, - self._token, - central_dns_suffix, - ) - - device_template = self._device_templates[device_template_urn] - if not device_template: - raise CLIError( - "No device template for device with id: '{}'.".format(device_id) - ) - - return device_template - - def get_device( + def show_device( self, device_id, central_dns_suffix="azureiotcentral.com", ): if not device_id: @@ -78,3 +43,14 @@ def get_device( raise CLIError("No device found with id: '{}'.".format(device_id)) return device + + def list_devices( + self, central_dns_suffix="azureiotcentral.com", + ): + devices = central_services.device.list_devices( + cmd=self._cmd, app_id=self._app_id, token=self._token + ) + for device in devices: + self._devices[device["id"]] = device + + return self._devices diff --git a/azext_iot/central/services/device.py b/azext_iot/central/services/device.py index de96af8a0..8b580b32d 100644 --- a/azext_iot/central/services/device.py +++ b/azext_iot/central/services/device.py @@ -10,6 +10,8 @@ from knack.util import CLIError from . import _utility as utility +BASE_PATH = "api/preview/devices" + def get_device( cmd, @@ -33,9 +35,7 @@ def get_device( device: dict """ - url = "https://{}.{}/api/preview/devices/{}".format( - app_id, central_dns_suffix, device_id - ) + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_id) headers = utility.get_headers(token, cmd) response = requests.get(url, headers=headers) @@ -46,3 +46,37 @@ def get_device( raise CLIError(body["error"]) return body + + +def list_devices( + cmd, app_id: str, token: str, central_dns_suffix="azureiotcentral.com", +) -> list: + """ + Get device info given a device id + + Args: + cmd: command passed into az + device_id: unique case-sensitive device id, + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + device: dict + """ + + url = "https://{}.{}/{}".format(app_id, central_dns_suffix, BASE_PATH) + headers = utility.get_headers(token, cmd) + + response = requests.get(url, headers=headers) + + body = response.json() + + if "error" in body: + raise CLIError(body["error"]) + + if "value" not in body: + raise CLIError("Value is not present in body: {}".format(body)) + + return body["value"] diff --git a/azext_iot/central/services/device_template.py b/azext_iot/central/services/device_template.py index de239f90a..92fcb19b6 100644 --- a/azext_iot/central/services/device_template.py +++ b/azext_iot/central/services/device_template.py @@ -10,6 +10,8 @@ from knack.util import CLIError from . import _utility as utility +BASE_PATH = "api/preview/deviceTemplates" + def get_device_template( cmd, @@ -32,8 +34,8 @@ def get_device_template( Returns: device: dict """ - url = "https://{}.{}/api/preview/deviceTemplates/{}".format( - app_id, central_dns_suffix, device_template_urn + url = "https://{}.{}/{}/{}".format( + app_id, central_dns_suffix, BASE_PATH, device_template_urn ) headers = utility.get_headers(token, cmd) @@ -45,3 +47,37 @@ def get_device_template( raise CLIError(body["error"]) return body + + +def list_device_templates( + cmd, app_id: str, token: str, central_dns_suffix="azureiotcentral.com", +) -> list: + """ + Get device info given a device id + + Args: + cmd: command passed into az + device_id: unique case-sensitive device id, + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + device: dict + """ + + url = "https://{}.{}/{}".format(app_id, central_dns_suffix, BASE_PATH) + headers = utility.get_headers(token, cmd) + + response = requests.get(url, headers=headers) + + body = response.json() + + if "error" in body: + raise CLIError(body["error"]) + + if "value" not in body: + raise CLIError("Value is not present in body: {}".format(body)) + + return body["value"] From 8e846f1b2a10f64e0d6f07588498806d5065dcdd Mon Sep 17 00:00:00 2001 From: prbans Date: Wed, 15 Apr 2020 21:06:49 -0700 Subject: [PATCH 02/13] Adding az cli commands for central devices --- azext_iot/central/command_map.py | 38 +++++++++++ azext_iot/central/commands_device.py | 18 +++++ azext_iot/central/commands_device_template.py | 18 +++++ azext_iot/central/params.py | 21 ++++++ .../providers/device_template_provider.py | 68 +++++++++++++++++++ 5 files changed, 163 insertions(+) create mode 100644 azext_iot/central/command_map.py create mode 100644 azext_iot/central/commands_device.py create mode 100644 azext_iot/central/commands_device_template.py create mode 100644 azext_iot/central/params.py create mode 100644 azext_iot/central/providers/device_template_provider.py diff --git a/azext_iot/central/command_map.py b/azext_iot/central/command_map.py new file mode 100644 index 000000000..e08fe5df7 --- /dev/null +++ b/azext_iot/central/command_map.py @@ -0,0 +1,38 @@ +# 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. +# -------------------------------------------------------------------------------------------- +# Dev note - think of this as the "router" and all self.command_group as the controllers + +""" +Load CLI commands +""" +from azure.cli.core.commands import CliCommandType + +central_device_ops = CliCommandType( + operations_tmpl="azext_iot.central.commands_device#{}" +) + +central_device_templates_ops = CliCommandType( + operations_tmpl="azext_iot.central.commands_device_template#{}" +) + + +def load_central_commands(self, _): + """ + Load CLI commands + """ + with self.command_group( + "iot central app device", command_type=central_device_ops, is_preview=True, + ) as cmd_group: + cmd_group.command("list", "list_devices") + cmd_group.command("show", "show_device") + + with self.command_group( + "iot central app device-template", + command_type=central_device_templates_ops, + is_preview=True, + ) as cmd_group: + cmd_group.command("list", "list_device_templates") + cmd_group.command("show", "show_device_template") diff --git a/azext_iot/central/commands_device.py b/azext_iot/central/commands_device.py new file mode 100644 index 000000000..d32be3507 --- /dev/null +++ b/azext_iot/central/commands_device.py @@ -0,0 +1,18 @@ +# 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. +# -------------------------------------------------------------------------------------------- +# Dev note - think of this as a controller + +from .providers import CentralDeviceProvider + + +def list_devices(cmd, app_id): + provider = CentralDeviceProvider(cmd, app_id) + return provider.list_devices() + + +def show_device(cmd, app_id, device_id): + provider = CentralDeviceProvider(cmd, app_id) + return provider.show_device(device_id) diff --git a/azext_iot/central/commands_device_template.py b/azext_iot/central/commands_device_template.py new file mode 100644 index 000000000..5ea652295 --- /dev/null +++ b/azext_iot/central/commands_device_template.py @@ -0,0 +1,18 @@ +# 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. +# -------------------------------------------------------------------------------------------- +# Dev note - think of this as a controller + +from .providers import CentralDeviceTemplateProvider + + +def show_device_template(cmd, app_id, device_template_id): + provider = CentralDeviceTemplateProvider(cmd, app_id) + return provider.show_device_template(device_template_id) + + +def list_device_templates(cmd, app_id): + provider = CentralDeviceTemplateProvider(cmd, app_id) + return provider.list_device_templates() diff --git a/azext_iot/central/params.py b/azext_iot/central/params.py new file mode 100644 index 000000000..22f5efaa2 --- /dev/null +++ b/azext_iot/central/params.py @@ -0,0 +1,21 @@ +# coding=utf-8 +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Unpublished works. +# -------------------------------------------------------------------------------------------- + +""" +CLI parameter definitions. +""" + + +def load_digitaltwins_arguments(self, _): + """ + Load CLI Args for Knack parser + """ + with self.argument_context("iot central") as context: + context.argument( + "model_id", + options_list=["--model-id"], + help="ADT Model Id. Example: urn:contosocom:DigitalTwins:Space:1", + ) diff --git a/azext_iot/central/providers/device_template_provider.py b/azext_iot/central/providers/device_template_provider.py new file mode 100644 index 000000000..409b56390 --- /dev/null +++ b/azext_iot/central/providers/device_template_provider.py @@ -0,0 +1,68 @@ +# 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 knack.util import CLIError +from azext_iot.central import services as central_services + +from .device_provider import CentralDeviceProvider + + +class CentralDeviceTemplateProvider: + def __init__(self, cmd, app_id, token=None): + """ + Provider for device/device_template APIs + + Args: + cmd: command passed into az + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + Useful in scenarios where user doesn't own the app + therefore AAD token won't work, but a SAS token generated by owner will + """ + self._cmd = cmd + self._app_id = app_id + self._token = token + self._device_templates = {} + + def show_device_template( + self, device_template_id, central_dns_suffix="azureiotcentral.com", + ): + # get or add to cache + if ( + device_template_id not in self._device_templates + or not self._device_templates.get(device_template_id) + ): + self._device_templates[ + device_template_id + ] = central_services.device_template.get_device_template( + self._cmd, + device_template_id, + self._app_id, + self._token, + central_dns_suffix, + ) + + device_template = self._device_templates[device_template_id] + if not device_template: + raise CLIError( + "No device template for device template with id: '{}'.".format( + device_template_id + ) + ) + + return device_template + + def list_device_templates( + self, central_dns_suffix="azureiotcentral.com", + ): + templates = central_services.device_template.list_device_templates( + cmd=self._cmd, app_id=self._app_id, token=self._token + ) + for template in templates: + self._device_templates[template["id"]] = template + + return self._device_templates From fdf046d9470417070e8d91b30ed25ad479d9c9e4 Mon Sep 17 00:00:00 2001 From: prbans Date: Wed, 15 Apr 2020 21:48:30 -0700 Subject: [PATCH 03/13] Added ability to provision a device --- azext_iot/__init__.py | 9 ++-- azext_iot/central/command_map.py | 1 + azext_iot/central/commands_device.py | 32 +++++++++++-- azext_iot/central/params.py | 24 ++++++++-- .../central/providers/device_provider.py | 35 +++++++++++++- azext_iot/central/services/_utility.py | 9 +++- azext_iot/central/services/device.py | 48 +++++++++++++++++++ 7 files changed, 144 insertions(+), 14 deletions(-) diff --git a/azext_iot/__init__.py b/azext_iot/__init__.py index fd4538567..e09d53fb4 100644 --- a/azext_iot/__init__.py +++ b/azext_iot/__init__.py @@ -39,20 +39,21 @@ def __init__(self, cli_ctx=None): def load_command_table(self, args): from azext_iot.commands import load_command_table - - load_command_table(self, args) from azext_iot.iothub.command_bindings import load_iothub_commands - - load_iothub_commands(self, args) from azext_iot.central.command_map import load_central_commands + load_command_table(self, args) + load_iothub_commands(self, args) load_central_commands(self, args) + return self.command_table def load_arguments(self, command): from azext_iot._params import load_arguments + from azext_iot.central.params import load_central_arguments load_arguments(self, command) + load_central_arguments(self, command) COMMAND_LOADER_CLS = IoTExtCommandsLoader diff --git a/azext_iot/central/command_map.py b/azext_iot/central/command_map.py index e08fe5df7..2e9accb6a 100644 --- a/azext_iot/central/command_map.py +++ b/azext_iot/central/command_map.py @@ -28,6 +28,7 @@ def load_central_commands(self, _): ) as cmd_group: cmd_group.command("list", "list_devices") cmd_group.command("show", "show_device") + cmd_group.command("add", "add_device") with self.command_group( "iot central app device-template", diff --git a/azext_iot/central/commands_device.py b/azext_iot/central/commands_device.py index d32be3507..aeaf15275 100644 --- a/azext_iot/central/commands_device.py +++ b/azext_iot/central/commands_device.py @@ -5,14 +5,40 @@ # -------------------------------------------------------------------------------------------- # Dev note - think of this as a controller +from knack.util import CLIError from .providers import CentralDeviceProvider -def list_devices(cmd, app_id): +def list_devices(cmd, app_id: str, central_dns_suffix="azureiotcentral.com"): provider = CentralDeviceProvider(cmd, app_id) return provider.list_devices() -def show_device(cmd, app_id, device_id): +def show_device( + cmd, app_id: str, device_id: str, central_dns_suffix="azureiotcentral.com" +): provider = CentralDeviceProvider(cmd, app_id) - return provider.show_device(device_id) + return provider.get_device(device_id) + + +def add_device( + cmd, + app_id: str, + device_id: str, + device_name=None, + instance_of=None, + simulated=False, + central_dns_suffix="azureiotcentral.com", +): + if simulated and not instance_of: + raise CLIError( + "Error: if you supply --simulated you must also specify --instance-of" + ) + provider = CentralDeviceProvider(cmd, app_id) + return provider.add_device( + device_id=device_id, + device_name=device_name, + instance_of=instance_of, + simulated=simulated, + central_dns_suffix=central_dns_suffix, + ) diff --git a/azext_iot/central/params.py b/azext_iot/central/params.py index 22f5efaa2..14eb059b2 100644 --- a/azext_iot/central/params.py +++ b/azext_iot/central/params.py @@ -8,14 +8,28 @@ CLI parameter definitions. """ +from azure.cli.core.commands.parameters import get_three_state_flag -def load_digitaltwins_arguments(self, _): + +def load_central_arguments(self, _): """ Load CLI Args for Knack parser """ - with self.argument_context("iot central") as context: + with self.argument_context("iot central app") as context: + context.argument( + "instance_of", + options_list=["--instance-of"], + help="Central model id. Example: urn:ojpkindbz:modelDefinition:iild3tm_uo", + ) + context.argument( + "device_name", + options_list=["--device-name"], + help="Human readable device name. Example: Fridge", + ) context.argument( - "model_id", - options_list=["--model-id"], - help="ADT Model Id. Example: urn:contosocom:DigitalTwins:Space:1", + "simulated", + options_list=["--simulated"], + arg_type=get_three_state_flag(), + help="Add this flag if you would like IoT Central to set this up as a simulated device. " + "--instance-of is required if this is true", ) diff --git a/azext_iot/central/providers/device_provider.py b/azext_iot/central/providers/device_provider.py index 397d27f14..662809112 100644 --- a/azext_iot/central/providers/device_provider.py +++ b/azext_iot/central/providers/device_provider.py @@ -26,7 +26,7 @@ def __init__(self, cmd, app_id, token=None): self._token = token self._devices = {} - def show_device( + def get_device( self, device_id, central_dns_suffix="azureiotcentral.com", ): if not device_id: @@ -54,3 +54,36 @@ def list_devices( self._devices[device["id"]] = device return self._devices + + def add_device( + self, + device_id, + device_name=None, + instance_of=None, + simulated=False, + central_dns_suffix="azureiotcentral.com", + ): + if not device_id: + raise CLIError("Device id must be specified.") + + if device_id in self._devices: + raise CLIError("Device already exists") + + # get or add to cache + if device_id not in self._devices or not self._devices.get(device_id): + self._devices[device_id] = central_services.device.add_device( + cmd=self._cmd, + token=self._token, + app_id=self._app_id, + device_id=device_id, + device_name=device_name, + instance_of=instance_of, + simulated=simulated, + central_dns_suffix=central_dns_suffix, + ) + + device = self._devices[device_id] + if not device: + raise CLIError("No device found with id: '{}'.".format(device_id)) + + return device diff --git a/azext_iot/central/services/_utility.py b/azext_iot/central/services/_utility.py index 5f01196e2..bdb8c2745 100644 --- a/azext_iot/central/services/_utility.py +++ b/azext_iot/central/services/_utility.py @@ -9,9 +9,16 @@ from azext_iot.common import auth -def get_headers(token, cmd): +def get_headers(token, cmd, has_json_payload=False): if not token: aad_token = auth.get_aad_token(cmd, resource="https://apps.azureiotcentral.com") token = "Bearer {}".format(aad_token["accessToken"]) + if has_json_payload: + return { + "Authorization": token, + "User-Agent": constants.USER_AGENT, + "Content-Type": "application/json", + } + return {"Authorization": token, "User-Agent": constants.USER_AGENT} diff --git a/azext_iot/central/services/device.py b/azext_iot/central/services/device.py index 8b580b32d..4b354d374 100644 --- a/azext_iot/central/services/device.py +++ b/azext_iot/central/services/device.py @@ -80,3 +80,51 @@ def list_devices( raise CLIError("Value is not present in body: {}".format(body)) return body["value"] + + +def add_device( + cmd, + token: str, + app_id: str, + device_id: str, + device_name: str, + instance_of: str, + simulated: bool, + central_dns_suffix="azureiotcentral.com", +) -> dict: + """ + Get device info given a device id + + Args: + cmd: command passed into az + device_id: unique case-sensitive device id, + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + device: dict + """ + + if not device_name: + device_name = device_id + + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_id) + headers = utility.get_headers(token, cmd, has_json_payload=True) + body = { + "displayName": device_name, + "simualted": simulated, + "approved": True, + } + if instance_of: + body["instanceOf"] = instance_of + + response = requests.put(url, headers=headers, json=body) + + body = response.json() + + if "error" in body: + raise CLIError(body["error"]) + + return body From 26299f019adffbb5325c41745dfd60f17e011af8 Mon Sep 17 00:00:00 2001 From: prbans Date: Thu, 16 Apr 2020 12:00:37 -0700 Subject: [PATCH 04/13] Added ability to create devices and templates --- azext_iot/central/command_map.py | 4 +- azext_iot/central/commands_device_template.py | 32 +- azext_iot/central/params.py | 10 + .../providers/device_template_provider.py | 52 +++- azext_iot/central/services/device.py | 8 +- azext_iot/central/services/device_template.py | 47 ++- .../json/create_device_template_payload.json | 285 ++++++++++++++++++ 7 files changed, 419 insertions(+), 19 deletions(-) create mode 100644 azext_iot/tests/central/json/create_device_template_payload.json diff --git a/azext_iot/central/command_map.py b/azext_iot/central/command_map.py index 2e9accb6a..7c4c0a456 100644 --- a/azext_iot/central/command_map.py +++ b/azext_iot/central/command_map.py @@ -3,7 +3,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -# Dev note - think of this as the "router" and all self.command_group as the controllers """ Load CLI commands @@ -19,6 +18,7 @@ ) +# Dev note - think of this as the "router" and all self.command_group as the controllers def load_central_commands(self, _): """ Load CLI commands @@ -36,4 +36,6 @@ def load_central_commands(self, _): is_preview=True, ) as cmd_group: cmd_group.command("list", "list_device_templates") + cmd_group.command("map", "map_device_templates") cmd_group.command("show", "show_device_template") + cmd_group.command("add", "add_device_template") diff --git a/azext_iot/central/commands_device_template.py b/azext_iot/central/commands_device_template.py index 5ea652295..142d2c1b7 100644 --- a/azext_iot/central/commands_device_template.py +++ b/azext_iot/central/commands_device_template.py @@ -8,11 +8,35 @@ from .providers import CentralDeviceTemplateProvider -def show_device_template(cmd, app_id, device_template_id): +def show_device_template( + cmd, app_id: str, device_template_id: str, central_dns_suffix="azureiotcentral.com" +): provider = CentralDeviceTemplateProvider(cmd, app_id) - return provider.show_device_template(device_template_id) + return provider.show_device_template( + device_template_id=device_template_id, central_dns_suffix=central_dns_suffix + ) -def list_device_templates(cmd, app_id): +def list_device_templates(cmd, app_id: str, central_dns_suffix="azureiotcentral.com"): provider = CentralDeviceTemplateProvider(cmd, app_id) - return provider.list_device_templates() + return provider.list_device_templates(central_dns_suffix=central_dns_suffix) + + +def map_device_templates(cmd, app_id: str, central_dns_suffix="azureiotcentral.com"): + provider = CentralDeviceTemplateProvider(cmd, app_id) + return provider.map_device_templates(central_dns_suffix=central_dns_suffix) + + +def add_device_template( + cmd, + app_id: str, + device_template_id: str, + file_path: str, + central_dns_suffix="azureiotcentral.com", +): + provider = CentralDeviceTemplateProvider(cmd, app_id) + return provider.add_device_template( + device_template_id=device_template_id, + file_path=file_path, + central_dns_suffix=central_dns_suffix, + ) diff --git a/azext_iot/central/params.py b/azext_iot/central/params.py index 14eb059b2..9b5b1c595 100644 --- a/azext_iot/central/params.py +++ b/azext_iot/central/params.py @@ -33,3 +33,13 @@ def load_central_arguments(self, _): help="Add this flag if you would like IoT Central to set this up as a simulated device. " "--instance-of is required if this is true", ) + context.argument( + "device_template_id", + options_list=["--device-template-id"], + help="Device template id. Example: somedevicetemplate", + ) + context.argument( + "file_path", + options_list=["--file-path", "--fp"], + help="Path to file containing the device template to be added to IoT Central. Example: ./path/to/template.json", + ) diff --git a/azext_iot/central/providers/device_template_provider.py b/azext_iot/central/providers/device_template_provider.py index 409b56390..0783082bf 100644 --- a/azext_iot/central/providers/device_template_provider.py +++ b/azext_iot/central/providers/device_template_provider.py @@ -4,10 +4,12 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- +import json +import os + from knack.util import CLIError from azext_iot.central import services as central_services - -from .device_provider import CentralDeviceProvider +from azext_iot.common import utility class CentralDeviceTemplateProvider: @@ -39,11 +41,11 @@ def show_device_template( self._device_templates[ device_template_id ] = central_services.device_template.get_device_template( - self._cmd, - device_template_id, - self._app_id, - self._token, - central_dns_suffix, + cmd=self._cmd, + app_id=self._app_id, + device_template_id=device_template_id, + token=self._token, + central_dns_suffix=central_dns_suffix, ) device_template = self._device_templates[device_template_id] @@ -66,3 +68,39 @@ def list_device_templates( self._device_templates[template["id"]] = template return self._device_templates + + def map_device_templates( + self, central_dns_suffix="azureiotcentral.com", + ): + templates = central_services.device_template.list_device_templates( + cmd=self._cmd, app_id=self._app_id, token=self._token + ) + return {template["displayName"]: template["id"] for template in templates} + + def add_device_template( + self, + device_template_id: str, + file_path: str, + central_dns_suffix="azureiotcentral.com", + ): + if not os.path.exists(file_path): + raise CLIError('File path "{}" does not exist!'.format(file_path)) + + content = utility.read_file_content(file_path) + try: + payload = json.loads(content) + except Exception: + raise CLIError("File must be json format") + + template = central_services.device_template.add_device_template( + cmd=self._cmd, + app_id=self._app_id, + device_template_id=device_template_id, + payload=payload, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) + + self._device_templates[template["id"]] = template + + return template diff --git a/azext_iot/central/services/device.py b/azext_iot/central/services/device.py index 4b354d374..b8976ee4a 100644 --- a/azext_iot/central/services/device.py +++ b/azext_iot/central/services/device.py @@ -112,15 +112,15 @@ def add_device( url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_id) headers = utility.get_headers(token, cmd, has_json_payload=True) - body = { + payload = { "displayName": device_name, - "simualted": simulated, + "simulated": simulated, "approved": True, } if instance_of: - body["instanceOf"] = instance_of + payload["instanceOf"] = instance_of - response = requests.put(url, headers=headers, json=body) + response = requests.put(url, headers=headers, json=payload) body = response.json() diff --git a/azext_iot/central/services/device_template.py b/azext_iot/central/services/device_template.py index 92fcb19b6..88f8ab126 100644 --- a/azext_iot/central/services/device_template.py +++ b/azext_iot/central/services/device_template.py @@ -8,15 +8,18 @@ import requests from knack.util import CLIError +from knack.log import get_logger from . import _utility as utility +logger = get_logger(__name__) + BASE_PATH = "api/preview/deviceTemplates" def get_device_template( cmd, - device_template_urn: str, app_id: str, + device_template_id: str, token: str, central_dns_suffix="azureiotcentral.com", ) -> dict: @@ -25,7 +28,7 @@ def get_device_template( Args: cmd: command passed into az - device_template_urn: case sensitive device template urn, + device_template_id: case sensitive device template urn, app_id: name of app (used for forming request URL) token: (OPTIONAL) authorization token to fetch device details from IoTC. MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') @@ -35,7 +38,7 @@ def get_device_template( device: dict """ url = "https://{}.{}/{}/{}".format( - app_id, central_dns_suffix, BASE_PATH, device_template_urn + app_id, central_dns_suffix, BASE_PATH, device_template_id ) headers = utility.get_headers(token, cmd) @@ -81,3 +84,41 @@ def list_device_templates( raise CLIError("Value is not present in body: {}".format(body)) return body["value"] + + +def add_device_template( + cmd, + app_id: str, + device_template_id: str, + payload: dict, + token: str, + central_dns_suffix="azureiotcentral.com", +) -> list: + """ + Get device info given a device id + + Args: + cmd: command passed into az + device_id: unique case-sensitive device id, + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs + + Returns: + device: dict + """ + + url = "https://{}.{}/{}/{}".format( + app_id, central_dns_suffix, BASE_PATH, device_template_id + ) + headers = utility.get_headers(token, cmd, has_json_payload=True) + + response = requests.put(url, headers=headers, json=payload) + + body = response.json() + + if "error" in body: + raise CLIError(body["error"]) + + return body diff --git a/azext_iot/tests/central/json/create_device_template_payload.json b/azext_iot/tests/central/json/create_device_template_payload.json new file mode 100644 index 000000000..2e73cf339 --- /dev/null +++ b/azext_iot/tests/central/json/create_device_template_payload.json @@ -0,0 +1,285 @@ +{ + "types": [ + "DeviceModel" + ], + "displayName": "some-device-template", + "capabilityModel": { + "@id": "urn:sampleApp:modelOne_bz:2", + "@type": [ + "CapabilityModel" + ], + "implements": [ + { + "@id": "urn:sampleApp:modelOne_bz:_rpgcmdpo:1", + "@type": [ + "InterfaceInstance" + ], + "displayName": "Interface", + "name": "modelOne_g4", + "schema": { + "@id": "urn:sampleApp:modelOne_g4:1", + "@type": [ + "Interface" + ], + "displayName": "Interface", + "contents": [ + { + "@id": "urn:sampleApp:modelOne_g4:Bool:1", + "@type": [ + "Telemetry" + ], + "displayName": "Bool", + "name": "Bool", + "schema": "boolean" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Date:1", + "@type": [ + "Telemetry" + ], + "displayName": "Date", + "name": "Date", + "schema": "date" + }, + { + "@id": "urn:sampleApp:modelOne_g4:DateTime:1", + "@type": [ + "Telemetry" + ], + "displayName": "DateTime", + "name": "DateTime", + "schema": "dateTime" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Double:1", + "@type": [ + "Telemetry" + ], + "displayName": "Double", + "name": "Double", + "schema": "double" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Duration:1", + "@type": [ + "Telemetry" + ], + "displayName": "Duration", + "name": "Duration", + "schema": "duration" + }, + { + "@id": "urn:sampleApp:modelOne_g4:IntEnum:1", + "@type": [ + "Telemetry" + ], + "displayName": "IntEnum", + "name": "IntEnum", + "schema": { + "@id": "urn:sampleApp:modelOne_g4:IntEnum:pgkbdhard:1", + "@type": [ + "Enum" + ], + "displayName": "Enum", + "valueSchema": "integer", + "enumValues": [ + { + "@id": "urn:sampleApp:modelOne_g4:IntEnum:pgkbdhard:Enum1:1", + "@type": [ + "EnumValue" + ], + "displayName": "Enum1", + "enumValue": 1, + "name": "Enum1" + }, + { + "@id": "urn:sampleApp:modelOne_g4:IntEnum:pgkbdhard:Enum2:1", + "@type": [ + "EnumValue" + ], + "displayName": "Enum2", + "enumValue": 2, + "name": "Enum2" + } + ] + } + }, + { + "@id": "urn:sampleApp:modelOne_g4:StringEnum:1", + "@type": [ + "Telemetry" + ], + "displayName": "StringEnum", + "name": "StringEnum", + "schema": { + "@id": "urn:sampleApp:modelOne_g4:StringEnum:kyesuinpsx:1", + "@type": [ + "Enum" + ], + "displayName": "Enum", + "valueSchema": "string", + "enumValues": [ + { + "@id": "urn:sampleApp:modelOne_g4:StringEnum:kyesuinpsx:EnumA:1", + "@type": [ + "EnumValue" + ], + "displayName": "EnumA", + "enumValue": "A", + "name": "EnumA" + }, + { + "@id": "urn:sampleApp:modelOne_g4:StringEnum:kyesuinpsx:EnumB:1", + "@type": [ + "EnumValue" + ], + "displayName": "EnumB", + "enumValue": "B", + "name": "EnumB" + } + ] + } + }, + { + "@id": "urn:sampleApp:modelOne_g4:Float:1", + "@type": [ + "Telemetry" + ], + "displayName": "Float", + "name": "Float", + "schema": "float" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Geopoint:1", + "@type": [ + "Telemetry" + ], + "displayName": "Geopoint", + "name": "Geopoint", + "schema": "geopoint" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Int:1", + "@type": [ + "Telemetry" + ], + "displayName": "Int", + "name": "Int", + "schema": "integer" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Long:1", + "@type": [ + "Telemetry" + ], + "displayName": "Long", + "name": "Long", + "schema": "long" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Object:1", + "@type": [ + "Telemetry" + ], + "displayName": "Object", + "name": "Object", + "schema": { + "@id": "urn:sampleApp:modelOne_g4:Object:8ot2x5whp8:1", + "@type": [ + "Object" + ], + "displayName": "Object", + "fields": [ + { + "@id": "urn:sampleApp:modelOne_g4:Object:8ot2x5whp8:Double:1", + "@type": [ + "SchemaField" + ], + "displayName": "Double", + "name": "Double", + "schema": "double" + } + ] + } + }, + { + "@id": "urn:sampleApp:modelOne_g4:String:1", + "@type": [ + "Telemetry" + ], + "displayName": "String", + "name": "String", + "schema": "string" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Time:1", + "@type": [ + "Telemetry" + ], + "displayName": "Time", + "name": "Time", + "schema": "time" + }, + { + "@id": "urn:sampleApp:modelOne_g4:Vector:1", + "@type": [ + "Telemetry" + ], + "displayName": "Vector", + "name": "Vector", + "schema": "vector" + } + ] + } + }, + { + "@id": "urn:sampleApp:modelOne_bz:myxqftpsr:2", + "@type": [ + "InterfaceInstance" + ], + "displayName": "Interface", + "name": "modelTwo_ed", + "schema": { + "@id": "urn:sampleApp:modelTwo_ed:1", + "@type": [ + "Interface" + ], + "displayName": "Interface", + "contents": [ + { + "@id": "urn:sampleApp:modelTwo_ed:Bool:1", + "@type": [ + "Telemetry" + ], + "displayName": "Bool", + "name": "Bool", + "schema": "boolean" + }, + { + "@id": "urn:sampleApp:modelTwo_ed:bool:1", + "@type": [ + "Telemetry" + ], + "displayName": "bool", + "name": "bool", + "schema": "boolean" + } + ] + } + } + ], + "displayName": "larger-telemetry-device", + "@context": [ + "http://azureiot.com/v1/contexts/IoTModel.json" + ] + }, + "solutionModel": { + "@id": "urn:d9cltbeus:lz1tl4a_jz", + "@type": [ + "SolutionModel" + ], + "cloudProperties": [], + "initialValues": [], + "overrides": [] + } +} \ No newline at end of file From 950e8733256abbdd08a87313dbd62175f38b3811 Mon Sep 17 00:00:00 2001 From: prbans Date: Thu, 16 Apr 2020 17:45:45 -0700 Subject: [PATCH 05/13] Add integration test for all device commands --- azext_iot/central/command_map.py | 6 +- azext_iot/central/commands_device.py | 11 ++- azext_iot/central/commands_device_template.py | 11 ++- .../central/providers/device_provider.py | 21 +++++- .../providers/device_template_provider.py | 19 ++++- azext_iot/central/services/_utility.py | 18 +++++ azext_iot/central/services/device.py | 51 ++++++++----- azext_iot/central/services/device_template.py | 53 +++++++++----- .../json/create_device_template_payload.json | 2 +- azext_iot/tests/test_iot_central_int.py | 73 +++++++++++++++++-- pytest.ini.example | 1 + 11 files changed, 214 insertions(+), 52 deletions(-) diff --git a/azext_iot/central/command_map.py b/azext_iot/central/command_map.py index 7c4c0a456..f7590c59a 100644 --- a/azext_iot/central/command_map.py +++ b/azext_iot/central/command_map.py @@ -28,7 +28,8 @@ def load_central_commands(self, _): ) as cmd_group: cmd_group.command("list", "list_devices") cmd_group.command("show", "show_device") - cmd_group.command("add", "add_device") + cmd_group.command("create", "create_device") + cmd_group.command("delete", "delete_device") with self.command_group( "iot central app device-template", @@ -38,4 +39,5 @@ def load_central_commands(self, _): cmd_group.command("list", "list_device_templates") cmd_group.command("map", "map_device_templates") cmd_group.command("show", "show_device_template") - cmd_group.command("add", "add_device_template") + cmd_group.command("create", "create_device_template") + cmd_group.command("delete", "delete_device_template") diff --git a/azext_iot/central/commands_device.py b/azext_iot/central/commands_device.py index aeaf15275..d825c7b1b 100644 --- a/azext_iot/central/commands_device.py +++ b/azext_iot/central/commands_device.py @@ -21,7 +21,7 @@ def show_device( return provider.get_device(device_id) -def add_device( +def create_device( cmd, app_id: str, device_id: str, @@ -35,10 +35,17 @@ def add_device( "Error: if you supply --simulated you must also specify --instance-of" ) provider = CentralDeviceProvider(cmd, app_id) - return provider.add_device( + return provider.create_device( device_id=device_id, device_name=device_name, instance_of=instance_of, simulated=simulated, central_dns_suffix=central_dns_suffix, ) + + +def delete_device( + cmd, app_id: str, device_id: str, central_dns_suffix="azureiotcentral.com" +): + provider = CentralDeviceProvider(cmd, app_id) + return provider.delete_device(device_id) diff --git a/azext_iot/central/commands_device_template.py b/azext_iot/central/commands_device_template.py index 142d2c1b7..115fe9e24 100644 --- a/azext_iot/central/commands_device_template.py +++ b/azext_iot/central/commands_device_template.py @@ -27,7 +27,7 @@ def map_device_templates(cmd, app_id: str, central_dns_suffix="azureiotcentral.c return provider.map_device_templates(central_dns_suffix=central_dns_suffix) -def add_device_template( +def create_device_template( cmd, app_id: str, device_template_id: str, @@ -40,3 +40,12 @@ def add_device_template( file_path=file_path, central_dns_suffix=central_dns_suffix, ) + + +def delete_device_template( + cmd, app_id: str, device_template_id: str, central_dns_suffix="azureiotcentral.com" +): + provider = CentralDeviceTemplateProvider(cmd, app_id) + return provider.delete_device_template( + device_template_id=device_template_id, central_dns_suffix=central_dns_suffix + ) diff --git a/azext_iot/central/providers/device_provider.py b/azext_iot/central/providers/device_provider.py index 662809112..d3737dcc3 100644 --- a/azext_iot/central/providers/device_provider.py +++ b/azext_iot/central/providers/device_provider.py @@ -55,7 +55,7 @@ def list_devices( return self._devices - def add_device( + def create_device( self, device_id, device_name=None, @@ -71,7 +71,7 @@ def add_device( # get or add to cache if device_id not in self._devices or not self._devices.get(device_id): - self._devices[device_id] = central_services.device.add_device( + self._devices[device_id] = central_services.device.create_device( cmd=self._cmd, token=self._token, app_id=self._app_id, @@ -87,3 +87,20 @@ def add_device( raise CLIError("No device found with id: '{}'.".format(device_id)) return device + + def delete_device( + self, device_id, central_dns_suffix="azureiotcentral.com", + ): + if not device_id: + raise CLIError("Device id must be specified.") + + # get or add to cache + result = central_services.device.delete_device( + cmd=self._cmd, + token=self._token, + app_id=self._app_id, + device_id=device_id, + central_dns_suffix=central_dns_suffix, + ) + + return result diff --git a/azext_iot/central/providers/device_template_provider.py b/azext_iot/central/providers/device_template_provider.py index 0783082bf..ce86eb9ca 100644 --- a/azext_iot/central/providers/device_template_provider.py +++ b/azext_iot/central/providers/device_template_provider.py @@ -92,7 +92,7 @@ def add_device_template( except Exception: raise CLIError("File must be json format") - template = central_services.device_template.add_device_template( + template = central_services.device_template.create_device_template( cmd=self._cmd, app_id=self._app_id, device_template_id=device_template_id, @@ -104,3 +104,20 @@ def add_device_template( self._device_templates[template["id"]] = template return template + + def delete_device_template( + self, device_template_id, central_dns_suffix="azureiotcentral.com", + ): + if not device_template_id: + raise CLIError("Device template id must be specified.") + + # get or add to cache + result = central_services.device_template.delete_device_template( + cmd=self._cmd, + token=self._token, + app_id=self._app_id, + device_template_id=device_template_id, + central_dns_suffix=central_dns_suffix, + ) + + return result diff --git a/azext_iot/central/services/_utility.py b/azext_iot/central/services/_utility.py index bdb8c2745..2af124d7b 100644 --- a/azext_iot/central/services/_utility.py +++ b/azext_iot/central/services/_utility.py @@ -5,6 +5,9 @@ # -------------------------------------------------------------------------------------------- # Nothing in this file should be used outside of service/central +from knack.util import CLIError +from requests import Response + from azext_iot import constants from azext_iot.common import auth @@ -22,3 +25,18 @@ def get_headers(token, cmd, has_json_payload=False): } return {"Authorization": token, "User-Agent": constants.USER_AGENT} + + +def try_extract_result(response: Response): + if response.status_code in [201, 204]: + return {"result": "success"} + + try: + body = response.json() + except: + raise CLIError("Error parsing response body") + + if "error" in body: + raise CLIError(body["error"]) + + return body diff --git a/azext_iot/central/services/device.py b/azext_iot/central/services/device.py index b8976ee4a..3e3a0c920 100644 --- a/azext_iot/central/services/device.py +++ b/azext_iot/central/services/device.py @@ -39,13 +39,7 @@ def get_device( headers = utility.get_headers(token, cmd) response = requests.get(url, headers=headers) - - body = response.json() - - if "error" in body: - raise CLIError(body["error"]) - - return body + return utility.try_extract_result(response) def list_devices( @@ -71,18 +65,15 @@ def list_devices( response = requests.get(url, headers=headers) - body = response.json() - - if "error" in body: - raise CLIError(body["error"]) + result = utility.try_extract_result(response) - if "value" not in body: - raise CLIError("Value is not present in body: {}".format(body)) + if "value" not in result: + raise CLIError("Value is not present in body: {}".format(result)) - return body["value"] + return result["value"] -def add_device( +def create_device( cmd, token: str, app_id: str, @@ -121,10 +112,32 @@ def add_device( payload["instanceOf"] = instance_of response = requests.put(url, headers=headers, json=payload) + return utility.try_extract_result(response) + + +def delete_device( + cmd, + token: str, + app_id: str, + device_id: str, + central_dns_suffix="azureiotcentral.com", +) -> dict: + """ + Get device info given a device id - body = response.json() + Args: + cmd: command passed into az + device_id: unique case-sensitive device id, + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs - if "error" in body: - raise CLIError(body["error"]) + Returns: + device: dict + """ + url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_id) + headers = utility.get_headers(token, cmd) - return body + response = requests.delete(url, headers=headers) + return utility.try_extract_result(response) diff --git a/azext_iot/central/services/device_template.py b/azext_iot/central/services/device_template.py index 88f8ab126..70b2663fd 100644 --- a/azext_iot/central/services/device_template.py +++ b/azext_iot/central/services/device_template.py @@ -43,13 +43,7 @@ def get_device_template( headers = utility.get_headers(token, cmd) response = requests.get(url, headers=headers) - - body = response.json() - - if "error" in body: - raise CLIError(body["error"]) - - return body + return utility.try_extract_result(response) def list_device_templates( @@ -75,18 +69,15 @@ def list_device_templates( response = requests.get(url, headers=headers) - body = response.json() - - if "error" in body: - raise CLIError(body["error"]) + result = utility.try_extract_result(response) - if "value" not in body: - raise CLIError("Value is not present in body: {}".format(body)) + if "value" not in result: + raise CLIError("Value is not present in body: {}".format(result)) - return body["value"] + return result["value"] -def add_device_template( +def create_device_template( cmd, app_id: str, device_template_id: str, @@ -115,10 +106,34 @@ def add_device_template( headers = utility.get_headers(token, cmd, has_json_payload=True) response = requests.put(url, headers=headers, json=payload) + return utility.try_extract_result(response) + + +def delete_device_template( + cmd, + token: str, + app_id: str, + device_template_id: str, + central_dns_suffix="azureiotcentral.com", +) -> dict: + """ + Get device info given a device id - body = response.json() + Args: + cmd: command passed into az + device_id: unique case-sensitive device id, + app_id: name of app (used for forming request URL) + token: (OPTIONAL) authorization token to fetch device details from IoTC. + MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') + central_dns_suffix: {centralDnsSuffixInPath} as found in docs - if "error" in body: - raise CLIError(body["error"]) + Returns: + device: dict + """ + url = "https://{}.{}/{}/{}".format( + app_id, central_dns_suffix, BASE_PATH, device_template_id + ) + headers = utility.get_headers(token, cmd) - return body + response = requests.delete(url, headers=headers) + return utility.try_extract_result(response) diff --git a/azext_iot/tests/central/json/create_device_template_payload.json b/azext_iot/tests/central/json/create_device_template_payload.json index 2e73cf339..db5587790 100644 --- a/azext_iot/tests/central/json/create_device_template_payload.json +++ b/azext_iot/tests/central/json/create_device_template_payload.json @@ -2,7 +2,7 @@ "types": [ "DeviceModel" ], - "displayName": "some-device-template", + "displayName": "create-device-template-payload", "capabilityModel": { "@id": "urn:sampleApp:modelOne_bz:2", "@type": [ diff --git a/azext_iot/tests/test_iot_central_int.py b/azext_iot/tests/test_iot_central_int.py index 841faf8f8..d7bbc30fc 100644 --- a/azext_iot/tests/test_iot_central_int.py +++ b/azext_iot/tests/test_iot_central_int.py @@ -9,17 +9,18 @@ APP_ID = os.environ.get("azext_iot_central_app_id") DEVICE_ID = os.environ.get("azext_iot_central_device_id") +DEVICE_TEMPLATE_ID = os.environ.get("azext_iot_central_device_template_id") -if not all([APP_ID, DEVICE_ID]): +if not all([APP_ID, DEVICE_ID, DEVICE_TEMPLATE_ID]): raise ValueError( - "Set azext_iot_central_app_id " - "and azext_iot_central_device_id to run central integration tests. " + "Set azext_iot_central_app_id, azext_iot_central_device_id " + "and azext_iot_central_device_template_id to run central integration tests. " ) class TestIotCentral(LiveScenarioTest): - def __init__(self, test_method): - super(TestIotCentral, self).__init__("test_central_device_show") + def __init__(self, test_case): + super(TestIotCentral, self).__init__(test_case) def test_central_device_show(self): # Verify incorrect app-id throws error @@ -63,3 +64,65 @@ def test_central_validate_messages(self): # Ensure no failure # We cannot verify that the result is correct, as the Azure CLI for IoT Central does not support adding devices self.cmd("iot central app validate-messages --app-id {} --to 1".format(APP_ID)) + + def test_central_device_methods_CRLD(self): + # currently: create, show, list, delete + self.cmd( + "iot central app device create --app-id {} -d {}".format(APP_ID, DEVICE_ID), + checks=[ + self.check("approved", True), + self.check("displayName", DEVICE_ID), + self.check("id", DEVICE_ID), + self.check("simulated", False), + ], + ) + + self.cmd( + "iot central app device show --app-id {} -d {}".format(APP_ID, DEVICE_ID), + checks=[ + self.check("approved", True), + self.check("displayName", DEVICE_ID), + self.check("id", DEVICE_ID), + self.check("simulated", False), + ], + ) + + list_output = self.cmd("iot central app device list --app-id {}".format(APP_ID)) + + self.cmd( + "iot central app device delete --app-id {} -d {}".format(APP_ID, DEVICE_ID), + checks=[self.check("result", "success")], + ) + + assert DEVICE_ID in list_output.get_output_in_json() + + def test_central_device_template_methods_CRLD(self): + # currently: create, show, list, delete + self.cmd( + "iot central app device create --app-id {} -d {}".format(APP_ID, DEVICE_ID), + checks=[ + self.check("approved", True), + self.check("displayName", DEVICE_ID), + self.check("id", DEVICE_ID), + self.check("simulated", False), + ], + ) + + self.cmd( + "iot central app device show --app-id {} -d {}".format(APP_ID, DEVICE_ID), + checks=[ + self.check("approved", True), + self.check("displayName", DEVICE_ID), + self.check("id", DEVICE_ID), + self.check("simulated", False), + ], + ) + + list_output = self.cmd("iot central app device list --app-id {}".format(APP_ID)) + + self.cmd( + "iot central app device delete --app-id {} -d {}".format(APP_ID, DEVICE_ID), + checks=[self.check("result", "success")], + ) + + assert DEVICE_ID in list_output.get_output_in_json() diff --git a/pytest.ini.example b/pytest.ini.example index 8708ac4aa..a3756d9c8 100644 --- a/pytest.ini.example +++ b/pytest.ini.example @@ -23,3 +23,4 @@ env = azext_pnp_cs= azext_iot_central_app_id= azext_iot_central_device_id= + azext_iot_central_device_template_id= From e3b553bbb061dea5989b4a14247765c9aba5b4b7 Mon Sep 17 00:00:00 2001 From: prbans Date: Thu, 16 Apr 2020 19:19:58 -0700 Subject: [PATCH 06/13] Added device template IT --- azext_iot/central/commands_device_template.py | 14 ++- azext_iot/central/params.py | 9 +- .../providers/device_template_provider.py | 17 +--- ...oad.json => device_template_int_test.json} | 2 +- azext_iot/tests/test_iot_central_int.py | 97 ++++++++++++++++--- pytest.ini.example | 2 +- 6 files changed, 102 insertions(+), 39 deletions(-) rename azext_iot/tests/central/json/{create_device_template_payload.json => device_template_int_test.json} (99%) diff --git a/azext_iot/central/commands_device_template.py b/azext_iot/central/commands_device_template.py index 115fe9e24..296df2f45 100644 --- a/azext_iot/central/commands_device_template.py +++ b/azext_iot/central/commands_device_template.py @@ -5,6 +5,9 @@ # -------------------------------------------------------------------------------------------- # Dev note - think of this as a controller +from knack.util import CLIError + +from azext_iot.common import utility from .providers import CentralDeviceTemplateProvider @@ -31,13 +34,18 @@ def create_device_template( cmd, app_id: str, device_template_id: str, - file_path: str, + content: str, central_dns_suffix="azureiotcentral.com", ): + if not type(content) == str: + raise CLIError("content must be a string: {}".format(content)) + + payload = utility.process_json_arg(content, argument_name="content") + provider = CentralDeviceTemplateProvider(cmd, app_id) - return provider.add_device_template( + return provider.create_device_template( device_template_id=device_template_id, - file_path=file_path, + payload=payload, central_dns_suffix=central_dns_suffix, ) diff --git a/azext_iot/central/params.py b/azext_iot/central/params.py index 9b5b1c595..a158a969f 100644 --- a/azext_iot/central/params.py +++ b/azext_iot/central/params.py @@ -39,7 +39,10 @@ def load_central_arguments(self, _): help="Device template id. Example: somedevicetemplate", ) context.argument( - "file_path", - options_list=["--file-path", "--fp"], - help="Path to file containing the device template to be added to IoT Central. Example: ./path/to/template.json", + "content", + options_list=["--content", "-k"], + help="Configuration for request. " + "Provide path to JSON file or raw stringified JSON. " + "[File Path Example: ./path/to/file.json] " + "[Stringified JSON Example: {'a': 'b'}] ", ) diff --git a/azext_iot/central/providers/device_template_provider.py b/azext_iot/central/providers/device_template_provider.py index ce86eb9ca..06f2103ef 100644 --- a/azext_iot/central/providers/device_template_provider.py +++ b/azext_iot/central/providers/device_template_provider.py @@ -4,12 +4,8 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -import json -import os - from knack.util import CLIError from azext_iot.central import services as central_services -from azext_iot.common import utility class CentralDeviceTemplateProvider: @@ -77,21 +73,12 @@ def map_device_templates( ) return {template["displayName"]: template["id"] for template in templates} - def add_device_template( + def create_device_template( self, device_template_id: str, - file_path: str, + payload: str, central_dns_suffix="azureiotcentral.com", ): - if not os.path.exists(file_path): - raise CLIError('File path "{}" does not exist!'.format(file_path)) - - content = utility.read_file_content(file_path) - try: - payload = json.loads(content) - except Exception: - raise CLIError("File must be json format") - template = central_services.device_template.create_device_template( cmd=self._cmd, app_id=self._app_id, diff --git a/azext_iot/tests/central/json/create_device_template_payload.json b/azext_iot/tests/central/json/device_template_int_test.json similarity index 99% rename from azext_iot/tests/central/json/create_device_template_payload.json rename to azext_iot/tests/central/json/device_template_int_test.json index db5587790..672320418 100644 --- a/azext_iot/tests/central/json/create_device_template_payload.json +++ b/azext_iot/tests/central/json/device_template_int_test.json @@ -2,7 +2,7 @@ "types": [ "DeviceModel" ], - "displayName": "create-device-template-payload", + "displayName": "int-test-device-template", "capabilityModel": { "@id": "urn:sampleApp:modelOne_bz:2", "@type": [ diff --git a/azext_iot/tests/test_iot_central_int.py b/azext_iot/tests/test_iot_central_int.py index d7bbc30fc..220761ff8 100644 --- a/azext_iot/tests/test_iot_central_int.py +++ b/azext_iot/tests/test_iot_central_int.py @@ -7,14 +7,16 @@ import os from azure.cli.testsdk import LiveScenarioTest +from azext_iot.common import utility + APP_ID = os.environ.get("azext_iot_central_app_id") DEVICE_ID = os.environ.get("azext_iot_central_device_id") -DEVICE_TEMPLATE_ID = os.environ.get("azext_iot_central_device_template_id") +DEVICE_TEMPLATE_PATH = os.environ.get("azext_iot_central_device_template_path") -if not all([APP_ID, DEVICE_ID, DEVICE_TEMPLATE_ID]): +if not all([APP_ID, DEVICE_ID, DEVICE_TEMPLATE_PATH]): raise ValueError( "Set azext_iot_central_app_id, azext_iot_central_device_id " - "and azext_iot_central_device_template_id to run central integration tests. " + "and azext_iot_central_device_template_path to run central integration tests. " ) @@ -98,31 +100,94 @@ def test_central_device_methods_CRLD(self): def test_central_device_template_methods_CRLD(self): # currently: create, show, list, delete + template = utility.process_json_arg( + DEVICE_TEMPLATE_PATH, argument_name="DEVICE_TEMPLATE_PATH" + ) + template_name = template["displayName"] + template_id = template_name + "id" + self.cmd( - "iot central app device create --app-id {} -d {}".format(APP_ID, DEVICE_ID), + "iot central app device-template create --app-id {} --device-template-id {} -k {}".format( + APP_ID, template_id, DEVICE_TEMPLATE_PATH + ), checks=[ - self.check("approved", True), - self.check("displayName", DEVICE_ID), - self.check("id", DEVICE_ID), - self.check("simulated", False), + self.check("displayName", template_name), + self.check("id", template_id), ], ) self.cmd( - "iot central app device show --app-id {} -d {}".format(APP_ID, DEVICE_ID), + "iot central app device-template show --app-id {} --device-template-id {}".format( + APP_ID, template_id + ), checks=[ - self.check("approved", True), - self.check("displayName", DEVICE_ID), - self.check("id", DEVICE_ID), - self.check("simulated", False), + self.check("displayName", template_name), + self.check("id", template_id), ], ) - list_output = self.cmd("iot central app device list --app-id {}".format(APP_ID)) + list_output = self.cmd( + "iot central app device-template list --app-id {}".format(APP_ID) + ) + map_output = self.cmd( + "iot central app device-template map --app-id {}".format(APP_ID) + ) self.cmd( - "iot central app device delete --app-id {} -d {}".format(APP_ID, DEVICE_ID), + "iot central app device-template delete --app-id {} --device-template-id {}".format( + APP_ID, template_id + ), checks=[self.check("result", "success")], ) - assert DEVICE_ID in list_output.get_output_in_json() + assert template_id in list_output.get_output_in_json() + + map_json = map_output.get_output_in_json() + assert map_json[template_name] == template_id + + def test_central_device_monitor_events(self): + # currently: create, show, list, delete + template = utility.process_json_arg( + DEVICE_TEMPLATE_PATH, argument_name="DEVICE_TEMPLATE_PATH" + ) + template_name = template["displayName"] + template_id = template_name + "id" + + self.cmd( + "iot central app device-template create --app-id {} --device-template-id {} -k {}".format( + APP_ID, template_id, DEVICE_TEMPLATE_PATH + ), + checks=[ + self.check("displayName", template_name), + self.check("id", template_id), + ], + ) + + self.cmd( + "iot central app device-template show --app-id {} --device-template-id {}".format( + APP_ID, template_id + ), + checks=[ + self.check("displayName", template_name), + self.check("id", template_id), + ], + ) + + list_output = self.cmd( + "iot central app device-template list --app-id {}".format(APP_ID) + ) + map_output = self.cmd( + "iot central app device-template map --app-id {}".format(APP_ID) + ) + + self.cmd( + "iot central app device-template delete --app-id {} --device-template-id {}".format( + APP_ID, template_id + ), + checks=[self.check("result", "success")], + ) + + assert template_id in list_output.get_output_in_json() + + map_json = map_output.get_output_in_json() + assert map_json[template_name] == template_id diff --git a/pytest.ini.example b/pytest.ini.example index a3756d9c8..ad6dca94e 100644 --- a/pytest.ini.example +++ b/pytest.ini.example @@ -23,4 +23,4 @@ env = azext_pnp_cs= azext_iot_central_app_id= azext_iot_central_device_id= - azext_iot_central_device_template_id= + azext_iot_central_device_template_path=./azext_iot/tests/central/json/device_template_int_test.json From c94c08dac3daae24ee6a0b16de40b096f2522621 Mon Sep 17 00:00:00 2001 From: prbans Date: Thu, 16 Apr 2020 20:33:25 -0700 Subject: [PATCH 07/13] fixed UT errors, minor renames --- azext_iot/central/command_map.py | 4 ++-- azext_iot/central/commands_device.py | 2 +- azext_iot/central/commands_device_template.py | 4 ++-- .../central/providers/device_provider.py | 18 +++++++++++++++++ .../providers/device_template_provider.py | 2 +- azext_iot/operations/central.py | 11 ++++++---- azext_iot/operations/events3/_parser.py | 8 ++++---- azext_iot/tests/test_iot_central_int.py | 20 +++++++++++-------- azext_iot/tests/test_iot_central_unit.py | 12 ++++++----- azext_iot/tests/test_iot_utility_unit.py | 10 +++++++--- 10 files changed, 61 insertions(+), 30 deletions(-) diff --git a/azext_iot/central/command_map.py b/azext_iot/central/command_map.py index f7590c59a..f4a1250ac 100644 --- a/azext_iot/central/command_map.py +++ b/azext_iot/central/command_map.py @@ -27,7 +27,7 @@ def load_central_commands(self, _): "iot central app device", command_type=central_device_ops, is_preview=True, ) as cmd_group: cmd_group.command("list", "list_devices") - cmd_group.command("show", "show_device") + cmd_group.command("show", "get_device") cmd_group.command("create", "create_device") cmd_group.command("delete", "delete_device") @@ -38,6 +38,6 @@ def load_central_commands(self, _): ) as cmd_group: cmd_group.command("list", "list_device_templates") cmd_group.command("map", "map_device_templates") - cmd_group.command("show", "show_device_template") + cmd_group.command("show", "get_device_template") cmd_group.command("create", "create_device_template") cmd_group.command("delete", "delete_device_template") diff --git a/azext_iot/central/commands_device.py b/azext_iot/central/commands_device.py index d825c7b1b..89922a522 100644 --- a/azext_iot/central/commands_device.py +++ b/azext_iot/central/commands_device.py @@ -14,7 +14,7 @@ def list_devices(cmd, app_id: str, central_dns_suffix="azureiotcentral.com"): return provider.list_devices() -def show_device( +def get_device( cmd, app_id: str, device_id: str, central_dns_suffix="azureiotcentral.com" ): provider = CentralDeviceProvider(cmd, app_id) diff --git a/azext_iot/central/commands_device_template.py b/azext_iot/central/commands_device_template.py index 296df2f45..78814baa6 100644 --- a/azext_iot/central/commands_device_template.py +++ b/azext_iot/central/commands_device_template.py @@ -11,11 +11,11 @@ from .providers import CentralDeviceTemplateProvider -def show_device_template( +def get_device_template( cmd, app_id: str, device_template_id: str, central_dns_suffix="azureiotcentral.com" ): provider = CentralDeviceTemplateProvider(cmd, app_id) - return provider.show_device_template( + return provider.get_device_template( device_template_id=device_template_id, central_dns_suffix=central_dns_suffix ) diff --git a/azext_iot/central/providers/device_provider.py b/azext_iot/central/providers/device_provider.py index d3737dcc3..f152dfac6 100644 --- a/azext_iot/central/providers/device_provider.py +++ b/azext_iot/central/providers/device_provider.py @@ -6,6 +6,7 @@ from knack.util import CLIError from azext_iot.central import services as central_services +from .device_template_provider import CentralDeviceTemplateProvider class CentralDeviceProvider: @@ -25,6 +26,7 @@ def __init__(self, cmd, app_id, token=None): self._app_id = app_id self._token = token self._devices = {} + self._device_templates = {} def get_device( self, device_id, central_dns_suffix="azureiotcentral.com", @@ -44,6 +46,22 @@ def get_device( return device + def get_device_template_by_device_id( + self, device_id, central_dns_suffix="azureiotcentral.com", + ): + if not device_id: + raise CLIError("Device id must be specified.") + + device = self.get_device(device_id, central_dns_suffix) + device_template_id = device["instanceOf"] + + template = CentralDeviceTemplateProvider.get_device_template( + self=self, + device_template_id=device_template_id, + central_dns_suffix=central_dns_suffix, + ) + return template + def list_devices( self, central_dns_suffix="azureiotcentral.com", ): diff --git a/azext_iot/central/providers/device_template_provider.py b/azext_iot/central/providers/device_template_provider.py index 06f2103ef..30c619a26 100644 --- a/azext_iot/central/providers/device_template_provider.py +++ b/azext_iot/central/providers/device_template_provider.py @@ -26,7 +26,7 @@ def __init__(self, cmd, app_id, token=None): self._token = token self._device_templates = {} - def show_device_template( + def get_device_template( self, device_template_id, central_dns_suffix="azureiotcentral.com", ): # get or add to cache diff --git a/azext_iot/operations/central.py b/azext_iot/operations/central.py index a11b903b9..8e67ced9c 100644 --- a/azext_iot/operations/central.py +++ b/azext_iot/operations/central.py @@ -10,7 +10,10 @@ from azext_iot.common.shared import SdkType from azext_iot.common.utility import unpack_msrest_error, init_monitoring from azext_iot.common.sas_token_auth import BasicSasTokenAuthentication -from azext_iot.central.providers import CentralDeviceProvider +from azext_iot.central.providers import ( + CentralDeviceProvider, + CentralDeviceTemplateProvider, +) def find_between(s, start, end): @@ -32,10 +35,10 @@ def iot_central_device_show( def iot_central_device_capability_model_show( - cmd, device_id, app_id, central_api_uri="api.azureiotcentral.com" + cmd, device_id, app_id, central_dns_suffix="azureiotcentral.com" ): - provider = CentralDeviceProvider(cmd, app_id) - return provider.get_device_template(device_id) + device_provider = CentralDeviceProvider(cmd, app_id) + return device_provider.get_device_template_by_device_id(device_id) def iot_central_validate_messages( diff --git a/azext_iot/operations/events3/_parser.py b/azext_iot/operations/events3/_parser.py index 72b37c4b3..c0a8d3d8f 100644 --- a/azext_iot/operations/events3/_parser.py +++ b/azext_iot/operations/events3/_parser.py @@ -20,12 +20,10 @@ class Event3Parser(object): - _info = [] - _warnings = [] - _errors = [] _logger = get_logger(__name__) def __init__(self, logger=None): + self._reset_issues() if logger: self._logger = logger @@ -286,7 +284,9 @@ def _validate_payload_against_dcm( return try: - template = central_device_provider.get_device_template(origin_device_id) + template = central_device_provider.get_device_template_by_device_id( + origin_device_id + ) except Exception as e: self._errors.append( "Unable to get DCM for device: {}." diff --git a/azext_iot/tests/test_iot_central_int.py b/azext_iot/tests/test_iot_central_int.py index 220761ff8..91fd52ab7 100644 --- a/azext_iot/tests/test_iot_central_int.py +++ b/azext_iot/tests/test_iot_central_int.py @@ -68,23 +68,27 @@ def test_central_validate_messages(self): self.cmd("iot central app validate-messages --app-id {} --to 1".format(APP_ID)) def test_central_device_methods_CRLD(self): + device_id = self.create_random_name(prefix="aztest", length=24) + device_name = self.create_random_name(prefix="aztest", length=24) # currently: create, show, list, delete self.cmd( - "iot central app device create --app-id {} -d {}".format(APP_ID, DEVICE_ID), + "iot central app device create --app-id {} -d {} --device-name {}".format( + APP_ID, device_id, device_name + ), checks=[ self.check("approved", True), - self.check("displayName", DEVICE_ID), - self.check("id", DEVICE_ID), + self.check("displayName", device_name), + self.check("id", device_id), self.check("simulated", False), ], ) self.cmd( - "iot central app device show --app-id {} -d {}".format(APP_ID, DEVICE_ID), + "iot central app device show --app-id {} -d {}".format(APP_ID, device_id), checks=[ self.check("approved", True), - self.check("displayName", DEVICE_ID), - self.check("id", DEVICE_ID), + self.check("displayName", device_name), + self.check("id", device_id), self.check("simulated", False), ], ) @@ -92,11 +96,11 @@ def test_central_device_methods_CRLD(self): list_output = self.cmd("iot central app device list --app-id {}".format(APP_ID)) self.cmd( - "iot central app device delete --app-id {} -d {}".format(APP_ID, DEVICE_ID), + "iot central app device delete --app-id {} -d {}".format(APP_ID, device_id), checks=[self.check("result", "success")], ) - assert DEVICE_ID in list_output.get_output_in_json() + assert device_id in list_output.get_output_in_json() def test_central_device_template_methods_CRLD(self): # currently: create, show, list, delete diff --git a/azext_iot/tests/test_iot_central_unit.py b/azext_iot/tests/test_iot_central_unit.py index 2365bcef3..613d87e70 100644 --- a/azext_iot/tests/test_iot_central_unit.py +++ b/azext_iot/tests/test_iot_central_unit.py @@ -11,7 +11,10 @@ from azure.cli.core.mock import DummyCli from azext_iot.operations import central as subject from azext_iot.common.shared import SdkType -from azext_iot.central.providers import CentralDeviceProvider +from azext_iot.central.providers import ( + CentralDeviceProvider, + CentralDeviceTemplateProvider, +) from .helpers import load_json from .test_constants import FileNames @@ -201,19 +204,18 @@ def test_should_return_device_template( self, mock_device_svc, mock_device_template_svc ): # setup - provider = CentralDeviceProvider(cmd=None, app_id=app_id) + provider = CentralDeviceTemplateProvider(cmd=None, app_id=app_id) mock_device_svc.get_device.return_value = self._device mock_device_template_svc.get_device_template.return_value = ( self._device_template ) # act - template = provider.get_device_template("someDeviceId") + template = provider.get_device_template("someDeviceTemplate") # check that caching is working - template = provider.get_device_template("someDeviceId") + template = provider.get_device_template("someDeviceTemplate") # verify # call counts should be at most 1 since the provider has a cache - assert mock_device_svc.get_device.call_count == 1 assert mock_device_template_svc.get_device_template.call_count == 1 assert template == self._device_template diff --git a/azext_iot/tests/test_iot_utility_unit.py b/azext_iot/tests/test_iot_utility_unit.py index c4143098d..9fc75ee09 100644 --- a/azext_iot/tests/test_iot_utility_unit.py +++ b/azext_iot/tests/test_iot_utility_unit.py @@ -313,7 +313,9 @@ def test_parse_message_should_succeed(self): device_template = load_json(FileNames.central_device_template_file) provider = CentralDeviceProvider(cmd=None, app_id=None) - provider.get_device_template = mock.MagicMock(return_value=device_template) + provider.get_device_template_by_device_id = mock.MagicMock( + return_value=device_template + ) # act parsed_msg = parser.parse_message( @@ -543,7 +545,9 @@ def test_validate_against_template_should_fail(self): device_template = load_json(FileNames.central_device_template_file) provider = CentralDeviceProvider(cmd=None, app_id=None) - provider.get_device_template = mock.MagicMock(return_value=device_template) + provider.get_device_template_by_device_id = mock.MagicMock( + return_value=device_template + ) # act parsed_msg = parser.parse_message( @@ -593,7 +597,7 @@ def test_validate_against_bad_template_should_not_throw(self): parser = _parser.Event3Parser() provider = CentralDeviceProvider(cmd=None, app_id=None) - provider.get_device_template = mock.MagicMock( + provider.get_device_template_by_device_id = mock.MagicMock( return_value="an_unparseable_template" ) From 1ee0c232a25d81945e1200d2ccd7fec1388ddc9d Mon Sep 17 00:00:00 2001 From: prbans Date: Thu, 16 Apr 2020 20:54:08 -0700 Subject: [PATCH 08/13] Removed a bad IT --- azext_iot/tests/test_iot_central_int.py | 47 ------------------------- 1 file changed, 47 deletions(-) diff --git a/azext_iot/tests/test_iot_central_int.py b/azext_iot/tests/test_iot_central_int.py index 91fd52ab7..524d1b8f0 100644 --- a/azext_iot/tests/test_iot_central_int.py +++ b/azext_iot/tests/test_iot_central_int.py @@ -148,50 +148,3 @@ def test_central_device_template_methods_CRLD(self): map_json = map_output.get_output_in_json() assert map_json[template_name] == template_id - - def test_central_device_monitor_events(self): - # currently: create, show, list, delete - template = utility.process_json_arg( - DEVICE_TEMPLATE_PATH, argument_name="DEVICE_TEMPLATE_PATH" - ) - template_name = template["displayName"] - template_id = template_name + "id" - - self.cmd( - "iot central app device-template create --app-id {} --device-template-id {} -k {}".format( - APP_ID, template_id, DEVICE_TEMPLATE_PATH - ), - checks=[ - self.check("displayName", template_name), - self.check("id", template_id), - ], - ) - - self.cmd( - "iot central app device-template show --app-id {} --device-template-id {}".format( - APP_ID, template_id - ), - checks=[ - self.check("displayName", template_name), - self.check("id", template_id), - ], - ) - - list_output = self.cmd( - "iot central app device-template list --app-id {}".format(APP_ID) - ) - map_output = self.cmd( - "iot central app device-template map --app-id {}".format(APP_ID) - ) - - self.cmd( - "iot central app device-template delete --app-id {} --device-template-id {}".format( - APP_ID, template_id - ), - checks=[self.check("result", "success")], - ) - - assert template_id in list_output.get_output_in_json() - - map_json = map_output.get_output_in_json() - assert map_json[template_name] == template_id From 02d183199f0de152d75c6faba98e214d2462e762 Mon Sep 17 00:00:00 2001 From: prbans Date: Thu, 16 Apr 2020 21:15:45 -0700 Subject: [PATCH 09/13] fix unused issue --- azext_iot/operations/central.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/azext_iot/operations/central.py b/azext_iot/operations/central.py index 8e67ced9c..5870b2f2f 100644 --- a/azext_iot/operations/central.py +++ b/azext_iot/operations/central.py @@ -10,10 +10,7 @@ from azext_iot.common.shared import SdkType from azext_iot.common.utility import unpack_msrest_error, init_monitoring from azext_iot.common.sas_token_auth import BasicSasTokenAuthentication -from azext_iot.central.providers import ( - CentralDeviceProvider, - CentralDeviceTemplateProvider, -) +from azext_iot.central.providers import CentralDeviceProvider def find_between(s, start, end): From 1d06c6905ca616da6c44daa54fd4a1d67a31bd4e Mon Sep 17 00:00:00 2001 From: prbans Date: Fri, 17 Apr 2020 12:39:21 -0700 Subject: [PATCH 10/13] Updated docs, code cleanup --- .../central/providers/device_provider.py | 64 ++++++++++++------- .../providers/device_template_provider.py | 28 ++++---- azext_iot/central/services/device.py | 26 ++++---- azext_iot/central/services/device_template.py | 21 +++--- 4 files changed, 83 insertions(+), 56 deletions(-) diff --git a/azext_iot/central/providers/device_provider.py b/azext_iot/central/providers/device_provider.py index f152dfac6..605d2a229 100644 --- a/azext_iot/central/providers/device_provider.py +++ b/azext_iot/central/providers/device_provider.py @@ -10,9 +10,9 @@ class CentralDeviceProvider: - def __init__(self, cmd, app_id, token=None): + def __init__(self, cmd, app_id: str, token=None): """ - Provider for device/device_template APIs + Provider for device APIs Args: cmd: command passed into az @@ -35,12 +35,17 @@ def get_device( raise CLIError("Device id must be specified.") # get or add to cache - if device_id not in self._devices or not self._devices.get(device_id): - self._devices[device_id] = central_services.device.get_device( - self._cmd, device_id, self._app_id, self._token, central_dns_suffix + device = self._devices.get(device_id) + if not device: + device = central_services.device.get_device( + cmd=self._cmd, + app_id=self._app_id, + device_id=device_id, + token=self._token, + central_dns_suffix=central_dns_suffix, ) + self._devices[device_id] = device - device = self._devices[device_id] if not device: raise CLIError("No device found with id: '{}'.".format(device_id)) @@ -53,11 +58,17 @@ def get_device_template_by_device_id( raise CLIError("Device id must be specified.") device = self.get_device(device_id, central_dns_suffix) - device_template_id = device["instanceOf"] + instance_of = device.get("instanceOf") + if not instance_of: + raise CLIError( + "Device '{}' does not have a corresponding device template.".format( + device_id + ) + ) template = CentralDeviceTemplateProvider.get_device_template( self=self, - device_template_id=device_template_id, + device_template_id=instance_of, central_dns_suffix=central_dns_suffix, ) return template @@ -68,8 +79,9 @@ def list_devices( devices = central_services.device.list_devices( cmd=self._cmd, app_id=self._app_id, token=self._token ) - for device in devices: - self._devices[device["id"]] = device + + # add to cache + self._devices.update({device["id"]: device for device in devices}) return self._devices @@ -87,23 +99,23 @@ def create_device( if device_id in self._devices: raise CLIError("Device already exists") - # get or add to cache - if device_id not in self._devices or not self._devices.get(device_id): - self._devices[device_id] = central_services.device.create_device( - cmd=self._cmd, - token=self._token, - app_id=self._app_id, - device_id=device_id, - device_name=device_name, - instance_of=instance_of, - simulated=simulated, - central_dns_suffix=central_dns_suffix, - ) + device = central_services.device.create_device( + cmd=self._cmd, + app_id=self._app_id, + device_id=device_id, + device_name=device_name, + instance_of=instance_of, + simulated=simulated, + token=self._token, + central_dns_suffix=central_dns_suffix, + ) - device = self._devices[device_id] if not device: raise CLIError("No device found with id: '{}'.".format(device_id)) + # add to cache + self._devices[device["id"]] = device + return device def delete_device( @@ -115,10 +127,14 @@ def delete_device( # get or add to cache result = central_services.device.delete_device( cmd=self._cmd, - token=self._token, app_id=self._app_id, device_id=device_id, + token=self._token, central_dns_suffix=central_dns_suffix, ) + # remove from cache + # pop "miss" raises a KeyError if None is not provided + self._devices.pop(device_id, None) + return result diff --git a/azext_iot/central/providers/device_template_provider.py b/azext_iot/central/providers/device_template_provider.py index 30c619a26..e7bc3ff0a 100644 --- a/azext_iot/central/providers/device_template_provider.py +++ b/azext_iot/central/providers/device_template_provider.py @@ -11,7 +11,7 @@ class CentralDeviceTemplateProvider: def __init__(self, cmd, app_id, token=None): """ - Provider for device/device_template APIs + Provider for device_template APIs Args: cmd: command passed into az @@ -30,21 +30,17 @@ def get_device_template( self, device_template_id, central_dns_suffix="azureiotcentral.com", ): # get or add to cache - if ( - device_template_id not in self._device_templates - or not self._device_templates.get(device_template_id) - ): - self._device_templates[ - device_template_id - ] = central_services.device_template.get_device_template( + device_template = self._device_templates.get(device_template_id) + if not device_template: + device_template = central_services.device_template.get_device_template( cmd=self._cmd, app_id=self._app_id, device_template_id=device_template_id, token=self._token, central_dns_suffix=central_dns_suffix, ) + self._device_templates[device_template_id] = device_template - device_template = self._device_templates[device_template_id] if not device_template: raise CLIError( "No device template for device template with id: '{}'.".format( @@ -60,14 +56,19 @@ def list_device_templates( templates = central_services.device_template.list_device_templates( cmd=self._cmd, app_id=self._app_id, token=self._token ) - for template in templates: - self._device_templates[template["id"]] = template + + self._device_templates.update( + {template["id"]: template for template in templates} + ) return self._device_templates def map_device_templates( self, central_dns_suffix="azureiotcentral.com", ): + """ + Maps each template name to the corresponding template id + """ templates = central_services.device_template.list_device_templates( cmd=self._cmd, app_id=self._app_id, token=self._token ) @@ -98,7 +99,6 @@ def delete_device_template( if not device_template_id: raise CLIError("Device template id must be specified.") - # get or add to cache result = central_services.device_template.delete_device_template( cmd=self._cmd, token=self._token, @@ -107,4 +107,8 @@ def delete_device_template( central_dns_suffix=central_dns_suffix, ) + # remove from cache + # pop "miss" raises a KeyError if None is not provided + self._device_templates.pop(device_template_id, None) + return result diff --git a/azext_iot/central/services/device.py b/azext_iot/central/services/device.py index 3e3a0c920..f20217a86 100644 --- a/azext_iot/central/services/device.py +++ b/azext_iot/central/services/device.py @@ -15,8 +15,8 @@ def get_device( cmd, - device_id: str, app_id: str, + device_id: str, token: str, central_dns_suffix="azureiotcentral.com", ) -> dict: @@ -46,18 +46,17 @@ def list_devices( cmd, app_id: str, token: str, central_dns_suffix="azureiotcentral.com", ) -> list: """ - Get device info given a device id + Get a list of all devices in IoTC app Args: cmd: command passed into az - device_id: unique case-sensitive device id, app_id: name of app (used for forming request URL) token: (OPTIONAL) authorization token to fetch device details from IoTC. MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') central_dns_suffix: {centralDnsSuffixInPath} as found in docs Returns: - device: dict + list of devices """ url = "https://{}.{}/{}".format(app_id, central_dns_suffix, BASE_PATH) @@ -75,21 +74,25 @@ def list_devices( def create_device( cmd, - token: str, app_id: str, device_id: str, device_name: str, instance_of: str, simulated: bool, + token: str, central_dns_suffix="azureiotcentral.com", ) -> dict: """ - Get device info given a device id + Create a device in IoTC Args: cmd: command passed into az - device_id: unique case-sensitive device id, app_id: name of app (used for forming request URL) + device_id: unique case-sensitive device id + device_name: (non-unique) human readable name for the device + instance_of: (optional) string that maps to the device_template_id + of the device template that this device is to be an instance of + simulated: if IoTC is to simulate data for this device token: (OPTIONAL) authorization token to fetch device details from IoTC. MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') central_dns_suffix: {centralDnsSuffixInPath} as found in docs @@ -117,24 +120,25 @@ def create_device( def delete_device( cmd, - token: str, app_id: str, device_id: str, + token: str, central_dns_suffix="azureiotcentral.com", ) -> dict: """ - Get device info given a device id + Delete a device from IoTC Args: cmd: command passed into az - device_id: unique case-sensitive device id, app_id: name of app (used for forming request URL) + device_id: unique case-sensitive device id, token: (OPTIONAL) authorization token to fetch device details from IoTC. MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') central_dns_suffix: {centralDnsSuffixInPath} as found in docs Returns: - device: dict + {"result": "success"} on success + Raises error on failure """ url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_id) headers = utility.get_headers(token, cmd) diff --git a/azext_iot/central/services/device_template.py b/azext_iot/central/services/device_template.py index 70b2663fd..3d5c5e8cc 100644 --- a/azext_iot/central/services/device_template.py +++ b/azext_iot/central/services/device_template.py @@ -24,11 +24,11 @@ def get_device_template( central_dns_suffix="azureiotcentral.com", ) -> dict: """ - Get device template given a device id + Get a specific device template from IoTC Args: cmd: command passed into az - device_template_id: case sensitive device template urn, + device_template_id: case sensitive device template id, app_id: name of app (used for forming request URL) token: (OPTIONAL) authorization token to fetch device details from IoTC. MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') @@ -50,11 +50,10 @@ def list_device_templates( cmd, app_id: str, token: str, central_dns_suffix="azureiotcentral.com", ) -> list: """ - Get device info given a device id + Get a list of all device templates in IoTC Args: cmd: command passed into az - device_id: unique case-sensitive device id, app_id: name of app (used for forming request URL) token: (OPTIONAL) authorization token to fetch device details from IoTC. MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') @@ -86,12 +85,16 @@ def create_device_template( central_dns_suffix="azureiotcentral.com", ) -> list: """ - Get device info given a device id + Create a device template in IoTC Args: cmd: command passed into az - device_id: unique case-sensitive device id, app_id: name of app (used for forming request URL) + device_template_id: case sensitive device template id, + payload: see example payload available in + /azext_iot/tests/central/json/device_template_int_test.json + or check here for more information + https://docs.microsoft.com/en-us/rest/api/iotcentral/devicetemplates token: (OPTIONAL) authorization token to fetch device details from IoTC. MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') central_dns_suffix: {centralDnsSuffixInPath} as found in docs @@ -111,18 +114,18 @@ def create_device_template( def delete_device_template( cmd, - token: str, app_id: str, device_template_id: str, + token: str, central_dns_suffix="azureiotcentral.com", ) -> dict: """ - Get device info given a device id + Delete a device template from IoTC Args: cmd: command passed into az - device_id: unique case-sensitive device id, app_id: name of app (used for forming request URL) + device_template_id: case sensitive device template id, token: (OPTIONAL) authorization token to fetch device details from IoTC. MUST INCLUDE type (e.g. 'SharedAccessToken ...', 'Bearer ...') central_dns_suffix: {centralDnsSuffixInPath} as found in docs From a4b2629abb5437a858ce62c07458cf8e19b2664f Mon Sep 17 00:00:00 2001 From: prbans Date: Fri, 17 Apr 2020 13:04:34 -0700 Subject: [PATCH 11/13] Added help --- azext_iot/central/__init__.py | 4 + azext_iot/central/_help.py | 178 ++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 azext_iot/central/_help.py diff --git a/azext_iot/central/__init__.py b/azext_iot/central/__init__.py index 55614acbf..e4c62d725 100644 --- a/azext_iot/central/__init__.py +++ b/azext_iot/central/__init__.py @@ -3,3 +3,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- + +from ._help import load_central_help + +load_central_help() diff --git a/azext_iot/central/_help.py b/azext_iot/central/_help.py new file mode 100644 index 000000000..98058f486 --- /dev/null +++ b/azext_iot/central/_help.py @@ -0,0 +1,178 @@ +# 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 knack.help_files import helps + + +def load_central_help(): + helps[ + "iot central" + ] = """ + type: group + short-summary: Manage Azure Central (IoTC) solutions & infrastructure + """ + + _load_central_devices_help() + _load_central_device_templates_help() + + +def _load_central_devices_help(): + helps[ + "iot central app device" + ] = """ + type: group + short-summary: Manage and configure IoTC devices + """ + + helps[ + "iot central app device create" + ] = """ + type: command + short-summary: Create a device in IoTC + + examples: + - name: Create a device + text: > + az iot central app device create + --app-id {appid} + --device-id {deviceid} + + - name: Create a simulated device + text: > + az iot central app device create + --app-id {appid} + --device-id {deviceid} + --instance-of {devicetemplateid} + --simulated + """ + + helps[ + "iot central app device show" + ] = """ + type: command + short-summary: Get a device from IoTC + + examples: + - name: Get a device + text: > + az iot central app device show + --app-id {appid} + --device-id {deviceid} + """ + + helps[ + "iot central app device list" + ] = """ + type: command + short-summary: List all devices in IoTC + + examples: + - name: Get a device + text: > + az iot central app device list + --app-id {appid} + """ + + helps[ + "iot central app device delete" + ] = """ + type: command + short-summary: Delete a device from IoTC + + examples: + - name: Get a device + text: > + az iot central app device list + --app-id {appid} + --device-id {deviceid} + """ + + +def _load_central_device_templates_help(): + helps[ + "iot central app device-template" + ] = """ + type: group + short-summary: Manage and configure IoTC device templates + """ + + helps[ + "iot central app device-template create" + ] = """ + type: command + short-summary: Create a device template in IoTC + + examples: + - name: Create a device with payload read from a file + text: > + az iot central app device create + --app-id {appid} + --content {pathtofile} + --device-template-id {devicetemplateid} + + - name: Create a device with payload read from raw json + text: > + az iot central app device create + --app-id {appid} + --content {json} + --device-template-id {devicetemplateid} + """ + + helps[ + "iot central app device-template show" + ] = """ + type: command + short-summary: Get a device template from IoTC + + examples: + - name: Get a device + text: > + az iot central app device show + --app-id {appid} + --device-template-id {devicetemplateid} + """ + + helps[ + "iot central app device-template list" + ] = """ + type: command + short-summary: List all device templates in IoTC + + examples: + - name: Get a device + text: > + az iot central app device-template list + --app-id {appid} + """ + + helps[ + "iot central app device-template map" + ] = """ + type: command + short-summary: Returns a mapping of device template name to device template id + + examples: + - name: Get device template name to id mapping + text: > + az iot central app device-template map + --app-id {appid} + """ + + helps[ + "iot central app device-template delete" + ] = """ + type: command + short-summary: Delete a device template from IoTC + long-summary: + Note: this is expected to fail + if any devices are still registered to this template. + + examples: + - name: Get a device + text: > + az iot central app device-template list + --app-id {appid} + """ From d60f2599a7294e9a915599d1b85bf11e6017eef8 Mon Sep 17 00:00:00 2001 From: prbans Date: Mon, 20 Apr 2020 17:38:24 -0700 Subject: [PATCH 12/13] relative -> full imports, added a comment, other minor fixes --- azext_iot/central/__init__.py | 2 +- azext_iot/central/commands_device.py | 2 +- azext_iot/central/commands_device_template.py | 4 ++-- azext_iot/central/providers/__init__.py | 6 ++++-- azext_iot/central/providers/device_provider.py | 3 ++- azext_iot/central/services/__init__.py | 2 +- azext_iot/central/services/_utility.py | 2 ++ azext_iot/central/services/device.py | 18 +++++++++--------- azext_iot/central/services/device_template.py | 18 +++++++++--------- 9 files changed, 31 insertions(+), 26 deletions(-) diff --git a/azext_iot/central/__init__.py b/azext_iot/central/__init__.py index e4c62d725..73178b8ad 100644 --- a/azext_iot/central/__init__.py +++ b/azext_iot/central/__init__.py @@ -4,6 +4,6 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from ._help import load_central_help +from azext_iot.central._help import load_central_help load_central_help() diff --git a/azext_iot/central/commands_device.py b/azext_iot/central/commands_device.py index 89922a522..745d92e31 100644 --- a/azext_iot/central/commands_device.py +++ b/azext_iot/central/commands_device.py @@ -6,7 +6,7 @@ # Dev note - think of this as a controller from knack.util import CLIError -from .providers import CentralDeviceProvider +from azext_iot.central.providers import CentralDeviceProvider def list_devices(cmd, app_id: str, central_dns_suffix="azureiotcentral.com"): diff --git a/azext_iot/central/commands_device_template.py b/azext_iot/central/commands_device_template.py index 78814baa6..2f2050f95 100644 --- a/azext_iot/central/commands_device_template.py +++ b/azext_iot/central/commands_device_template.py @@ -8,7 +8,7 @@ from knack.util import CLIError from azext_iot.common import utility -from .providers import CentralDeviceTemplateProvider +from azext_iot.central.providers import CentralDeviceTemplateProvider def get_device_template( @@ -37,7 +37,7 @@ def create_device_template( content: str, central_dns_suffix="azureiotcentral.com", ): - if not type(content) == str: + if not isinstance(content, str): raise CLIError("content must be a string: {}".format(content)) payload = utility.process_json_arg(content, argument_name="content") diff --git a/azext_iot/central/providers/__init__.py b/azext_iot/central/providers/__init__.py index aba5db625..57e791421 100644 --- a/azext_iot/central/providers/__init__.py +++ b/azext_iot/central/providers/__init__.py @@ -4,7 +4,9 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from .device_provider import CentralDeviceProvider -from .device_template_provider import CentralDeviceTemplateProvider +from azext_iot.central.providers.device_provider import CentralDeviceProvider +from azext_iot.central.providers.device_template_provider import ( + CentralDeviceTemplateProvider, +) __all__ = ["CentralDeviceProvider", "CentralDeviceTemplateProvider"] diff --git a/azext_iot/central/providers/device_provider.py b/azext_iot/central/providers/device_provider.py index 605d2a229..c45becb92 100644 --- a/azext_iot/central/providers/device_provider.py +++ b/azext_iot/central/providers/device_provider.py @@ -6,7 +6,6 @@ from knack.util import CLIError from azext_iot.central import services as central_services -from .device_template_provider import CentralDeviceTemplateProvider class CentralDeviceProvider: @@ -54,6 +53,8 @@ def get_device( def get_device_template_by_device_id( self, device_id, central_dns_suffix="azureiotcentral.com", ): + from azext_iot.central.providers import CentralDeviceTemplateProvider + if not device_id: raise CLIError("Device id must be specified.") diff --git a/azext_iot/central/services/__init__.py b/azext_iot/central/services/__init__.py index 6e13f4bad..190ad31e8 100644 --- a/azext_iot/central/services/__init__.py +++ b/azext_iot/central/services/__init__.py @@ -4,7 +4,7 @@ # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- -from . import device, device_template +from azext_iot.central.services import device, device_template __all__ = ["device", "device_template"] diff --git a/azext_iot/central/services/_utility.py b/azext_iot/central/services/_utility.py index 2af124d7b..f3afed651 100644 --- a/azext_iot/central/services/_utility.py +++ b/azext_iot/central/services/_utility.py @@ -28,6 +28,8 @@ def get_headers(token, cmd, has_json_payload=False): def try_extract_result(response: Response): + # 201 and 204 response codes indicate success + # with no content, hence attempting to retrieve content will fail if response.status_code in [201, 204]: return {"result": "success"} diff --git a/azext_iot/central/services/device.py b/azext_iot/central/services/device.py index f20217a86..46e221bcc 100644 --- a/azext_iot/central/services/device.py +++ b/azext_iot/central/services/device.py @@ -8,7 +8,7 @@ import requests from knack.util import CLIError -from . import _utility as utility +from azext_iot.central.services import _utility BASE_PATH = "api/preview/devices" @@ -36,10 +36,10 @@ def get_device( """ url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_id) - headers = utility.get_headers(token, cmd) + headers = _utility.get_headers(token, cmd) response = requests.get(url, headers=headers) - return utility.try_extract_result(response) + return _utility.try_extract_result(response) def list_devices( @@ -60,11 +60,11 @@ def list_devices( """ url = "https://{}.{}/{}".format(app_id, central_dns_suffix, BASE_PATH) - headers = utility.get_headers(token, cmd) + headers = _utility.get_headers(token, cmd) response = requests.get(url, headers=headers) - result = utility.try_extract_result(response) + result = _utility.try_extract_result(response) if "value" not in result: raise CLIError("Value is not present in body: {}".format(result)) @@ -105,7 +105,7 @@ def create_device( device_name = device_id url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_id) - headers = utility.get_headers(token, cmd, has_json_payload=True) + headers = _utility.get_headers(token, cmd, has_json_payload=True) payload = { "displayName": device_name, "simulated": simulated, @@ -115,7 +115,7 @@ def create_device( payload["instanceOf"] = instance_of response = requests.put(url, headers=headers, json=payload) - return utility.try_extract_result(response) + return _utility.try_extract_result(response) def delete_device( @@ -141,7 +141,7 @@ def delete_device( Raises error on failure """ url = "https://{}.{}/{}/{}".format(app_id, central_dns_suffix, BASE_PATH, device_id) - headers = utility.get_headers(token, cmd) + headers = _utility.get_headers(token, cmd) response = requests.delete(url, headers=headers) - return utility.try_extract_result(response) + return _utility.try_extract_result(response) diff --git a/azext_iot/central/services/device_template.py b/azext_iot/central/services/device_template.py index 3d5c5e8cc..46d39889b 100644 --- a/azext_iot/central/services/device_template.py +++ b/azext_iot/central/services/device_template.py @@ -9,7 +9,7 @@ from knack.util import CLIError from knack.log import get_logger -from . import _utility as utility +from azext_iot.central.services import _utility logger = get_logger(__name__) @@ -40,10 +40,10 @@ def get_device_template( url = "https://{}.{}/{}/{}".format( app_id, central_dns_suffix, BASE_PATH, device_template_id ) - headers = utility.get_headers(token, cmd) + headers = _utility.get_headers(token, cmd) response = requests.get(url, headers=headers) - return utility.try_extract_result(response) + return _utility.try_extract_result(response) def list_device_templates( @@ -64,11 +64,11 @@ def list_device_templates( """ url = "https://{}.{}/{}".format(app_id, central_dns_suffix, BASE_PATH) - headers = utility.get_headers(token, cmd) + headers = _utility.get_headers(token, cmd) response = requests.get(url, headers=headers) - result = utility.try_extract_result(response) + result = _utility.try_extract_result(response) if "value" not in result: raise CLIError("Value is not present in body: {}".format(result)) @@ -106,10 +106,10 @@ def create_device_template( url = "https://{}.{}/{}/{}".format( app_id, central_dns_suffix, BASE_PATH, device_template_id ) - headers = utility.get_headers(token, cmd, has_json_payload=True) + headers = _utility.get_headers(token, cmd, has_json_payload=True) response = requests.put(url, headers=headers, json=payload) - return utility.try_extract_result(response) + return _utility.try_extract_result(response) def delete_device_template( @@ -136,7 +136,7 @@ def delete_device_template( url = "https://{}.{}/{}/{}".format( app_id, central_dns_suffix, BASE_PATH, device_template_id ) - headers = utility.get_headers(token, cmd) + headers = _utility.get_headers(token, cmd) response = requests.delete(url, headers=headers) - return utility.try_extract_result(response) + return _utility.try_extract_result(response) From 32a91a3bf2f9aaa630736cc461c26fcbb6b03e4e Mon Sep 17 00:00:00 2001 From: prbans Date: Mon, 20 Apr 2020 18:33:36 -0700 Subject: [PATCH 13/13] fixed IT --- azext_iot/tests/iothub/jobs/test_iothub_jobs_int.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azext_iot/tests/iothub/jobs/test_iothub_jobs_int.py b/azext_iot/tests/iothub/jobs/test_iothub_jobs_int.py index e48e30125..1223cb87e 100644 --- a/azext_iot/tests/iothub/jobs/test_iothub_jobs_int.py +++ b/azext_iot/tests/iothub/jobs/test_iothub_jobs_int.py @@ -168,7 +168,7 @@ def test_jobs(self): # Cancel Job test # Create job to be cancelled - scheduled +7 days from now. - scheduled_time_iso = (datetime.utcnow() + timedelta(days=7)).isoformat() + scheduled_time_iso = (datetime.utcnow() + timedelta(days=6)).isoformat() self.cmd( "iot hub job create --job-id {} --job-type {} -q \"{}\" --twin-patch '{}' --start '{}' -n {} -g {}".format(