Skip to content
11 changes: 11 additions & 0 deletions azext_iot/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,17 @@
az iot central app validate-messages --app-id {app_id} --simulate-errors
"""

helps[
"iot central app capability-model show"
] = """
type: command
short-summary: Get the device model from IoT central.
examples:
- name: Basic usage
text: >
az iot central app capability-model show --app-id {app_id} -d {device_id}
"""

helps[
"iot central device-twin"
] = """
Expand Down
5 changes: 5 additions & 0 deletions azext_iot/central/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# 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.
# --------------------------------------------------------------------------------------------
9 changes: 9 additions & 0 deletions azext_iot/central/providers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 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 .device_provider import CentralDeviceProvider

__all__ = ["CentralDeviceProvider"]
80 changes: 80 additions & 0 deletions azext_iot/central/providers/device_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# 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


class CentralDeviceProvider:
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 = {}
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(
self, device_id, central_dns_suffix="azureiotcentral.com",
):
if not device_id:
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[device_id]
if not device:
raise CLIError("No device found with id: '{}'.".format(device_id))

return device
10 changes: 10 additions & 0 deletions azext_iot/central/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# 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 . import device, device_template


__all__ = ["device", "device_template"]
17 changes: 17 additions & 0 deletions azext_iot/central/services/_utility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 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.
# --------------------------------------------------------------------------------------------
# Nothing in this file should be used outside of service/central

from azext_iot import constants
from azext_iot.common import auth


def get_headers(token, cmd):
if not token:
aad_token = auth.get_aad_token(cmd, resource="https://apps.azureiotcentral.com")
token = "Bearer {}".format(aad_token["accessToken"])

return {"Authorization": token, "User-Agent": constants.USER_AGENT}
48 changes: 48 additions & 0 deletions azext_iot/central/services/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 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.
# --------------------------------------------------------------------------------------------
# This is largely derived from https://docs.microsoft.com/en-us/rest/api/iotcentral/devices

import requests

from knack.util import CLIError
from . import _utility as utility


def get_device(
cmd,
device_id: str,
app_id: str,
token: str,
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
"""

url = "https://{}.{}/api/preview/devices/{}".format(
app_id, central_dns_suffix, device_id
)
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
47 changes: 47 additions & 0 deletions azext_iot/central/services/device_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 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.
# --------------------------------------------------------------------------------------------
# This is largely derived from https://docs.microsoft.com/en-us/rest/api/iotcentral/devicetemplates

import requests

from knack.util import CLIError
from . import _utility as utility


def get_device_template(
cmd,
device_template_urn: str,
app_id: str,
token: str,
central_dns_suffix="azureiotcentral.com",
) -> dict:
"""
Get device template given a device id

Args:
cmd: command passed into az
device_template_urn: 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 ...')
central_dns_suffix: {centralDnsSuffixInPath} as found in docs

Returns:
device: dict
"""
url = "https://{}.{}/api/preview/deviceTemplates/{}".format(
app_id, central_dns_suffix, device_template_urn
)
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
7 changes: 7 additions & 0 deletions azext_iot/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ def load_command_table(self, _):
"validate-messages", "iot_central_validate_messages", is_preview=True
)

with self.command_group(
"iot central app capability-model", command_type=iotcentral_ops
Comment thread
This conversation was marked as resolved.
) as cmd_group:
cmd_group.command(
"show", "iot_central_device_capability_model_show", is_preview=True
)

with self.command_group(
"iot central device-twin", command_type=iotcentral_ops
) as cmd_group:
Expand Down
27 changes: 27 additions & 0 deletions azext_iot/common/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 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 azure.cli.core._profile import Profile


def get_aad_token(cmd, resource=None):
"""
get AAD token to access to a specified resource
:param resource: Azure resource endpoints. Default to Azure Resource Manager
Use 'az cloud show' command for other Azure resources
"""
resource = resource or cmd.cli_ctx.cloud.endpoints.active_directory_resource_id
profile = Profile(cli_ctx=cmd.cli_ctx)
creds, subscription, tenant = profile.get_raw_token(
subscription=None, resource=resource
)
return {
"tokenType": creds[0],
"accessToken": creds[1],
"expiresOn": creds[2].get("expiresOn", "N/A"),
"subscription": subscription,
"tenant": tenant,
}
61 changes: 37 additions & 24 deletions azext_iot/operations/central.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +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


def find_between(s, start, end):
Expand All @@ -30,6 +31,13 @@ def iot_central_device_show(
raise CLIError(unpack_msrest_error(e))


def iot_central_device_capability_model_show(
cmd, device_id, app_id, central_api_uri="api.azureiotcentral.com"
):
provider = CentralDeviceProvider(cmd, app_id)
return provider.get_device_template(device_id)


def iot_central_validate_messages(
cmd,
app_id,
Expand All @@ -43,19 +51,21 @@ def iot_central_validate_messages(
yes=False,
central_api_uri="api.azureiotcentral.com",
):
provider = CentralDeviceProvider(cmd, app_id)
_events3_runner(
cmd,
app_id,
device_id,
True,
simulate_errors,
consumer_group,
timeout,
enqueued_time,
repair,
properties,
yes,
central_api_uri,
cmd=cmd,
app_id=app_id,
device_id=device_id,
Comment thread
This conversation was marked as resolved.
validate_messages=True,
simulate_errors=simulate_errors,
consumer_group=consumer_group,
timeout=timeout,
enqueued_time=enqueued_time,
repair=repair,
properties=properties,
yes=yes,
central_api_uri=central_api_uri,
central_device_provider=provider,
)


Expand All @@ -72,18 +82,19 @@ def iot_central_monitor_events(
central_api_uri="api.azureiotcentral.com",
):
_events3_runner(
cmd,
app_id,
device_id,
False,
False,
consumer_group,
timeout,
enqueued_time,
repair,
properties,
yes,
central_api_uri,
cmd=cmd,
app_id=app_id,
device_id=device_id,
validate_messages=False,
simulate_errors=False,
consumer_group=consumer_group,
timeout=timeout,
enqueued_time=enqueued_time,
repair=repair,
properties=properties,
yes=yes,
central_api_uri=central_api_uri,
central_device_provider=None,
)


Expand All @@ -100,6 +111,7 @@ def _events3_runner(
properties,
yes,
central_api_uri,
central_device_provider,
):
(enqueued_time, properties, timeout, output) = init_monitoring(
cmd, timeout, properties, enqueued_time, repair, yes
Expand All @@ -121,4 +133,5 @@ def _events3_runner(
output=output,
validate_messages=validate_messages,
simulate_errors=simulate_errors,
central_device_provider=central_device_provider,
)
Loading