Support Azure IoT hub and related module (#55121)
* Support iothub creation * raise errordetailexception rather than clouderror * add facts * change requirement * compare endpoint * add documentation * add documentation * add iot device facts * modify line ending * add auth method * add iot module * add consumer group * add the test * enhencement of doc * add list consumer groups * fix lint * fix lint * fix doc * fix doc * Update auzre_rm_iothub related document * changed paramter's type * update type * rename facts -> info * fixed sanity * missed during merge
This commit is contained in:
parent
6fb7073adc
commit
b4732dd2e6
12 changed files with 3119 additions and 8 deletions
|
@ -172,10 +172,26 @@ try:
|
||||||
import azure.mgmt.loganalytics.models as LogAnalyticsModels
|
import azure.mgmt.loganalytics.models as LogAnalyticsModels
|
||||||
from azure.mgmt.automation import AutomationClient
|
from azure.mgmt.automation import AutomationClient
|
||||||
import azure.mgmt.automation.models as AutomationModel
|
import azure.mgmt.automation.models as AutomationModel
|
||||||
|
from azure.mgmt.iothub import IotHubClient
|
||||||
|
from azure.mgmt.iothub import models as IoTHubModels
|
||||||
|
from msrest.service_client import ServiceClient
|
||||||
|
from msrestazure import AzureConfiguration
|
||||||
|
from msrest.authentication import Authentication
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
|
Authentication = object
|
||||||
HAS_AZURE_EXC = traceback.format_exc()
|
HAS_AZURE_EXC = traceback.format_exc()
|
||||||
HAS_AZURE = False
|
HAS_AZURE = False
|
||||||
|
|
||||||
|
from base64 import b64encode, b64decode
|
||||||
|
from hashlib import sha256
|
||||||
|
from hmac import HMAC
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
try:
|
||||||
|
from urllib import (urlencode, quote_plus)
|
||||||
|
except ImportError:
|
||||||
|
from urllib.parse import (urlencode, quote_plus)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from azure.cli.core.util import CLIError
|
from azure.cli.core.util import CLIError
|
||||||
from azure.common.credentials import get_azure_cli_credentials, get_cli_profile
|
from azure.common.credentials import get_azure_cli_credentials, get_cli_profile
|
||||||
|
@ -311,6 +327,7 @@ class AzureRMModuleBase(object):
|
||||||
self._log_analytics_client = None
|
self._log_analytics_client = None
|
||||||
self._servicebus_client = None
|
self._servicebus_client = None
|
||||||
self._automation_client = None
|
self._automation_client = None
|
||||||
|
self._IoThub_client = None
|
||||||
|
|
||||||
self.check_mode = self.module.check_mode
|
self.check_mode = self.module.check_mode
|
||||||
self.api_profile = self.module.params.get('api_profile')
|
self.api_profile = self.module.params.get('api_profile')
|
||||||
|
@ -771,20 +788,49 @@ class AzureRMModuleBase(object):
|
||||||
setattr(client, '_ansible_models', importlib.import_module(client_type.__module__).models)
|
setattr(client, '_ansible_models', importlib.import_module(client_type.__module__).models)
|
||||||
client.models = types.MethodType(_ansible_get_models, client)
|
client.models = types.MethodType(_ansible_get_models, client)
|
||||||
|
|
||||||
# Add user agent for Ansible
|
client.config = self.add_user_agent(client.config)
|
||||||
client.config.add_user_agent(ANSIBLE_USER_AGENT)
|
|
||||||
# Add user agent when running from Cloud Shell
|
|
||||||
if CLOUDSHELL_USER_AGENT_KEY in os.environ:
|
|
||||||
client.config.add_user_agent(os.environ[CLOUDSHELL_USER_AGENT_KEY])
|
|
||||||
# Add user agent when running from VSCode extension
|
|
||||||
if VSCODEEXT_USER_AGENT_KEY in os.environ:
|
|
||||||
client.config.add_user_agent(os.environ[VSCODEEXT_USER_AGENT_KEY])
|
|
||||||
|
|
||||||
if self.azure_auth._cert_validation_mode == 'ignore':
|
if self.azure_auth._cert_validation_mode == 'ignore':
|
||||||
client.config.session_configuration_callback = self._validation_ignore_callback
|
client.config.session_configuration_callback = self._validation_ignore_callback
|
||||||
|
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
def add_user_agent(self, config):
|
||||||
|
# Add user agent for Ansible
|
||||||
|
config.add_user_agent(ANSIBLE_USER_AGENT)
|
||||||
|
# Add user agent when running from Cloud Shell
|
||||||
|
if CLOUDSHELL_USER_AGENT_KEY in os.environ:
|
||||||
|
config.add_user_agent(os.environ[CLOUDSHELL_USER_AGENT_KEY])
|
||||||
|
# Add user agent when running from VSCode extension
|
||||||
|
if VSCODEEXT_USER_AGENT_KEY in os.environ:
|
||||||
|
config.add_user_agent(os.environ[VSCODEEXT_USER_AGENT_KEY])
|
||||||
|
return config
|
||||||
|
|
||||||
|
def generate_sas_token(self, **kwags):
|
||||||
|
base_url = kwags.get('base_url', None)
|
||||||
|
expiry = kwags.get('expiry', time() + 3600)
|
||||||
|
key = kwags.get('key', None)
|
||||||
|
policy = kwags.get('policy', None)
|
||||||
|
url = quote_plus(base_url)
|
||||||
|
ttl = int(expiry)
|
||||||
|
sign_key = '{0}\n{1}'.format(url, ttl)
|
||||||
|
signature = b64encode(HMAC(b64decode(key), sign_key.encode('utf-8'), sha256).digest())
|
||||||
|
result = {
|
||||||
|
'sr': url,
|
||||||
|
'sig': signature,
|
||||||
|
'se': str(ttl),
|
||||||
|
}
|
||||||
|
if policy:
|
||||||
|
result['skn'] = policy
|
||||||
|
return 'SharedAccessSignature ' + urlencode(result)
|
||||||
|
|
||||||
|
def get_data_svc_client(self, **kwags):
|
||||||
|
url = kwags.get('base_url', None)
|
||||||
|
config = AzureConfiguration(base_url='https://{0}'.format(url))
|
||||||
|
config.credentials = AzureSASAuthentication(token=self.generate_sas_token(**kwags))
|
||||||
|
config = self.add_user_agent(config)
|
||||||
|
return ServiceClient(creds=config.credentials, config=config)
|
||||||
|
|
||||||
# passthru methods to AzureAuth instance for backcompat
|
# passthru methods to AzureAuth instance for backcompat
|
||||||
@property
|
@property
|
||||||
def credentials(self):
|
def credentials(self):
|
||||||
|
@ -1020,6 +1066,45 @@ class AzureRMModuleBase(object):
|
||||||
def automation_models(self):
|
def automation_models(self):
|
||||||
return AutomationModel
|
return AutomationModel
|
||||||
|
|
||||||
|
@property
|
||||||
|
def IoThub_client(self):
|
||||||
|
self.log('Getting iothub client')
|
||||||
|
if not self._IoThub_client:
|
||||||
|
self._IoThub_client = self.get_mgmt_svc_client(IotHubClient,
|
||||||
|
base_url=self._cloud_environment.endpoints.resource_manager)
|
||||||
|
return self._IoThub_client
|
||||||
|
|
||||||
|
@property
|
||||||
|
def IoThub_models(self):
|
||||||
|
return IoTHubModels
|
||||||
|
|
||||||
|
|
||||||
|
class AzureSASAuthentication(Authentication):
|
||||||
|
"""Simple SAS Authentication.
|
||||||
|
An implementation of Authentication in
|
||||||
|
https://github.com/Azure/msrest-for-python/blob/0732bc90bdb290e5f58c675ffdd7dbfa9acefc93/msrest/authentication.py
|
||||||
|
|
||||||
|
:param str token: SAS token
|
||||||
|
"""
|
||||||
|
def __init__(self, token):
|
||||||
|
self.token = token
|
||||||
|
|
||||||
|
def signed_session(self):
|
||||||
|
session = super(AzureSASAuthentication, self).signed_session()
|
||||||
|
session.headers['Authorization'] = self.token
|
||||||
|
return session
|
||||||
|
|
||||||
|
def automation_client(self):
|
||||||
|
self.log('Getting automation client')
|
||||||
|
if not self._automation_client:
|
||||||
|
self._automation_client = self.get_mgmt_svc_client(AutomationClient,
|
||||||
|
base_url=self._cloud_environment.endpoints.resource_manager)
|
||||||
|
return self._automation_client
|
||||||
|
|
||||||
|
@property
|
||||||
|
def automation_models(self):
|
||||||
|
return AutomationModel
|
||||||
|
|
||||||
|
|
||||||
class AzureRMAuthException(Exception):
|
class AzureRMAuthException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
472
lib/ansible/modules/cloud/azure/azure_rm_iotdevice.py
Normal file
472
lib/ansible/modules/cloud/azure/azure_rm_iotdevice.py
Normal file
|
@ -0,0 +1,472 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2019 Yuwei Zhou, <yuwzho@microsoft.com>
|
||||||
|
#
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'}
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: azure_rm_iotdevice
|
||||||
|
version_added: "2.9"
|
||||||
|
short_description: Manage Azure IoT hub device
|
||||||
|
description:
|
||||||
|
- Create, delete an Azure IoT hub device.
|
||||||
|
options:
|
||||||
|
hub:
|
||||||
|
description:
|
||||||
|
- Name of IoT Hub.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
hub_policy_name:
|
||||||
|
description:
|
||||||
|
- Policy name of the IoT Hub which will be used to query from IoT hub.
|
||||||
|
- This policy should have 'RegistryWrite, ServiceConnect, DeviceConnect' accesses. You may get 401 error when you lack any of these.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
hub_policy_key:
|
||||||
|
description:
|
||||||
|
- Key of the I(hub_policy_name).
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the IoT hub device identity.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- State of the IoT hub. Use C(present) to create or update an IoT hub device and C(absent) to delete an IoT hub device.
|
||||||
|
type: str
|
||||||
|
default: present
|
||||||
|
choices:
|
||||||
|
- absent
|
||||||
|
- present
|
||||||
|
auth_method:
|
||||||
|
description:
|
||||||
|
- The authorization type an entity is to be created with.
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- sas
|
||||||
|
- certificate_authority
|
||||||
|
- self_signed
|
||||||
|
default: sas
|
||||||
|
primary_key:
|
||||||
|
description:
|
||||||
|
- Explicit self-signed certificate thumbprint to use for primary key.
|
||||||
|
- Explicit Shared Private Key to use for primary key.
|
||||||
|
type: str
|
||||||
|
aliases:
|
||||||
|
- primary_thumbprint
|
||||||
|
secondary_key:
|
||||||
|
description:
|
||||||
|
- Explicit self-signed certificate thumbprint to use for secondary key.
|
||||||
|
- Explicit Shared Private Key to use for secondary key.
|
||||||
|
type: str
|
||||||
|
aliases:
|
||||||
|
- secondary_thumbprint
|
||||||
|
status:
|
||||||
|
description:
|
||||||
|
- Set device status upon creation.
|
||||||
|
type: bool
|
||||||
|
edge_enabled:
|
||||||
|
description:
|
||||||
|
- Flag indicating edge enablement.
|
||||||
|
- Not supported in IoT Hub with Basic tier.
|
||||||
|
type: bool
|
||||||
|
twin_tags:
|
||||||
|
description:
|
||||||
|
- A section that the solution back end can read from and write to.
|
||||||
|
- Tags are not visible to device apps.
|
||||||
|
- "The tag can be nested dictionary, '.', '$', '#', ' ' is not allowed in the key."
|
||||||
|
- List is not supported.
|
||||||
|
- Not supported in IoT Hub with Basic tier.
|
||||||
|
type: dict
|
||||||
|
desired:
|
||||||
|
description:
|
||||||
|
- Used along with reported properties to synchronize device configuration or conditions.
|
||||||
|
- "The tag can be nested dictionary, '.', '$', '#', ' ' is not allowed in the key."
|
||||||
|
- List is not supported.
|
||||||
|
- Not supported in IoT Hub with Basic tier.
|
||||||
|
type: dict
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- azure
|
||||||
|
- azure_tags
|
||||||
|
|
||||||
|
author:
|
||||||
|
- Yuwei Zhou (@yuwzho)
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Create simplest Azure IoT Hub device
|
||||||
|
azure_rm_iotdevice:
|
||||||
|
hub: myHub
|
||||||
|
name: Testing
|
||||||
|
hub_policy_name: iothubowner
|
||||||
|
hub_policy_key: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
|
||||||
|
- name: Create Azure IoT Edge device
|
||||||
|
azure_rm_iotdevice:
|
||||||
|
hub: myHub
|
||||||
|
name: Testing
|
||||||
|
hub_policy_name: iothubowner
|
||||||
|
hub_policy_key: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
edge_enabled: yes
|
||||||
|
|
||||||
|
- name: Create Azure IoT Hub device with device twin properties and tag
|
||||||
|
azure_rm_iotdevice:
|
||||||
|
hub: myHub
|
||||||
|
name: Testing
|
||||||
|
hub_policy_name: iothubowner
|
||||||
|
hub_policy_key: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
twin_tags:
|
||||||
|
location:
|
||||||
|
country: US
|
||||||
|
city: Redmond
|
||||||
|
sensor: humidity
|
||||||
|
desired:
|
||||||
|
period: 100
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
device:
|
||||||
|
description:
|
||||||
|
- IoT Hub device.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
"authentication": {
|
||||||
|
"symmetricKey": {
|
||||||
|
"primaryKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||||
|
"secondaryKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
},
|
||||||
|
"type": "sas",
|
||||||
|
"x509Thumbprint": {
|
||||||
|
"primaryThumbprint": null,
|
||||||
|
"secondaryThumbprint": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"iotEdge": false
|
||||||
|
},
|
||||||
|
"changed": true,
|
||||||
|
"cloudToDeviceMessageCount": 0,
|
||||||
|
"connectionState": "Disconnected",
|
||||||
|
"connectionStateUpdatedTime": "0001-01-01T00:00:00",
|
||||||
|
"deviceId": "Testing",
|
||||||
|
"etag": "NzA2NjU2ODc=",
|
||||||
|
"failed": false,
|
||||||
|
"generationId": "636903014505613307",
|
||||||
|
"lastActivityTime": "0001-01-01T00:00:00",
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"authentication": {
|
||||||
|
"symmetricKey": {
|
||||||
|
"primaryKey": "XXXXXXXXXXXXXXXXXXX",
|
||||||
|
"secondaryKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
},
|
||||||
|
"type": "sas",
|
||||||
|
"x509Thumbprint": {
|
||||||
|
"primaryThumbprint": null,
|
||||||
|
"secondaryThumbprint": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cloudToDeviceMessageCount": 0,
|
||||||
|
"connectionState": "Disconnected",
|
||||||
|
"connectionStateUpdatedTime": "0001-01-01T00:00:00",
|
||||||
|
"deviceId": "testdevice",
|
||||||
|
"etag": "MjgxOTE5ODE4",
|
||||||
|
"generationId": "636903840872788074",
|
||||||
|
"lastActivityTime": "0001-01-01T00:00:00",
|
||||||
|
"managedBy": null,
|
||||||
|
"moduleId": "test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"desired": {
|
||||||
|
"$metadata": {
|
||||||
|
"$lastUpdated": "2019-04-10T05:00:46.2702079Z",
|
||||||
|
"$lastUpdatedVersion": 8,
|
||||||
|
"period": {
|
||||||
|
"$lastUpdated": "2019-04-10T05:00:46.2702079Z",
|
||||||
|
"$lastUpdatedVersion": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$version": 1,
|
||||||
|
"period": 100
|
||||||
|
},
|
||||||
|
"reported": {
|
||||||
|
"$metadata": {
|
||||||
|
"$lastUpdated": "2019-04-08T06:24:10.5613307Z"
|
||||||
|
},
|
||||||
|
"$version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": "enabled",
|
||||||
|
"statusReason": null,
|
||||||
|
"statusUpdatedTime": "0001-01-01T00:00:00",
|
||||||
|
"tags": {
|
||||||
|
"location": {
|
||||||
|
"country": "us",
|
||||||
|
"city": "Redmond"
|
||||||
|
},
|
||||||
|
"sensor": "humidity"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''' # NOQA
|
||||||
|
|
||||||
|
import json
|
||||||
|
import copy
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ansible.module_utils.azure_rm_common import AzureRMModuleBase, format_resource_id
|
||||||
|
from ansible.module_utils.common.dict_transformations import _snake_to_camel
|
||||||
|
|
||||||
|
try:
|
||||||
|
from msrestazure.tools import parse_resource_id
|
||||||
|
from msrestazure.azure_exceptions import CloudError
|
||||||
|
except ImportError:
|
||||||
|
# This is handled in azure_rm_common
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AzureRMIoTDevice(AzureRMModuleBase):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.module_arg_spec = dict(
|
||||||
|
name=dict(type='str', required=True),
|
||||||
|
hub_policy_name=dict(type='str', required=True),
|
||||||
|
hub_policy_key=dict(type='str', required=True),
|
||||||
|
hub=dict(type='str', required=True),
|
||||||
|
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||||
|
status=dict(type='bool'),
|
||||||
|
edge_enabled=dict(type='bool'),
|
||||||
|
twin_tags=dict(type='dict'),
|
||||||
|
desired=dict(type='dict'),
|
||||||
|
auth_method=dict(type='str', choices=['self_signed', 'sas', 'certificate_authority'], default='sas'),
|
||||||
|
primary_key=dict(type='str', no_log=True, aliases=['primary_thumbprint']),
|
||||||
|
secondary_key=dict(type='str', no_log=True, aliases=['secondary_thumbprint'])
|
||||||
|
)
|
||||||
|
|
||||||
|
self.results = dict(
|
||||||
|
changed=False,
|
||||||
|
id=None
|
||||||
|
)
|
||||||
|
|
||||||
|
self.name = None
|
||||||
|
self.hub = None
|
||||||
|
self.hub_policy_key = None
|
||||||
|
self.hub_policy_name = None
|
||||||
|
self.state = None
|
||||||
|
self.status = None
|
||||||
|
self.edge_enabled = None
|
||||||
|
self.twin_tags = None
|
||||||
|
self.desired = None
|
||||||
|
self.auth_method = None
|
||||||
|
self.primary_key = None
|
||||||
|
self.secondary_key = None
|
||||||
|
|
||||||
|
required_if = [
|
||||||
|
['auth_method', 'self_signed', ['certificate_authority']]
|
||||||
|
]
|
||||||
|
|
||||||
|
self._base_url = None
|
||||||
|
self._mgmt_client = None
|
||||||
|
self.query_parameters = {
|
||||||
|
'api-version': '2018-06-30'
|
||||||
|
}
|
||||||
|
self.header_parameters = {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
'accept-language': 'en-US'
|
||||||
|
}
|
||||||
|
super(AzureRMIoTDevice, self).__init__(self.module_arg_spec, supports_check_mode=True, required_if=required_if)
|
||||||
|
|
||||||
|
def exec_module(self, **kwargs):
|
||||||
|
|
||||||
|
for key in self.module_arg_spec.keys():
|
||||||
|
setattr(self, key, kwargs[key])
|
||||||
|
|
||||||
|
self._base_url = '{0}.azure-devices.net'.format(self.hub)
|
||||||
|
config = {
|
||||||
|
'base_url': self._base_url,
|
||||||
|
'key': self.hub_policy_key,
|
||||||
|
'policy': self.hub_policy_name
|
||||||
|
}
|
||||||
|
self._mgmt_client = self.get_data_svc_client(**config)
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
device = self.get_device()
|
||||||
|
if self.state == 'present':
|
||||||
|
if not device:
|
||||||
|
changed = True
|
||||||
|
auth = {'type': _snake_to_camel(self.auth_method)}
|
||||||
|
if self.auth_method == 'self_signed':
|
||||||
|
auth['x509Thumbprint'] = {
|
||||||
|
'primaryThumbprint': self.primary_key,
|
||||||
|
'secondaryThumbprint': self.secondary_key
|
||||||
|
}
|
||||||
|
elif self.auth_method == 'sas':
|
||||||
|
auth['symmetricKey'] = {
|
||||||
|
'primaryKey': self.primary_key,
|
||||||
|
'secondaryKey': self.secondary_key
|
||||||
|
}
|
||||||
|
device = {
|
||||||
|
'deviceId': self.name,
|
||||||
|
'capabilities': {'iotEdge': self.edge_enabled or False},
|
||||||
|
'authentication': auth
|
||||||
|
}
|
||||||
|
if self.status is not None and not self.status:
|
||||||
|
device['status'] = 'disabled'
|
||||||
|
else:
|
||||||
|
if self.edge_enabled is not None and self.edge_enabled != device['capabilities']['iotEdge']:
|
||||||
|
changed = True
|
||||||
|
device['capabilities']['iotEdge'] = self.edge_enabled
|
||||||
|
if self.status is not None:
|
||||||
|
status = 'enabled' if self.status else 'disabled'
|
||||||
|
if status != device['status']:
|
||||||
|
changed = True
|
||||||
|
device['status'] = status
|
||||||
|
if changed and not self.check_mode:
|
||||||
|
device = self.create_or_update_device(device)
|
||||||
|
twin = self.get_twin()
|
||||||
|
if twin:
|
||||||
|
if not twin.get('tags'):
|
||||||
|
twin['tags'] = dict()
|
||||||
|
twin_change = False
|
||||||
|
if self.twin_tags and not self.is_equal(self.twin_tags, twin['tags']):
|
||||||
|
twin_change = True
|
||||||
|
if self.desired and not self.is_equal(self.desired, twin['properties']['desired']):
|
||||||
|
twin_change = True
|
||||||
|
if twin_change and not self.check_mode:
|
||||||
|
self.update_twin(twin)
|
||||||
|
changed = changed or twin_change
|
||||||
|
device['tags'] = twin.get('tags') or dict()
|
||||||
|
device['properties'] = twin['properties']
|
||||||
|
device['modules'] = self.list_device_modules()
|
||||||
|
elif self.twin_tags or self.desired:
|
||||||
|
self.fail("Device twin is not supported in IoT Hub with basic tier.")
|
||||||
|
elif device:
|
||||||
|
if not self.check_mode:
|
||||||
|
self.delete_device(device['etag'])
|
||||||
|
changed = True
|
||||||
|
device = None
|
||||||
|
self.results = device or dict()
|
||||||
|
self.results['changed'] = changed
|
||||||
|
return self.results
|
||||||
|
|
||||||
|
def is_equal(self, updated, original):
|
||||||
|
changed = False
|
||||||
|
if not isinstance(updated, dict):
|
||||||
|
self.fail('The Property or Tag should be a dict')
|
||||||
|
for key in updated.keys():
|
||||||
|
if re.search(r'[.|$|#|\s]', key):
|
||||||
|
self.fail("Property or Tag name has invalid characters: '.', '$', '#' or ' '. Got '{0}'".format(key))
|
||||||
|
original_value = original.get(key)
|
||||||
|
updated_value = updated[key]
|
||||||
|
if isinstance(updated_value, dict):
|
||||||
|
if not isinstance(original_value, dict):
|
||||||
|
changed = True
|
||||||
|
original[key] = updated_value
|
||||||
|
elif not self.is_equal(updated_value, original_value):
|
||||||
|
changed = True
|
||||||
|
elif original_value != updated_value:
|
||||||
|
changed = True
|
||||||
|
original[key] = updated_value
|
||||||
|
return not changed
|
||||||
|
|
||||||
|
def create_or_update_device(self, device):
|
||||||
|
try:
|
||||||
|
url = '/devices/{0}'.format(self.name)
|
||||||
|
headers = copy.copy(self.header_parameters)
|
||||||
|
if device.get('etag'):
|
||||||
|
headers['If-Match'] = '"{0}"'.format(device['etag'])
|
||||||
|
request = self._mgmt_client.put(url, self.query_parameters)
|
||||||
|
response = self._mgmt_client.send(request=request, headers=headers, content=device)
|
||||||
|
if response.status_code not in [200, 201, 202]:
|
||||||
|
raise CloudError(response)
|
||||||
|
return json.loads(response.text)
|
||||||
|
except Exception as exc:
|
||||||
|
if exc.status_code in [403] and self.edge_enabled:
|
||||||
|
self.fail('Edge device is not supported in IoT Hub with Basic tier.')
|
||||||
|
else:
|
||||||
|
self.fail('Error when creating or updating IoT Hub device {0}: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def delete_device(self, etag):
|
||||||
|
try:
|
||||||
|
url = '/devices/{0}'.format(self.name)
|
||||||
|
headers = copy.copy(self.header_parameters)
|
||||||
|
headers['If-Match'] = '"{0}"'.format(etag)
|
||||||
|
request = self._mgmt_client.delete(url, self.query_parameters)
|
||||||
|
response = self._mgmt_client.send(request=request, headers=headers)
|
||||||
|
if response.status_code not in [204]:
|
||||||
|
raise CloudError(response)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when deleting IoT Hub device {0}: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def get_device(self):
|
||||||
|
try:
|
||||||
|
url = '/devices/{0}'.format(self.name)
|
||||||
|
device = self._https_get(url, self.query_parameters, self.header_parameters)
|
||||||
|
return device
|
||||||
|
except Exception as exc:
|
||||||
|
if exc.status_code in [404]:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
self.fail('Error when getting IoT Hub device {0}: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def get_twin(self):
|
||||||
|
try:
|
||||||
|
url = '/twins/{0}'.format(self.name)
|
||||||
|
return self._https_get(url, self.query_parameters, self.header_parameters)
|
||||||
|
except Exception as exc:
|
||||||
|
if exc.status_code in [403]:
|
||||||
|
# The Basic sku has nothing to to with twin
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
self.fail('Error when getting IoT Hub device {0} twin: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def update_twin(self, twin):
|
||||||
|
try:
|
||||||
|
url = '/twins/{0}'.format(self.name)
|
||||||
|
headers = copy.copy(self.header_parameters)
|
||||||
|
headers['If-Match'] = '"{0}"'.format(twin['etag'])
|
||||||
|
request = self._mgmt_client.patch(url, self.query_parameters)
|
||||||
|
response = self._mgmt_client.send(request=request, headers=headers, content=twin)
|
||||||
|
if response.status_code not in [200]:
|
||||||
|
raise CloudError(response)
|
||||||
|
return json.loads(response.text)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when creating or updating IoT Hub device twin {0}: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def list_device_modules(self):
|
||||||
|
try:
|
||||||
|
url = '/devices/{0}/modules'.format(self.name)
|
||||||
|
return self._https_get(url, self.query_parameters, self.header_parameters)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when listing IoT Hub device {0} modules: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def _https_get(self, url, query_parameters, header_parameters):
|
||||||
|
request = self._mgmt_client.get(url, query_parameters)
|
||||||
|
response = self._mgmt_client.send(request=request, headers=header_parameters, content=None)
|
||||||
|
if response.status_code not in [200]:
|
||||||
|
raise CloudError(response)
|
||||||
|
return json.loads(response.text)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
AzureRMIoTDevice()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
313
lib/ansible/modules/cloud/azure/azure_rm_iotdevice_info.py
Normal file
313
lib/ansible/modules/cloud/azure/azure_rm_iotdevice_info.py
Normal file
|
@ -0,0 +1,313 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2019 Yuwei Zhou, <yuwzho@microsoft.com>
|
||||||
|
#
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'}
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: azure_rm_iotdevice_info
|
||||||
|
version_added: "2.9"
|
||||||
|
short_description: Facts of Azure IoT hub device
|
||||||
|
description:
|
||||||
|
- Query, get Azure IoT hub device.
|
||||||
|
options:
|
||||||
|
hub:
|
||||||
|
description:
|
||||||
|
- Name of IoT Hub.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
hub_policy_name:
|
||||||
|
description:
|
||||||
|
- Policy name of the IoT Hub which will be used to query from IoT hub.
|
||||||
|
- This policy should have at least 'Registry Read' access.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
hub_policy_key:
|
||||||
|
description:
|
||||||
|
- Key of the I(hub_policy_name).
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the IoT hub device identity.
|
||||||
|
type: str
|
||||||
|
aliases:
|
||||||
|
- device_id
|
||||||
|
module_id:
|
||||||
|
description:
|
||||||
|
- Name of the IoT hub device module.
|
||||||
|
- Must use with I(device_id) defined.
|
||||||
|
type: str
|
||||||
|
query:
|
||||||
|
description:
|
||||||
|
- Query an IoT hub to retrieve information regarding device twins using a SQL-like language.
|
||||||
|
- "See U(https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-query-language)."
|
||||||
|
type: str
|
||||||
|
top:
|
||||||
|
description:
|
||||||
|
- Used when I(name) not defined.
|
||||||
|
- List the top n devices in the query.
|
||||||
|
type: int
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- azure
|
||||||
|
- azure_tags
|
||||||
|
|
||||||
|
author:
|
||||||
|
- Yuwei Zhou (@yuwzho)
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Get the details of a device
|
||||||
|
azure_rm_iotdevice_info:
|
||||||
|
name: Testing
|
||||||
|
hub: MyIoTHub
|
||||||
|
hub_policy_name: registryRead
|
||||||
|
hub_policy_key: XXXXXXXXXXXXXXXXXXXX
|
||||||
|
|
||||||
|
- name: Query all device modules in an IoT Hub
|
||||||
|
azure_rm_iotdevice_info:
|
||||||
|
query: "SELECT * FROM devices.modules"
|
||||||
|
hub: MyIoTHub
|
||||||
|
hub_policy_name: registryRead
|
||||||
|
hub_policy_key: XXXXXXXXXXXXXXXXXXXX
|
||||||
|
|
||||||
|
- name: List all devices in an IoT Hub
|
||||||
|
azure_rm_iotdevice_info:
|
||||||
|
hub: MyIoTHub
|
||||||
|
hub_policy_name: registryRead
|
||||||
|
hub_policy_key: XXXXXXXXXXXXXXXXXXXX
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
iot_devices:
|
||||||
|
description:
|
||||||
|
- IoT Hub device.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
"authentication": {
|
||||||
|
"symmetricKey": {
|
||||||
|
"primaryKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||||
|
"secondaryKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
},
|
||||||
|
"type": "sas",
|
||||||
|
"x509Thumbprint": {
|
||||||
|
"primaryThumbprint": null,
|
||||||
|
"secondaryThumbprint": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"iotEdge": false
|
||||||
|
},
|
||||||
|
"changed": true,
|
||||||
|
"cloudToDeviceMessageCount": 0,
|
||||||
|
"connectionState": "Disconnected",
|
||||||
|
"connectionStateUpdatedTime": "0001-01-01T00:00:00",
|
||||||
|
"deviceId": "Testing",
|
||||||
|
"etag": "NzA2NjU2ODc=",
|
||||||
|
"failed": false,
|
||||||
|
"generationId": "636903014505613307",
|
||||||
|
"lastActivityTime": "0001-01-01T00:00:00",
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"authentication": {
|
||||||
|
"symmetricKey": {
|
||||||
|
"primaryKey": "XXXXXXXXXXXXXXXXXXX",
|
||||||
|
"secondaryKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
},
|
||||||
|
"type": "sas",
|
||||||
|
"x509Thumbprint": {
|
||||||
|
"primaryThumbprint": null,
|
||||||
|
"secondaryThumbprint": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cloudToDeviceMessageCount": 0,
|
||||||
|
"connectionState": "Disconnected",
|
||||||
|
"connectionStateUpdatedTime": "0001-01-01T00:00:00",
|
||||||
|
"deviceId": "testdevice",
|
||||||
|
"etag": "MjgxOTE5ODE4",
|
||||||
|
"generationId": "636903840872788074",
|
||||||
|
"lastActivityTime": "0001-01-01T00:00:00",
|
||||||
|
"managedBy": null,
|
||||||
|
"moduleId": "test"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"desired": {
|
||||||
|
"$metadata": {
|
||||||
|
"$lastUpdated": "2019-04-10T05:00:46.2702079Z",
|
||||||
|
"$lastUpdatedVersion": 8,
|
||||||
|
"period": {
|
||||||
|
"$lastUpdated": "2019-04-10T05:00:46.2702079Z",
|
||||||
|
"$lastUpdatedVersion": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$version": 1,
|
||||||
|
"period": 100
|
||||||
|
},
|
||||||
|
"reported": {
|
||||||
|
"$metadata": {
|
||||||
|
"$lastUpdated": "2019-04-08T06:24:10.5613307Z"
|
||||||
|
},
|
||||||
|
"$version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": "enabled",
|
||||||
|
"statusReason": null,
|
||||||
|
"statusUpdatedTime": "0001-01-01T00:00:00",
|
||||||
|
"tags": {
|
||||||
|
"location": {
|
||||||
|
"country": "us",
|
||||||
|
"city": "Redmond"
|
||||||
|
},
|
||||||
|
"sensor": "humidity"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''' # NOQA
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ansible.module_utils.azure_rm_common import AzureRMModuleBase, format_resource_id
|
||||||
|
from ansible.module_utils.common.dict_transformations import _snake_to_camel, _camel_to_snake
|
||||||
|
|
||||||
|
try:
|
||||||
|
from msrestazure.tools import parse_resource_id
|
||||||
|
from msrestazure.azure_exceptions import CloudError
|
||||||
|
except ImportError:
|
||||||
|
# This is handled in azure_rm_common
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AzureRMIoTDeviceFacts(AzureRMModuleBase):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.module_arg_spec = dict(
|
||||||
|
name=dict(type='str', aliases=['device_id']),
|
||||||
|
module_id=dict(type='str'),
|
||||||
|
query=dict(type='str'),
|
||||||
|
hub=dict(type='str', required=True),
|
||||||
|
hub_policy_name=dict(type='str', required=True),
|
||||||
|
hub_policy_key=dict(type='str', required=True),
|
||||||
|
top=dict(type='int')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.results = dict(
|
||||||
|
changed=False,
|
||||||
|
iot_devices=[]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.name = None
|
||||||
|
self.module_id = None
|
||||||
|
self.hub = None
|
||||||
|
self.hub_policy_name = None
|
||||||
|
self.hub_policy_key = None
|
||||||
|
self.top = None
|
||||||
|
|
||||||
|
self._mgmt_client = None
|
||||||
|
self._base_url = None
|
||||||
|
self.query_parameters = {
|
||||||
|
'api-version': '2018-06-30'
|
||||||
|
}
|
||||||
|
self.header_parameters = {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
'accept-language': 'en-US'
|
||||||
|
}
|
||||||
|
super(AzureRMIoTDeviceFacts, self).__init__(self.module_arg_spec, supports_check_mode=True)
|
||||||
|
|
||||||
|
def exec_module(self, **kwargs):
|
||||||
|
|
||||||
|
for key in self.module_arg_spec.keys():
|
||||||
|
setattr(self, key, kwargs[key])
|
||||||
|
|
||||||
|
self._base_url = '{0}.azure-devices.net'.format(self.hub)
|
||||||
|
config = {
|
||||||
|
'base_url': self._base_url,
|
||||||
|
'key': self.hub_policy_key,
|
||||||
|
'policy': self.hub_policy_name
|
||||||
|
}
|
||||||
|
if self.top:
|
||||||
|
self.query_parameters['top'] = self.top
|
||||||
|
self._mgmt_client = self.get_data_svc_client(**config)
|
||||||
|
|
||||||
|
response = []
|
||||||
|
if self.module_id:
|
||||||
|
response = [self.get_device_module()]
|
||||||
|
elif self.name:
|
||||||
|
response = [self.get_device()]
|
||||||
|
elif self.query:
|
||||||
|
response = self.hub_query()
|
||||||
|
else:
|
||||||
|
response = self.list_devices()
|
||||||
|
|
||||||
|
self.results['iot_devices'] = response
|
||||||
|
return self.results
|
||||||
|
|
||||||
|
def hub_query(self):
|
||||||
|
try:
|
||||||
|
url = '/devices/query'
|
||||||
|
request = self._mgmt_client.post(url, self.query_parameters)
|
||||||
|
query = {
|
||||||
|
'query': self.query
|
||||||
|
}
|
||||||
|
response = self._mgmt_client.send(request=request, headers=self.header_parameters, content=query)
|
||||||
|
if response.status_code not in [200]:
|
||||||
|
raise CloudError(response)
|
||||||
|
return json.loads(response.text)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when running query "{0}" in IoT Hub {1}: {2}'.format(self.query, self.hub, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def get_device(self):
|
||||||
|
try:
|
||||||
|
url = '/devices/{0}'.format(self.name)
|
||||||
|
device = self._https_get(url, self.query_parameters, self.header_parameters)
|
||||||
|
device['modules'] = self.list_device_modules()
|
||||||
|
return device
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when getting IoT Hub device {0}: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def get_device_module(self):
|
||||||
|
try:
|
||||||
|
url = '/devices/{0}/modules/{1}'.format(self.name, self.module_id)
|
||||||
|
return self._https_get(url, self.query_parameters, self.header_parameters)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when getting IoT Hub device {0}: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def list_device_modules(self):
|
||||||
|
try:
|
||||||
|
url = '/devices/{0}/modules'.format(self.name)
|
||||||
|
return self._https_get(url, self.query_parameters, self.header_parameters)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when getting IoT Hub device {0}: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def list_devices(self):
|
||||||
|
try:
|
||||||
|
url = '/devices'
|
||||||
|
return self._https_get(url, self.query_parameters, self.header_parameters)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when listing IoT Hub devices in {0}: {1}'.format(self.hub, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def _https_get(self, url, query_parameters, header_parameters):
|
||||||
|
request = self._mgmt_client.get(url, query_parameters)
|
||||||
|
response = self._mgmt_client.send(request=request, headers=header_parameters, content=None)
|
||||||
|
if response.status_code not in [200]:
|
||||||
|
raise CloudError(response)
|
||||||
|
return json.loads(response.text)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
AzureRMIoTDeviceFacts()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
379
lib/ansible/modules/cloud/azure/azure_rm_iotdevicemodule.py
Normal file
379
lib/ansible/modules/cloud/azure/azure_rm_iotdevicemodule.py
Normal file
|
@ -0,0 +1,379 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2019 Yuwei Zhou, <yuwzho@microsoft.com>
|
||||||
|
#
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'}
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: azure_rm_iotdevicemodule
|
||||||
|
version_added: "2.9"
|
||||||
|
short_description: Manage Azure IoT hub device module
|
||||||
|
description:
|
||||||
|
- Create, delete an Azure IoT hub device module.
|
||||||
|
options:
|
||||||
|
hub:
|
||||||
|
description:
|
||||||
|
- Name of IoT Hub.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
hub_policy_name:
|
||||||
|
description:
|
||||||
|
- Policy name of the IoT Hub which will be used to query from IoT hub.
|
||||||
|
- This policy should have at least 'Registry Read' access.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
hub_policy_key:
|
||||||
|
description:
|
||||||
|
- Key of the I(hub_policy_name).
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the IoT hub device identity.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
device:
|
||||||
|
description:
|
||||||
|
- Device name the module associate with.
|
||||||
|
required: true
|
||||||
|
type: str
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- State of the IoT hub. Use C(present) to create or update an IoT hub device and C(absent) to delete an IoT hub device.
|
||||||
|
type: str
|
||||||
|
default: present
|
||||||
|
choices:
|
||||||
|
- absent
|
||||||
|
- present
|
||||||
|
auth_method:
|
||||||
|
description:
|
||||||
|
- The authorization type an entity is to be created with.
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- sas
|
||||||
|
- certificate_authority
|
||||||
|
- self_signed
|
||||||
|
default: sas
|
||||||
|
primary_key:
|
||||||
|
description:
|
||||||
|
- Explicit self-signed certificate thumbprint to use for primary key.
|
||||||
|
- Explicit Shared Private Key to use for primary key.
|
||||||
|
type: str
|
||||||
|
aliases:
|
||||||
|
- primary_thumbprint
|
||||||
|
secondary_key:
|
||||||
|
description:
|
||||||
|
- Explicit self-signed certificate thumbprint to use for secondary key.
|
||||||
|
- Explicit Shared Private Key to use for secondary key.
|
||||||
|
type: str
|
||||||
|
aliases:
|
||||||
|
- secondary_thumbprint
|
||||||
|
twin_tags:
|
||||||
|
description:
|
||||||
|
- A section that the solution back end can read from and write to.
|
||||||
|
- Tags are not visible to device apps.
|
||||||
|
- "The tag can be nested dictionary, '.', '$', '#', ' ' is not allowed in the key."
|
||||||
|
- List is not supported.
|
||||||
|
type: dict
|
||||||
|
desired:
|
||||||
|
description:
|
||||||
|
- Used along with reported properties to synchronize device configuration or conditions.
|
||||||
|
- "The tag can be nested dictionary, '.', '$', '#', ' ' is not allowed in the key."
|
||||||
|
- List is not supported.
|
||||||
|
type: dict
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- azure
|
||||||
|
- azure_tags
|
||||||
|
|
||||||
|
author:
|
||||||
|
- Yuwei Zhou (@yuwzho)
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Create simplest Azure IoT Hub device module
|
||||||
|
azure_rm_iotdevicemodule:
|
||||||
|
hub: myHub
|
||||||
|
name: Testing
|
||||||
|
device: mydevice
|
||||||
|
hub_policy_name: iothubowner
|
||||||
|
hub_policy_key: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
|
||||||
|
- name: Create Azure IoT Edge device module
|
||||||
|
azure_rm_iotdevice:
|
||||||
|
hub: myHub
|
||||||
|
device: mydevice
|
||||||
|
name: Testing
|
||||||
|
hub_policy_name: iothubowner
|
||||||
|
hub_policy_key: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
edge_enabled: yes
|
||||||
|
|
||||||
|
- name: Create Azure IoT Hub device module with module twin properties and tag
|
||||||
|
azure_rm_iotdevice:
|
||||||
|
hub: myHub
|
||||||
|
name: Testing
|
||||||
|
device: mydevice
|
||||||
|
hub_policy_name: iothubowner
|
||||||
|
hub_policy_key: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
twin_tags:
|
||||||
|
location:
|
||||||
|
country: US
|
||||||
|
city: Redmond
|
||||||
|
sensor: humidity
|
||||||
|
desired:
|
||||||
|
period: 100
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
module:
|
||||||
|
description:
|
||||||
|
- IoT Hub device.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
"authentication": {
|
||||||
|
"symmetricKey": {
|
||||||
|
"primaryKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||||
|
"secondaryKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||||
|
},
|
||||||
|
"type": "sas",
|
||||||
|
"x509Thumbprint": {
|
||||||
|
"primaryThumbprint": null,
|
||||||
|
"secondaryThumbprint": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cloudToDeviceMessageCount": 0,
|
||||||
|
"connectionState": "Disconnected",
|
||||||
|
"connectionStateUpdatedTime": "0001-01-01T00:00:00",
|
||||||
|
"deviceId": "mydevice",
|
||||||
|
"etag": "ODM2NjI3ODg=",
|
||||||
|
"generationId": "636904759703045768",
|
||||||
|
"lastActivityTime": "0001-01-01T00:00:00",
|
||||||
|
"managedBy": null,
|
||||||
|
"moduleId": "Testing"
|
||||||
|
}
|
||||||
|
''' # NOQA
|
||||||
|
|
||||||
|
import json
|
||||||
|
import copy
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ansible.module_utils.azure_rm_common import AzureRMModuleBase, format_resource_id
|
||||||
|
from ansible.module_utils.common.dict_transformations import _snake_to_camel
|
||||||
|
|
||||||
|
try:
|
||||||
|
from msrestazure.tools import parse_resource_id
|
||||||
|
from msrestazure.azure_exceptions import CloudError
|
||||||
|
except ImportError:
|
||||||
|
# This is handled in azure_rm_common
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AzureRMIoTDeviceModule(AzureRMModuleBase):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.module_arg_spec = dict(
|
||||||
|
name=dict(type='str', required=True),
|
||||||
|
hub_policy_name=dict(type='str', required=True),
|
||||||
|
hub_policy_key=dict(type='str', required=True),
|
||||||
|
hub=dict(type='str', required=True),
|
||||||
|
device=dict(type='str', required=True),
|
||||||
|
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||||
|
twin_tags=dict(type='dict'),
|
||||||
|
desired=dict(type='dict'),
|
||||||
|
auth_method=dict(type='str', choices=['self_signed', 'sas', 'certificate_authority'], default='sas'),
|
||||||
|
primary_key=dict(type='str', no_log=True, aliases=['primary_thumbprint']),
|
||||||
|
secondary_key=dict(type='str', no_log=True, aliases=['secondary_thumbprint'])
|
||||||
|
)
|
||||||
|
|
||||||
|
self.results = dict(
|
||||||
|
changed=False,
|
||||||
|
id=None
|
||||||
|
)
|
||||||
|
|
||||||
|
self.name = None
|
||||||
|
self.hub = None
|
||||||
|
self.device = None
|
||||||
|
self.hub_policy_key = None
|
||||||
|
self.hub_policy_name = None
|
||||||
|
self.state = None
|
||||||
|
self.twin_tags = None
|
||||||
|
self.desired = None
|
||||||
|
self.auth_method = None
|
||||||
|
self.primary_key = None
|
||||||
|
self.secondary_key = None
|
||||||
|
|
||||||
|
required_if = [
|
||||||
|
['auth_method', 'self_signed', ['certificate_authority']]
|
||||||
|
]
|
||||||
|
|
||||||
|
self._base_url = None
|
||||||
|
self._mgmt_client = None
|
||||||
|
self.query_parameters = {
|
||||||
|
'api-version': '2018-06-30'
|
||||||
|
}
|
||||||
|
self.header_parameters = {
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
'accept-language': 'en-US'
|
||||||
|
}
|
||||||
|
super(AzureRMIoTDeviceModule, self).__init__(self.module_arg_spec, supports_check_mode=True, required_if=required_if)
|
||||||
|
|
||||||
|
def exec_module(self, **kwargs):
|
||||||
|
|
||||||
|
for key in self.module_arg_spec.keys():
|
||||||
|
setattr(self, key, kwargs[key])
|
||||||
|
|
||||||
|
self._base_url = '{0}.azure-devices.net'.format(self.hub)
|
||||||
|
config = {
|
||||||
|
'base_url': self._base_url,
|
||||||
|
'key': self.hub_policy_key,
|
||||||
|
'policy': self.hub_policy_name
|
||||||
|
}
|
||||||
|
self._mgmt_client = self.get_data_svc_client(**config)
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
module = self.get_module()
|
||||||
|
if self.state == 'present':
|
||||||
|
if not module:
|
||||||
|
changed = True
|
||||||
|
auth = {'type': _snake_to_camel(self.auth_method)}
|
||||||
|
if self.auth_method == 'self_signed':
|
||||||
|
auth['x509Thumbprint'] = {
|
||||||
|
'primaryThumbprint': self.primary_key,
|
||||||
|
'secondaryThumbprint': self.secondary_key
|
||||||
|
}
|
||||||
|
elif self.auth_method == 'sas':
|
||||||
|
auth['symmetricKey'] = {
|
||||||
|
'primaryKey': self.primary_key,
|
||||||
|
'secondaryKey': self.secondary_key
|
||||||
|
}
|
||||||
|
module = {
|
||||||
|
'deviceId': self.device,
|
||||||
|
'moduleId': self.name,
|
||||||
|
'authentication': auth
|
||||||
|
}
|
||||||
|
if changed and not self.check_mode:
|
||||||
|
module = self.create_or_update_module(module)
|
||||||
|
twin = self.get_twin()
|
||||||
|
if not twin.get('tags'):
|
||||||
|
twin['tags'] = dict()
|
||||||
|
twin_change = False
|
||||||
|
if self.twin_tags and not self.is_equal(self.twin_tags, twin['tags']):
|
||||||
|
twin_change = True
|
||||||
|
if self.desired and not self.is_equal(self.desired, twin['properties']['desired']):
|
||||||
|
self.module.warn('desired')
|
||||||
|
twin_change = True
|
||||||
|
if twin_change and not self.check_mode:
|
||||||
|
twin = self.update_twin(twin)
|
||||||
|
changed = changed or twin_change
|
||||||
|
module['tags'] = twin.get('tags') or dict()
|
||||||
|
module['properties'] = twin['properties']
|
||||||
|
elif module:
|
||||||
|
if not self.check_mode:
|
||||||
|
self.delete_module(module['etag'])
|
||||||
|
changed = True
|
||||||
|
module = None
|
||||||
|
self.results = module or dict()
|
||||||
|
self.results['changed'] = changed
|
||||||
|
return self.results
|
||||||
|
|
||||||
|
def is_equal(self, updated, original):
|
||||||
|
changed = False
|
||||||
|
if not isinstance(updated, dict):
|
||||||
|
self.fail('The Property or Tag should be a dict')
|
||||||
|
for key in updated.keys():
|
||||||
|
if re.search(r'[.|$|#|\s]', key):
|
||||||
|
self.fail("Property or Tag name has invalid characters: '.', '$', '#' or ' '. Got '{0}'".format(key))
|
||||||
|
original_value = original.get(key)
|
||||||
|
updated_value = updated[key]
|
||||||
|
if isinstance(updated_value, dict):
|
||||||
|
if not isinstance(original_value, dict):
|
||||||
|
changed = True
|
||||||
|
original[key] = updated_value
|
||||||
|
elif not self.is_equal(updated_value, original_value):
|
||||||
|
changed = True
|
||||||
|
elif original_value != updated_value:
|
||||||
|
changed = True
|
||||||
|
original[key] = updated_value
|
||||||
|
return not changed
|
||||||
|
|
||||||
|
def create_or_update_module(self, module):
|
||||||
|
try:
|
||||||
|
url = '/devices/{0}/modules/{1}'.format(self.device, self.name)
|
||||||
|
headers = copy.copy(self.header_parameters)
|
||||||
|
if module.get('etag'):
|
||||||
|
headers['If-Match'] = '"{0}"'.format(module['etag'])
|
||||||
|
request = self._mgmt_client.put(url, self.query_parameters)
|
||||||
|
response = self._mgmt_client.send(request=request, headers=headers, content=module)
|
||||||
|
if response.status_code not in [200, 201]:
|
||||||
|
raise CloudError(response)
|
||||||
|
return json.loads(response.text)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when creating or updating IoT Hub device {0}: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def delete_module(self, etag):
|
||||||
|
try:
|
||||||
|
url = '/devices/{0}/modules/{1}'.format(self.device, self.name)
|
||||||
|
headers = copy.copy(self.header_parameters)
|
||||||
|
headers['If-Match'] = '"{0}"'.format(etag)
|
||||||
|
request = self._mgmt_client.delete(url, self.query_parameters)
|
||||||
|
response = self._mgmt_client.send(request=request, headers=headers)
|
||||||
|
if response.status_code not in [204]:
|
||||||
|
raise CloudError(response)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when deleting IoT Hub device {0}: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def get_module(self):
|
||||||
|
try:
|
||||||
|
url = '/devices/{0}/modules/{1}'.format(self.device, self.name)
|
||||||
|
return self._https_get(url, self.query_parameters, self.header_parameters)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_twin(self):
|
||||||
|
try:
|
||||||
|
url = '/twins/{0}/modules/{1}'.format(self.device, self.name)
|
||||||
|
return self._https_get(url, self.query_parameters, self.header_parameters)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when getting IoT Hub device {0} module twin {1}: {2}'.format(self.device, self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def update_twin(self, twin):
|
||||||
|
try:
|
||||||
|
url = '/twins/{0}/modules/{1}'.format(self.device, self.name)
|
||||||
|
headers = copy.copy(self.header_parameters)
|
||||||
|
headers['If-Match'] = twin['etag']
|
||||||
|
request = self._mgmt_client.patch(url, self.query_parameters)
|
||||||
|
response = self._mgmt_client.send(request=request, headers=headers, content=twin)
|
||||||
|
if response.status_code not in [200]:
|
||||||
|
raise CloudError(response)
|
||||||
|
return json.loads(response.text)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when creating or updating IoT Hub device {0} module twin {1}: {2}'.format(self.device, self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def _https_get(self, url, query_parameters, header_parameters):
|
||||||
|
request = self._mgmt_client.get(url, query_parameters)
|
||||||
|
response = self._mgmt_client.send(request=request, headers=header_parameters, content=None)
|
||||||
|
if response.status_code not in [200]:
|
||||||
|
raise CloudError(response)
|
||||||
|
return json.loads(response.text)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
AzureRMIoTDeviceModule()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
896
lib/ansible/modules/cloud/azure/azure_rm_iothub.py
Normal file
896
lib/ansible/modules/cloud/azure/azure_rm_iothub.py
Normal file
|
@ -0,0 +1,896 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2019 Yuwei Zhou, <yuwzho@microsoft.com>
|
||||||
|
#
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'}
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: azure_rm_iothub
|
||||||
|
version_added: "2.9"
|
||||||
|
short_description: Manage Azure IoT hub
|
||||||
|
description:
|
||||||
|
- Create, delete an Azure IoT hub.
|
||||||
|
options:
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Name of resource group.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the IoT hub.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- State of the IoT hub. Use C(present) to create or update an IoT hub and C(absent) to delete an IoT hub.
|
||||||
|
type: str
|
||||||
|
default: present
|
||||||
|
choices:
|
||||||
|
- absent
|
||||||
|
- present
|
||||||
|
location:
|
||||||
|
description:
|
||||||
|
- Location of the IoT hub.
|
||||||
|
type: str
|
||||||
|
sku:
|
||||||
|
description:
|
||||||
|
- Pricing tier for Azure IoT Hub.
|
||||||
|
- Note that only one free IoT hub instance is allowed in each subscription. Exception will be thrown if free instances exceed one.
|
||||||
|
- Default is C(s1) when creation.
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- b1
|
||||||
|
- b2
|
||||||
|
- b3
|
||||||
|
- f1
|
||||||
|
- s1
|
||||||
|
- s2
|
||||||
|
- s3
|
||||||
|
unit:
|
||||||
|
description:
|
||||||
|
- Units in your IoT Hub.
|
||||||
|
- Default is C(1).
|
||||||
|
type: int
|
||||||
|
event_endpoint:
|
||||||
|
description:
|
||||||
|
- The Event Hub-compatible endpoint property.
|
||||||
|
type: dict
|
||||||
|
suboptions:
|
||||||
|
partition_count:
|
||||||
|
description:
|
||||||
|
- The number of partitions for receiving device-to-cloud messages in the Event Hub-compatible endpoint.
|
||||||
|
- "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#device-to-cloud-messages)."
|
||||||
|
- Default is C(2).
|
||||||
|
type: int
|
||||||
|
retention_time_in_days:
|
||||||
|
description:
|
||||||
|
- The retention time for device-to-cloud messages in days.
|
||||||
|
- "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#device-to-cloud-messages)."
|
||||||
|
- Default is C(1).
|
||||||
|
type: int
|
||||||
|
enable_file_upload_notifications:
|
||||||
|
description:
|
||||||
|
- File upload notifications are enabled if set to C(True).
|
||||||
|
type: bool
|
||||||
|
ip_filters:
|
||||||
|
description:
|
||||||
|
- Configure rules for rejecting or accepting traffic from specific IPv4 addresses.
|
||||||
|
type: list
|
||||||
|
suboptions:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the filter.
|
||||||
|
type: str
|
||||||
|
required: yes
|
||||||
|
ip_mask:
|
||||||
|
description:
|
||||||
|
- A string that contains the IP address range in CIDR notation for the rule.
|
||||||
|
type: str
|
||||||
|
required: yes
|
||||||
|
action:
|
||||||
|
description:
|
||||||
|
- The desired action for requests captured by this rule.
|
||||||
|
type: str
|
||||||
|
required: yes
|
||||||
|
choices:
|
||||||
|
- accept
|
||||||
|
- reject
|
||||||
|
routing_endpoints:
|
||||||
|
description:
|
||||||
|
- Custom endpoints.
|
||||||
|
type: list
|
||||||
|
suboptions:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
required: yes
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Resource group of the endpoint.
|
||||||
|
- Default is the same as I(resource_group).
|
||||||
|
type: str
|
||||||
|
subscription:
|
||||||
|
description:
|
||||||
|
- Subscription id of the endpoint.
|
||||||
|
- Default is the same as I(subscription).
|
||||||
|
type: str
|
||||||
|
resource_type:
|
||||||
|
description:
|
||||||
|
- Resource type of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- eventhub
|
||||||
|
- queue
|
||||||
|
- storage
|
||||||
|
- topic
|
||||||
|
required: yes
|
||||||
|
connection_string:
|
||||||
|
description:
|
||||||
|
- Connection string of the custom endpoint.
|
||||||
|
- The connection string should have send priviledge.
|
||||||
|
type: str
|
||||||
|
required: yes
|
||||||
|
container:
|
||||||
|
description:
|
||||||
|
- Container name of the custom endpoint when I(resource_type=storage).
|
||||||
|
type: str
|
||||||
|
encoding:
|
||||||
|
description:
|
||||||
|
- Encoding of the message when I(resource_type=storage).
|
||||||
|
type: str
|
||||||
|
routes:
|
||||||
|
description:
|
||||||
|
- Route device-to-cloud messages to service-facing endpoints.
|
||||||
|
type: list
|
||||||
|
suboptions:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the route.
|
||||||
|
type: str
|
||||||
|
required: yes
|
||||||
|
source:
|
||||||
|
description:
|
||||||
|
- The origin of the data stream to be acted upon.
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- device_messages
|
||||||
|
- twin_change_events
|
||||||
|
- device_lifecycle_events
|
||||||
|
- device_job_lifecycle_events
|
||||||
|
required: yes
|
||||||
|
enabled:
|
||||||
|
description:
|
||||||
|
- Whether to enable the route.
|
||||||
|
type: bool
|
||||||
|
required: yes
|
||||||
|
endpoint_name:
|
||||||
|
description:
|
||||||
|
- The name of the endpoint in I(routing_endpoints) where IoT Hub sends messages that match the query.
|
||||||
|
type: str
|
||||||
|
required: yes
|
||||||
|
condition:
|
||||||
|
description:
|
||||||
|
- "The query expression for the routing query that is run against the message application properties,
|
||||||
|
system properties, message body, device twin tags, and device twin properties to determine if it is a match for the endpoint."
|
||||||
|
- "For more information about constructing a query,
|
||||||
|
see U(https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-routing-query-syntax)"
|
||||||
|
type: str
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- azure
|
||||||
|
- azure_tags
|
||||||
|
|
||||||
|
author:
|
||||||
|
- Yuwei Zhou (@yuwzho)
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Create a simplest IoT hub
|
||||||
|
azure_rm_iothub:
|
||||||
|
name: Testing
|
||||||
|
resource_group: myResourceGroup
|
||||||
|
- name: Create an IoT hub with route
|
||||||
|
azure_rm_iothub:
|
||||||
|
resource_group: myResourceGroup
|
||||||
|
name: Testing
|
||||||
|
routing_endpoints:
|
||||||
|
- connection_string: "Endpoint=sb://qux.servicebus.windows.net/;SharedAccessKeyName=quux;SharedAccessKey=****;EntityPath=myQueue"
|
||||||
|
name: foo
|
||||||
|
resource_type: queue
|
||||||
|
resource_group: myResourceGroup1
|
||||||
|
routes:
|
||||||
|
- name: bar
|
||||||
|
source: device_messages
|
||||||
|
endpoint_name: foo
|
||||||
|
enabled: yes
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
id:
|
||||||
|
description:
|
||||||
|
- Resource ID of the IoT hub.
|
||||||
|
sample: "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/myResourceGroup/providers/Microsoft.Devices/IotHubs/Testing"
|
||||||
|
returned: success
|
||||||
|
type: str
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the IoT hub.
|
||||||
|
sample: Testing
|
||||||
|
returned: success
|
||||||
|
type: str
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Resource group of the IoT hub.
|
||||||
|
sample: myResourceGroup.
|
||||||
|
returned: success
|
||||||
|
type: str
|
||||||
|
location:
|
||||||
|
description:
|
||||||
|
- Location of the IoT hub.
|
||||||
|
sample: eastus
|
||||||
|
returned: success
|
||||||
|
type: str
|
||||||
|
unit:
|
||||||
|
description:
|
||||||
|
- Units in the IoT Hub.
|
||||||
|
sample: 1
|
||||||
|
returned: success
|
||||||
|
type: int
|
||||||
|
sku:
|
||||||
|
description:
|
||||||
|
- Pricing tier for Azure IoT Hub.
|
||||||
|
sample: f1
|
||||||
|
returned: success
|
||||||
|
type: str
|
||||||
|
cloud_to_device:
|
||||||
|
description:
|
||||||
|
- Cloud to device message properties.
|
||||||
|
contains:
|
||||||
|
max_delivery_count:
|
||||||
|
description:
|
||||||
|
- The number of times the IoT hub attempts to deliver a message on the feedback queue.
|
||||||
|
- "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#cloud-to-device-messages)."
|
||||||
|
type: int
|
||||||
|
returned: success
|
||||||
|
sample: 10
|
||||||
|
ttl_as_iso8601:
|
||||||
|
description:
|
||||||
|
- The period of time for which a message is available to consume before it is expired by the IoT hub.
|
||||||
|
- "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#cloud-to-device-messages)."
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: "1:00:00"
|
||||||
|
returned: success
|
||||||
|
type: complex
|
||||||
|
enable_file_upload_notifications:
|
||||||
|
description:
|
||||||
|
- Whether file upload notifications are enabled.
|
||||||
|
sample: True
|
||||||
|
returned: success
|
||||||
|
type: bool
|
||||||
|
event_endpoints:
|
||||||
|
description:
|
||||||
|
- Built-in endpoint where to deliver device message.
|
||||||
|
contains:
|
||||||
|
endpoint:
|
||||||
|
description:
|
||||||
|
- The Event Hub-compatible endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: "sb://iothub-ns-testing-1478811-9bbc4a15f0.servicebus.windows.net/"
|
||||||
|
partition_count:
|
||||||
|
description:
|
||||||
|
- The number of partitions for receiving device-to-cloud messages in the Event Hub-compatible endpoint.
|
||||||
|
- "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#device-to-cloud-messages)."
|
||||||
|
type: int
|
||||||
|
returned: success
|
||||||
|
sample: 2
|
||||||
|
retention_time_in_days:
|
||||||
|
description:
|
||||||
|
- The retention time for device-to-cloud messages in days.
|
||||||
|
- "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#device-to-cloud-messages)."
|
||||||
|
type: int
|
||||||
|
returned: success
|
||||||
|
sample: 1
|
||||||
|
partition_ids:
|
||||||
|
description:
|
||||||
|
- List of the partition id for the event endpoint.
|
||||||
|
type: list
|
||||||
|
returned: success
|
||||||
|
sample: ["0", "1"]
|
||||||
|
returned: success
|
||||||
|
type: complex
|
||||||
|
host_name:
|
||||||
|
description:
|
||||||
|
- Host of the IoT hub.
|
||||||
|
sample: "testing.azure-devices.net"
|
||||||
|
returned: success
|
||||||
|
type: str
|
||||||
|
ip_filters:
|
||||||
|
description:
|
||||||
|
- Configure rules for rejecting or accepting traffic from specific IPv4 addresses.
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the filter.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: filter
|
||||||
|
ip_mask:
|
||||||
|
description:
|
||||||
|
- A string that contains the IP address range in CIDR notation for the rule.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: 40.54.7.3
|
||||||
|
action:
|
||||||
|
description:
|
||||||
|
- The desired action for requests captured by this rule.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: Reject
|
||||||
|
returned: success
|
||||||
|
type: complex
|
||||||
|
routing_endpoints:
|
||||||
|
description:
|
||||||
|
- Custom endpoints.
|
||||||
|
contains:
|
||||||
|
event_hubs:
|
||||||
|
description:
|
||||||
|
- List of custom endpoints of event hubs.
|
||||||
|
type: complex
|
||||||
|
returned: success
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: foo
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Resource group of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: bar
|
||||||
|
subscription:
|
||||||
|
description:
|
||||||
|
- Subscription id of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||||
|
connection_string:
|
||||||
|
description:
|
||||||
|
- Connection string of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
|
||||||
|
service_bus_queues:
|
||||||
|
description:
|
||||||
|
- List of custom endpoints of service bus queue.
|
||||||
|
type: complex
|
||||||
|
returned: always
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: foo
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Resource group of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: bar
|
||||||
|
subscription:
|
||||||
|
description:
|
||||||
|
- Subscription ID of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||||
|
connection_string:
|
||||||
|
description:
|
||||||
|
- Connection string of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
|
||||||
|
service_bus_topics:
|
||||||
|
description:
|
||||||
|
- List of custom endpoints of service bus topic.
|
||||||
|
type: complex
|
||||||
|
returned: success
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: foo
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Resource group of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: bar
|
||||||
|
subscription:
|
||||||
|
description:
|
||||||
|
- Subscription ID of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||||
|
connection_string:
|
||||||
|
description:
|
||||||
|
- Connection string of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
|
||||||
|
storage_containers:
|
||||||
|
description:
|
||||||
|
- List of custom endpoints of storage
|
||||||
|
type: complex
|
||||||
|
returned: success
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: foo
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Resource group of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: bar
|
||||||
|
subscription:
|
||||||
|
description:
|
||||||
|
- Subscription ID of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||||
|
connection_string:
|
||||||
|
description:
|
||||||
|
- Connection string of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
|
||||||
|
returned: success
|
||||||
|
type: complex
|
||||||
|
routes:
|
||||||
|
description:
|
||||||
|
- Route device-to-cloud messages to service-facing endpoints.
|
||||||
|
type: complex
|
||||||
|
returned: success
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the route.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: route1
|
||||||
|
source:
|
||||||
|
description:
|
||||||
|
- The origin of the data stream to be acted upon.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: device_messages
|
||||||
|
enabled:
|
||||||
|
description:
|
||||||
|
- Whether to enable the route.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: true
|
||||||
|
endpoint_name:
|
||||||
|
description:
|
||||||
|
- The name of the endpoint in C(routing_endpoints) where IoT Hub sends messages that match the query.
|
||||||
|
type: str
|
||||||
|
returned: success
|
||||||
|
sample: foo
|
||||||
|
condition:
|
||||||
|
description:
|
||||||
|
- "The query expression for the routing query that is run against the message application properties,
|
||||||
|
system properties, message body, device twin tags, and device twin properties to determine if it is a match for the endpoint."
|
||||||
|
- "For more information about constructing a query,
|
||||||
|
see I(https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-routing-query-syntax)"
|
||||||
|
type: bool
|
||||||
|
returned: success
|
||||||
|
sample: "true"
|
||||||
|
''' # NOQA
|
||||||
|
|
||||||
|
from ansible.module_utils.azure_rm_common import AzureRMModuleBase, format_resource_id
|
||||||
|
from ansible.module_utils.common.dict_transformations import _snake_to_camel, _camel_to_snake
|
||||||
|
import re
|
||||||
|
|
||||||
|
try:
|
||||||
|
from msrestazure.tools import parse_resource_id
|
||||||
|
from msrestazure.azure_exceptions import CloudError
|
||||||
|
except ImportError:
|
||||||
|
# This is handled in azure_rm_common
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
ip_filter_spec = dict(
|
||||||
|
name=dict(type='str', required=True),
|
||||||
|
ip_mask=dict(type='str', required=True),
|
||||||
|
action=dict(type='str', required=True, choices=['accept', 'reject'])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
routing_endpoints_spec = dict(
|
||||||
|
connection_string=dict(type='str', required=True),
|
||||||
|
name=dict(type='str', required=True),
|
||||||
|
resource_group=dict(type='str'),
|
||||||
|
subscription=dict(type='str'),
|
||||||
|
resource_type=dict(type='str', required=True, choices=['eventhub', 'queue', 'storage', 'topic']),
|
||||||
|
container=dict(type='str'),
|
||||||
|
encoding=dict(type='str')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
routing_endpoints_resource_type_mapping = {
|
||||||
|
'eventhub': {'model': 'RoutingEventHubProperties', 'attribute': 'event_hubs'},
|
||||||
|
'queue': {'model': 'RoutingServiceBusQueueEndpointProperties', 'attribute': 'service_bus_queues'},
|
||||||
|
'topic': {'model': 'RoutingServiceBusTopicEndpointProperties', 'attribute': 'service_bus_topics'},
|
||||||
|
'storage': {'model': 'RoutingStorageContainerProperties', 'attribute': 'storage_containers'}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
routes_spec = dict(
|
||||||
|
name=dict(type='str', required=True),
|
||||||
|
source=dict(type='str', required=True, choices=['device_messages', 'twin_change_events', 'device_lifecycle_events', 'device_job_lifecycle_events']),
|
||||||
|
enabled=dict(type='bool', required=True),
|
||||||
|
endpoint_name=dict(type='str', required=True),
|
||||||
|
condition=dict(type='str')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
event_endpoint_spec = dict(
|
||||||
|
partition_count=dict(type='int'),
|
||||||
|
retention_time_in_days=dict(type='int')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AzureRMIoTHub(AzureRMModuleBase):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.module_arg_spec = dict(
|
||||||
|
resource_group=dict(type='str', required=True),
|
||||||
|
name=dict(type='str', required=True),
|
||||||
|
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||||
|
location=dict(type='str'),
|
||||||
|
sku=dict(type='str', choices=['b1', 'b2', 'b3', 'f1', 's1', 's2', 's3']),
|
||||||
|
unit=dict(type='int'),
|
||||||
|
event_endpoint=dict(type='dict', options=event_endpoint_spec),
|
||||||
|
enable_file_upload_notifications=dict(type='bool'),
|
||||||
|
ip_filters=dict(type='list', elements='dict', options=ip_filter_spec),
|
||||||
|
routing_endpoints=dict(type='list', elements='dict', options=routing_endpoints_spec),
|
||||||
|
routes=dict(type='list', elements='dict', options=routes_spec)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.results = dict(
|
||||||
|
changed=False,
|
||||||
|
id=None
|
||||||
|
)
|
||||||
|
|
||||||
|
self.resource_group = None
|
||||||
|
self.name = None
|
||||||
|
self.state = None
|
||||||
|
self.location = None
|
||||||
|
self.sku = None
|
||||||
|
self.unit = None
|
||||||
|
self.event_endpoint = None
|
||||||
|
self.tags = None
|
||||||
|
self.enable_file_upload_notifications = None
|
||||||
|
self.ip_filters = None
|
||||||
|
self.routing_endpoints = None
|
||||||
|
self.routes = None
|
||||||
|
|
||||||
|
super(AzureRMIoTHub, self).__init__(self.module_arg_spec, supports_check_mode=True)
|
||||||
|
|
||||||
|
def exec_module(self, **kwargs):
|
||||||
|
|
||||||
|
for key in list(self.module_arg_spec.keys()) + ['tags']:
|
||||||
|
setattr(self, key, kwargs[key])
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
if not self.location:
|
||||||
|
# Set default location
|
||||||
|
resource_group = self.get_resource_group(self.resource_group)
|
||||||
|
self.location = resource_group.location
|
||||||
|
self.sku = str.capitalize(self.sku) if self.sku else None
|
||||||
|
iothub = self.get_hub()
|
||||||
|
if self.state == 'present':
|
||||||
|
if not iothub:
|
||||||
|
changed = True
|
||||||
|
self.sku = self.sku or 'S1'
|
||||||
|
self.unit = self.unit or 1
|
||||||
|
self.event_endpoint = self.event_endpoint or {}
|
||||||
|
self.event_endpoint['partition_count'] = self.event_endpoint.get('partition_count') or 2
|
||||||
|
self.event_endpoint['retention_time_in_days'] = self.event_endpoint.get('retention_time_in_days') or 1
|
||||||
|
event_hub_properties = dict()
|
||||||
|
event_hub_properties['events'] = self.IoThub_models.EventHubProperties(**self.event_endpoint)
|
||||||
|
iothub_property = self.IoThub_models.IotHubProperties(event_hub_endpoints=event_hub_properties)
|
||||||
|
if self.enable_file_upload_notifications:
|
||||||
|
iothub_property.enable_file_upload_notifications = self.enable_file_upload_notifications
|
||||||
|
if self.ip_filters:
|
||||||
|
iothub_property.ip_filter_rules = self.construct_ip_filters()
|
||||||
|
routing_endpoints = None
|
||||||
|
routes = None
|
||||||
|
if self.routing_endpoints:
|
||||||
|
routing_endpoints = self.construct_routing_endpoint(self.routing_endpoints)
|
||||||
|
if self.routes:
|
||||||
|
routes = [self.construct_route(x) for x in self.routes]
|
||||||
|
if routes or routing_endpoints:
|
||||||
|
routing_property = self.IoThub_models.RoutingProperties(endpoints=routing_endpoints,
|
||||||
|
routes=routes)
|
||||||
|
iothub_property.routing = routing_property
|
||||||
|
iothub = self.IoThub_models.IotHubDescription(location=self.location,
|
||||||
|
sku=self.IoThub_models.IotHubSkuInfo(name=self.sku, capacity=self.unit),
|
||||||
|
properties=iothub_property,
|
||||||
|
tags=self.tags)
|
||||||
|
if not self.check_mode:
|
||||||
|
iothub = self.create_or_update_hub(iothub)
|
||||||
|
else:
|
||||||
|
# compare sku
|
||||||
|
original_sku = iothub.sku
|
||||||
|
if self.sku and self.sku != original_sku.name:
|
||||||
|
self.log('SKU changed')
|
||||||
|
iothub.sku.name = self.sku
|
||||||
|
changed = True
|
||||||
|
if self.unit and self.unit != original_sku.capacity:
|
||||||
|
self.log('Unit count changed')
|
||||||
|
iothub.sku.capacity = self.unit
|
||||||
|
changed = True
|
||||||
|
# compare event hub property
|
||||||
|
event_hub = iothub.properties.event_hub_endpoints or dict()
|
||||||
|
if self.event_endpoint:
|
||||||
|
item = self.event_endpoint
|
||||||
|
original_item = event_hub.get('events')
|
||||||
|
if not original_item:
|
||||||
|
changed = True
|
||||||
|
event_hub['events'] = self.IoThub_models.EventHubProperties(partition_count=item.get('partition_count') or 2,
|
||||||
|
retention_time_in_days=item.get('retention_time_in_days') or 1)
|
||||||
|
elif item.get('partition_count') and original_item.partition_count != item['partition_count']:
|
||||||
|
changed = True
|
||||||
|
original_item.partition_count = item['partition_count']
|
||||||
|
elif item.get('retention_time_in_days') and original_item.retention_time_in_days != item['retention_time_in_days']:
|
||||||
|
changed = True
|
||||||
|
original_item.retention_time_in_days = item['retention_time_in_days']
|
||||||
|
# compare endpoint
|
||||||
|
original_endpoints = iothub.properties.routing.endpoints
|
||||||
|
endpoint_changed = False
|
||||||
|
if self.routing_endpoints:
|
||||||
|
# find the total length
|
||||||
|
total_length = 0
|
||||||
|
for item in routing_endpoints_resource_type_mapping.values():
|
||||||
|
attribute = item['attribute']
|
||||||
|
array = getattr(original_endpoints, attribute)
|
||||||
|
total_length += len(array or [])
|
||||||
|
if total_length != len(self.routing_endpoints):
|
||||||
|
endpoint_changed = True
|
||||||
|
else: # If already changed, no need to compare any more
|
||||||
|
for item in self.routing_endpoints:
|
||||||
|
if not self.lookup_endpoint(item, original_endpoints):
|
||||||
|
endpoint_changed = True
|
||||||
|
break
|
||||||
|
if endpoint_changed:
|
||||||
|
iothub.properties.routing.endpoints = self.construct_routing_endpoint(self.routing_endpoints)
|
||||||
|
changed = True
|
||||||
|
# compare routes
|
||||||
|
original_routes = iothub.properties.routing.routes
|
||||||
|
routes_changed = False
|
||||||
|
if self.routes:
|
||||||
|
if len(self.routes) != len(original_routes or []):
|
||||||
|
routes_changed = True
|
||||||
|
else:
|
||||||
|
for item in self.routes:
|
||||||
|
if not self.lookup_route(item, original_routes):
|
||||||
|
routes_changed = True
|
||||||
|
break
|
||||||
|
if routes_changed:
|
||||||
|
changed = True
|
||||||
|
iothub.properties.routing.routes = [self.construct_route(x) for x in self.routes]
|
||||||
|
# compare IP filter
|
||||||
|
ip_filter_changed = False
|
||||||
|
original_ip_filter = iothub.properties.ip_filter_rules
|
||||||
|
if self.ip_filters:
|
||||||
|
if len(self.ip_filters) != len(original_ip_filter or []):
|
||||||
|
ip_filter_changed = True
|
||||||
|
else:
|
||||||
|
for item in self.ip_filters:
|
||||||
|
if not self.lookup_ip_filter(item, original_ip_filter):
|
||||||
|
ip_filter_changed = True
|
||||||
|
break
|
||||||
|
if ip_filter_changed:
|
||||||
|
changed = True
|
||||||
|
iothub.properties.ip_filter_rules = self.construct_ip_filters()
|
||||||
|
|
||||||
|
# compare tags
|
||||||
|
tag_changed, updated_tags = self.update_tags(iothub.tags)
|
||||||
|
iothub.tags = updated_tags
|
||||||
|
if changed and not self.check_mode:
|
||||||
|
iothub = self.create_or_update_hub(iothub)
|
||||||
|
# only tags changed
|
||||||
|
if not changed and tag_changed:
|
||||||
|
changed = True
|
||||||
|
if not self.check_mode:
|
||||||
|
iothub = self.update_instance_tags(updated_tags)
|
||||||
|
self.results = self.to_dict(iothub)
|
||||||
|
elif iothub:
|
||||||
|
changed = True
|
||||||
|
if not self.check_mode:
|
||||||
|
self.delete_hub()
|
||||||
|
self.results['changed'] = changed
|
||||||
|
return self.results
|
||||||
|
|
||||||
|
def lookup_ip_filter(self, target, ip_filters):
|
||||||
|
if not ip_filters or len(ip_filters) == 0:
|
||||||
|
return False
|
||||||
|
for item in ip_filters:
|
||||||
|
if item.filter_name == target['name']:
|
||||||
|
if item.ip_mask != target['ip_mask']:
|
||||||
|
return False
|
||||||
|
if item.action.lower() != target['action']:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def lookup_route(self, target, routes):
|
||||||
|
if not routes or len(routes) == 0:
|
||||||
|
return False
|
||||||
|
for item in routes:
|
||||||
|
if item.name == target['name']:
|
||||||
|
if target['source'] != _camel_to_snake(item.source):
|
||||||
|
return False
|
||||||
|
if target['enabled'] != item.is_enabled:
|
||||||
|
return False
|
||||||
|
if target['endpoint_name'] != item.endpoint_names[0]:
|
||||||
|
return False
|
||||||
|
if target.get('condition') and target['condition'] != item.condition:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def lookup_endpoint(self, target, routing_endpoints):
|
||||||
|
resource_type = target['resource_type']
|
||||||
|
attribute = routing_endpoints_resource_type_mapping[resource_type]['attribute']
|
||||||
|
endpoints = getattr(routing_endpoints, attribute)
|
||||||
|
if not endpoints or len(endpoints) == 0:
|
||||||
|
return False
|
||||||
|
for item in endpoints:
|
||||||
|
if item.name == target['name']:
|
||||||
|
if target.get('resource_group') and target['resource_group'] != (item.resource_group or self.resource_group):
|
||||||
|
return False
|
||||||
|
if target.get('subscription_id') and target['subscription_id'] != (item.subscription_id or self.subscription_id):
|
||||||
|
return False
|
||||||
|
connection_string_regex = item.connection_string.replace('****', '.*')
|
||||||
|
connection_string_regex = re.sub(r':\d+/;', '/;', connection_string_regex)
|
||||||
|
if not re.search(connection_string_regex, target['connection_string']):
|
||||||
|
return False
|
||||||
|
if resource_type == 'storage':
|
||||||
|
if target.get('container') and item.container_name != target['container']:
|
||||||
|
return False
|
||||||
|
if target.get('encoding') and item.encoding != target['encoding']:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def construct_ip_filters(self):
|
||||||
|
return [self.IoThub_models.IpFilterRule(filter_name=x['name'],
|
||||||
|
action=self.IoThub_models.IpFilterActionType[x['action']],
|
||||||
|
ip_mask=x['ip_mask']) for x in self.ip_filters]
|
||||||
|
|
||||||
|
def construct_routing_endpoint(self, routing_endpoints):
|
||||||
|
if not routing_endpoints or len(routing_endpoints) == 0:
|
||||||
|
return None
|
||||||
|
result = self.IoThub_models.RoutingEndpoints()
|
||||||
|
for endpoint in routing_endpoints:
|
||||||
|
resource_type_property = routing_endpoints_resource_type_mapping.get(endpoint['resource_type'])
|
||||||
|
resource_type = getattr(self.IoThub_models, resource_type_property['model'])
|
||||||
|
array = getattr(result, resource_type_property['attribute']) or []
|
||||||
|
array.append(resource_type(**endpoint))
|
||||||
|
setattr(result, resource_type_property['attribute'], array)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def construct_route(self, route):
|
||||||
|
if not route:
|
||||||
|
return None
|
||||||
|
return self.IoThub_models.RouteProperties(name=route['name'],
|
||||||
|
source=_snake_to_camel(snake=route['source'], capitalize_first=True),
|
||||||
|
is_enabled=route['enabled'],
|
||||||
|
endpoint_names=[route['endpoint_name']],
|
||||||
|
condition=route.get('condition'))
|
||||||
|
|
||||||
|
def get_hub(self):
|
||||||
|
try:
|
||||||
|
return self.IoThub_client.iot_hub_resource.get(self.resource_group, self.name)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create_or_update_hub(self, hub):
|
||||||
|
try:
|
||||||
|
poller = self.IoThub_client.iot_hub_resource.create_or_update(self.resource_group, self.name, hub, if_match=hub.etag)
|
||||||
|
return self.get_poller_result(poller)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error creating or updating IoT Hub {0}: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def update_instance_tags(self, tags):
|
||||||
|
try:
|
||||||
|
poller = self.IoThub_client.iot_hub_resource.update(self.resource_group, self.name, tags=tags)
|
||||||
|
return self.get_poller_result(poller)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error updating IoT Hub {0}\'s tag: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def delete_hub(self):
|
||||||
|
try:
|
||||||
|
self.IoThub_client.iot_hub_resource.delete(self.resource_group, self.name)
|
||||||
|
return True
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error deleting IoT Hub {0}: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def route_to_dict(self, route):
|
||||||
|
return dict(
|
||||||
|
name=route.name,
|
||||||
|
source=_camel_to_snake(route.source),
|
||||||
|
endpoint_name=route.endpoint_names[0],
|
||||||
|
enabled=route.is_enabled,
|
||||||
|
condition=route.condition
|
||||||
|
)
|
||||||
|
|
||||||
|
def instance_dict_to_dict(self, instance_dict):
|
||||||
|
result = dict()
|
||||||
|
if not instance_dict:
|
||||||
|
return result
|
||||||
|
for key in instance_dict.keys():
|
||||||
|
result[key] = instance_dict[key].as_dict()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def to_dict(self, hub):
|
||||||
|
result = dict()
|
||||||
|
properties = hub.properties
|
||||||
|
result['id'] = hub.id
|
||||||
|
result['name'] = hub.name
|
||||||
|
result['resource_group'] = self.resource_group
|
||||||
|
result['location'] = hub.location
|
||||||
|
result['tags'] = hub.tags
|
||||||
|
result['unit'] = hub.sku.capacity
|
||||||
|
result['sku'] = hub.sku.name.lower()
|
||||||
|
result['cloud_to_device'] = dict(
|
||||||
|
max_delivery_count=properties.cloud_to_device.feedback.max_delivery_count,
|
||||||
|
ttl_as_iso8601=str(properties.cloud_to_device.feedback.ttl_as_iso8601)
|
||||||
|
) if properties.cloud_to_device else dict()
|
||||||
|
result['enable_file_upload_notifications'] = properties.enable_file_upload_notifications
|
||||||
|
result['event_endpoint'] = properties.event_hub_endpoints.get('events').as_dict() if properties.event_hub_endpoints.get('events') else None
|
||||||
|
result['host_name'] = properties.host_name
|
||||||
|
result['ip_filters'] = [x.as_dict() for x in properties.ip_filter_rules]
|
||||||
|
if properties.routing:
|
||||||
|
result['routing_endpoints'] = properties.routing.endpoints.as_dict()
|
||||||
|
result['routes'] = [self.route_to_dict(x) for x in properties.routing.routes]
|
||||||
|
result['fallback_route'] = self.route_to_dict(properties.routing.fallback_route)
|
||||||
|
result['status'] = properties.state
|
||||||
|
result['storage_endpoints'] = self.instance_dict_to_dict(properties.storage_endpoints)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
AzureRMIoTHub()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
618
lib/ansible/modules/cloud/azure/azure_rm_iothub_info.py
Normal file
618
lib/ansible/modules/cloud/azure/azure_rm_iothub_info.py
Normal file
|
@ -0,0 +1,618 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2019 Yuwei Zhou, <yuwzho@microsoft.com>
|
||||||
|
#
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'}
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: azure_rm_iothub_info
|
||||||
|
|
||||||
|
version_added: "2.9"
|
||||||
|
|
||||||
|
short_description: Get IoT Hub facts
|
||||||
|
|
||||||
|
description:
|
||||||
|
- Get facts for a specific IoT Hub or all IoT Hubs.
|
||||||
|
|
||||||
|
options:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Limit results to a specific resource group.
|
||||||
|
type: str
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- The resource group to search for the desired IoT Hub.
|
||||||
|
type: str
|
||||||
|
tags:
|
||||||
|
description:
|
||||||
|
- Limit results by providing a list of tags. Format tags as 'key' or 'key:value'.
|
||||||
|
type: list
|
||||||
|
show_stats:
|
||||||
|
description:
|
||||||
|
- Show the statistics for IoT Hub.
|
||||||
|
- Note this will have network overhead for each IoT Hub.
|
||||||
|
type: bool
|
||||||
|
show_quota_metrics:
|
||||||
|
description:
|
||||||
|
- Get the quota metrics for an IoT hub.
|
||||||
|
- Note this will have network overhead for each IoT Hub.
|
||||||
|
type: bool
|
||||||
|
show_endpoint_health:
|
||||||
|
description:
|
||||||
|
- Get the health for routing endpoints.
|
||||||
|
- Note this will have network overhead for each IoT Hub.
|
||||||
|
type: bool
|
||||||
|
test_route_message:
|
||||||
|
description:
|
||||||
|
- Test routes message. It will be used to test all routes.
|
||||||
|
type: str
|
||||||
|
list_consumer_groups:
|
||||||
|
description:
|
||||||
|
- List the consumer group of the built-in event hub.
|
||||||
|
type: bool
|
||||||
|
list_keys:
|
||||||
|
description:
|
||||||
|
- List the keys of IoT Hub.
|
||||||
|
- Note this will have network overhead for each IoT Hub.
|
||||||
|
type: bool
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- azure
|
||||||
|
|
||||||
|
author:
|
||||||
|
- Yuwei Zhou (@yuwzho)
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Get facts for one IoT Hub
|
||||||
|
azure_rm_iothub_info:
|
||||||
|
name: Testing
|
||||||
|
resource_group: myResourceGroup
|
||||||
|
|
||||||
|
- name: Get facts for all IoT Hubs
|
||||||
|
azure_rm_iothub_info:
|
||||||
|
|
||||||
|
- name: Get facts for all IoT Hubs in a specific resource group
|
||||||
|
azure_rm_iothub_info:
|
||||||
|
resource_group: myResourceGroup
|
||||||
|
|
||||||
|
- name: Get facts by tags
|
||||||
|
azure_rm_iothub_info:
|
||||||
|
tags:
|
||||||
|
- testing
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
azure_iothubs:
|
||||||
|
description:
|
||||||
|
- List of IoT Hub dicts.
|
||||||
|
returned: always
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
id:
|
||||||
|
description:
|
||||||
|
- Resource ID of the IoT hub.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/myResourceGroup/providers/Microsoft.Devices/IotHubs/Testing"
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the IoT hub.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: Testing
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Resource group of the IoT hub.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: myResourceGroup.
|
||||||
|
location:
|
||||||
|
description:
|
||||||
|
- Location of the IoT hub.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: eastus
|
||||||
|
unit:
|
||||||
|
description:
|
||||||
|
- Units in the IoT Hub.
|
||||||
|
type: int
|
||||||
|
returned: always
|
||||||
|
sample: 1
|
||||||
|
sku:
|
||||||
|
description:
|
||||||
|
- Pricing tier for Azure IoT Hub.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: f1
|
||||||
|
cloud_to_device:
|
||||||
|
description:
|
||||||
|
- Cloud to device message properties.
|
||||||
|
type: complex
|
||||||
|
returned: always
|
||||||
|
contains:
|
||||||
|
max_delivery_count:
|
||||||
|
description:
|
||||||
|
- The number of times the IoT hub attempts to deliver a message on the feedback queue.
|
||||||
|
- "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#cloud-to-device-messages)."
|
||||||
|
type: int
|
||||||
|
returned: always
|
||||||
|
sample: 10
|
||||||
|
ttl_as_iso8601:
|
||||||
|
description:
|
||||||
|
- The period of time for which a message is available to consume before it is expired by the IoT hub.
|
||||||
|
- "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#cloud-to-device-messages)."
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: "1:00:00"
|
||||||
|
enable_file_upload_notifications:
|
||||||
|
description:
|
||||||
|
- Whether file upload notifications are enabled.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: True
|
||||||
|
event_endpoints:
|
||||||
|
description:
|
||||||
|
- Built-in endpoint where to deliver device message.
|
||||||
|
type: complex
|
||||||
|
returned: always
|
||||||
|
contains:
|
||||||
|
endpoint:
|
||||||
|
description:
|
||||||
|
- The Event Hub-compatible endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: "sb://iothub-ns-testing-1478811-9bbc4a15f0.servicebus.windows.net/"
|
||||||
|
partition_count:
|
||||||
|
description:
|
||||||
|
- The number of partitions for receiving device-to-cloud messages in the Event Hub-compatible endpoint.
|
||||||
|
- "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#device-to-cloud-messages)."
|
||||||
|
type: int
|
||||||
|
returned: always
|
||||||
|
sample: 2
|
||||||
|
retention_time_in_days:
|
||||||
|
description:
|
||||||
|
- The retention time for device-to-cloud messages in days.
|
||||||
|
- "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#device-to-cloud-messages)."
|
||||||
|
type: int
|
||||||
|
returned: always
|
||||||
|
sample: 1
|
||||||
|
partition_ids:
|
||||||
|
description:
|
||||||
|
- List of the partition id for the event endpoint.
|
||||||
|
type: list
|
||||||
|
returned: always
|
||||||
|
sample: ["0", "1"]
|
||||||
|
host_name:
|
||||||
|
description:
|
||||||
|
- Host of the IoT hub.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: "testing.azure-devices.net"
|
||||||
|
ip_filters:
|
||||||
|
description:
|
||||||
|
- Configure rules for rejecting or accepting traffic from specific IPv4 addresses.
|
||||||
|
type: complex
|
||||||
|
returned: always
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the filter.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: filter
|
||||||
|
ip_mask:
|
||||||
|
description:
|
||||||
|
- A string that contains the IP address range in CIDR notation for the rule.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: 40.54.7.3
|
||||||
|
action:
|
||||||
|
description:
|
||||||
|
- The desired action for requests captured by this rule.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: Reject
|
||||||
|
routing_endpoints:
|
||||||
|
description:
|
||||||
|
- Custom endpoints.
|
||||||
|
type: complex
|
||||||
|
returned: always
|
||||||
|
contains:
|
||||||
|
event_hubs:
|
||||||
|
description:
|
||||||
|
- List of custom endpoints of event hubs.
|
||||||
|
type: complex
|
||||||
|
returned: always
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: foo
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Resource group of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: bar
|
||||||
|
subscription:
|
||||||
|
description:
|
||||||
|
- Subscription ID of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||||
|
connection_string:
|
||||||
|
description:
|
||||||
|
- Connection string of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
|
||||||
|
service_bus_queues:
|
||||||
|
description:
|
||||||
|
- List of custom endpoints of service bus queue.
|
||||||
|
type: complex
|
||||||
|
returned: always
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: foo
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Resource group of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: bar
|
||||||
|
subscription:
|
||||||
|
description:
|
||||||
|
- Subscription ID of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||||
|
connection_string:
|
||||||
|
description:
|
||||||
|
- Connection string of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
|
||||||
|
service_bus_topics:
|
||||||
|
description:
|
||||||
|
- List of custom endpoints of service bus topic.
|
||||||
|
type: complex
|
||||||
|
returned: always
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: foo
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Resource group of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: bar
|
||||||
|
subscription:
|
||||||
|
description:
|
||||||
|
- Subscription ID of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||||
|
connection_string:
|
||||||
|
description:
|
||||||
|
- Connection string of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
|
||||||
|
storage_containers:
|
||||||
|
description:
|
||||||
|
- List of custom endpoints of storage.
|
||||||
|
type: complex
|
||||||
|
returned: always
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: foo
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Resource group of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: bar
|
||||||
|
subscription:
|
||||||
|
description:
|
||||||
|
- Subscription ID of the endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
|
||||||
|
connection_string:
|
||||||
|
description:
|
||||||
|
- Connection string of the custom endpoint.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
|
||||||
|
routes:
|
||||||
|
description:
|
||||||
|
- Route device-to-cloud messages to service-facing endpoints.
|
||||||
|
type: complex
|
||||||
|
returned: always
|
||||||
|
contains:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the route.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: route1
|
||||||
|
source:
|
||||||
|
description:
|
||||||
|
- The origin of the data stream to be acted upon.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: device_messages
|
||||||
|
enabled:
|
||||||
|
description:
|
||||||
|
- Whether to enable the route.
|
||||||
|
type: bool
|
||||||
|
returned: always
|
||||||
|
sample: true
|
||||||
|
endpoint_name:
|
||||||
|
description:
|
||||||
|
- The name of the endpoint in I(routing_endpoints) where IoT Hub sends messages that match the query.
|
||||||
|
type: str
|
||||||
|
returned: always
|
||||||
|
sample: foo
|
||||||
|
condition:
|
||||||
|
description:
|
||||||
|
- "The query expression for the routing query that is run against the message application properties,
|
||||||
|
system properties, message body, device twin tags, and device twin properties to determine if it is a match for the endpoint."
|
||||||
|
- "For more information about constructing a query,
|
||||||
|
see U(https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-routing-query-syntax)"
|
||||||
|
type: bool
|
||||||
|
returned: always
|
||||||
|
sample: "true"
|
||||||
|
tags:
|
||||||
|
description:
|
||||||
|
- Limit results by providing a list of tags. Format tags as 'key' or 'key:value'.
|
||||||
|
type: dict
|
||||||
|
returned: always
|
||||||
|
sample: { 'key1': 'value1' }
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.azure_rm_common import AzureRMModuleBase
|
||||||
|
from ansible.module_utils.common.dict_transformations import _camel_to_snake
|
||||||
|
|
||||||
|
try:
|
||||||
|
from msrestazure.azure_exceptions import CloudError
|
||||||
|
from msrestazure.tools import parse_resource_id
|
||||||
|
from azure.common import AzureHttpError
|
||||||
|
except Exception:
|
||||||
|
# handled in azure_rm_common
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AzureRMIoTHubFacts(AzureRMModuleBase):
|
||||||
|
"""Utility class to get IoT Hub facts"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.module_args = dict(
|
||||||
|
name=dict(type='str'),
|
||||||
|
resource_group=dict(type='str'),
|
||||||
|
tags=dict(type='list'),
|
||||||
|
show_stats=dict(type='bool'),
|
||||||
|
show_quota_metrics=dict(type='bool'),
|
||||||
|
show_endpoint_health=dict(type='bool'),
|
||||||
|
list_keys=dict(type='bool'),
|
||||||
|
test_route_message=dict(type='str'),
|
||||||
|
list_consumer_groups=dict(type='bool')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.results = dict(
|
||||||
|
changed=False,
|
||||||
|
azure_iothubs=[]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.name = None
|
||||||
|
self.resource_group = None
|
||||||
|
self.tags = None
|
||||||
|
self.show_stats = None
|
||||||
|
self.show_quota_metrics = None
|
||||||
|
self.show_endpoint_health = None
|
||||||
|
self.list_keys = None
|
||||||
|
self.test_route_message = None
|
||||||
|
self.list_consumer_groups = None
|
||||||
|
|
||||||
|
super(AzureRMIoTHubFacts, self).__init__(
|
||||||
|
derived_arg_spec=self.module_args,
|
||||||
|
supports_tags=False,
|
||||||
|
facts_module=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def exec_module(self, **kwargs):
|
||||||
|
|
||||||
|
for key in self.module_args:
|
||||||
|
setattr(self, key, kwargs[key])
|
||||||
|
|
||||||
|
response = []
|
||||||
|
if self.name:
|
||||||
|
response = self.get_item()
|
||||||
|
elif self.resource_group:
|
||||||
|
response = self.list_by_resource_group()
|
||||||
|
else:
|
||||||
|
response = self.list_all()
|
||||||
|
self.results['iothubs'] = [self.to_dict(x) for x in response if self.has_tags(x.tags, self.tags)]
|
||||||
|
return self.results
|
||||||
|
|
||||||
|
def get_item(self):
|
||||||
|
"""Get a single IoT Hub"""
|
||||||
|
|
||||||
|
self.log('Get properties for {0}'.format(self.name))
|
||||||
|
|
||||||
|
item = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
item = self.IoThub_client.iot_hub_resource.get(self.resource_group, self.name)
|
||||||
|
return [item]
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when getting IoT Hub {0}: {1}'.format(self.name, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def list_all(self):
|
||||||
|
"""Get all IoT Hubs"""
|
||||||
|
|
||||||
|
self.log('List all IoT Hubs')
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.IoThub_client.iot_hub_resource.list_by_subscription()
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Failed to list all IoT Hubs - {0}'.format(str(exc)))
|
||||||
|
|
||||||
|
def list_by_resource_group(self):
|
||||||
|
try:
|
||||||
|
return self.IoThub_client.iot_hub_resource.list(self.resource_group)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Failed to list IoT Hub in resource group {0} - {1}'.format(self.resource_group, exc.message or str(exc)))
|
||||||
|
|
||||||
|
def show_hub_stats(self, resource_group, name):
|
||||||
|
try:
|
||||||
|
return self.IoThub_client.iot_hub_resource.get_stats(resource_group, name).as_dict()
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Failed to getting statistics for IoT Hub {0}/{1}: {2}'.format(resource_group, name, str(exc)))
|
||||||
|
|
||||||
|
def show_hub_quota_metrics(self, resource_group, name):
|
||||||
|
result = []
|
||||||
|
try:
|
||||||
|
resp = self.IoThub_client.iot_hub_resource.get_quota_metrics(resource_group, name)
|
||||||
|
while True:
|
||||||
|
result.append(resp.next().as_dict())
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Failed to getting quota metrics for IoT Hub {0}/{1}: {2}'.format(resource_group, name, str(exc)))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def show_hub_endpoint_health(self, resource_group, name):
|
||||||
|
result = []
|
||||||
|
try:
|
||||||
|
resp = self.IoThub_client.iot_hub_resource.get_endpoint_health(resource_group, name)
|
||||||
|
while True:
|
||||||
|
result.append(resp.next().as_dict())
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Failed to getting health for IoT Hub {0}/{1} routing endpoint: {2}'.format(resource_group, name, str(exc)))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def test_all_routes(self, resource_group, name):
|
||||||
|
try:
|
||||||
|
return self.IoThub_client.iot_hub_resource.test_all_routes(self.test_route_message, resource_group, name).routes.as_dict()
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Failed to getting statistics for IoT Hub {0}/{1}: {2}'.format(resource_group, name, str(exc)))
|
||||||
|
|
||||||
|
def list_hub_keys(self, resource_group, name):
|
||||||
|
result = []
|
||||||
|
try:
|
||||||
|
resp = self.IoThub_client.iot_hub_resource.list_keys(resource_group, name)
|
||||||
|
while True:
|
||||||
|
result.append(resp.next().as_dict())
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Failed to getting health for IoT Hub {0}/{1} routing endpoint: {2}'.format(resource_group, name, str(exc)))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def list_event_hub_consumer_groups(self, resource_group, name, event_hub_endpoint='events'):
|
||||||
|
result = []
|
||||||
|
try:
|
||||||
|
resp = self.IoThub_client.iot_hub_resource.list_event_hub_consumer_groups(resource_group, name, event_hub_endpoint)
|
||||||
|
while True:
|
||||||
|
cg = resp.next()
|
||||||
|
result.append(dict(
|
||||||
|
id=cg.id,
|
||||||
|
name=cg.name
|
||||||
|
))
|
||||||
|
except StopIteration:
|
||||||
|
pass
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Failed to listing consumer group for IoT Hub {0}/{1} routing endpoint: {2}'.format(resource_group, name, str(exc)))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def route_to_dict(self, route):
|
||||||
|
return dict(
|
||||||
|
name=route.name,
|
||||||
|
source=_camel_to_snake(route.source),
|
||||||
|
endpoint_name=route.endpoint_names[0],
|
||||||
|
enabled=route.is_enabled,
|
||||||
|
condition=route.condition
|
||||||
|
)
|
||||||
|
|
||||||
|
def instance_dict_to_dict(self, instance_dict):
|
||||||
|
result = dict()
|
||||||
|
for key in instance_dict.keys():
|
||||||
|
result[key] = instance_dict[key].as_dict()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def to_dict(self, hub):
|
||||||
|
result = dict()
|
||||||
|
properties = hub.properties
|
||||||
|
result['id'] = hub.id
|
||||||
|
result['name'] = hub.name
|
||||||
|
result['resource_group'] = parse_resource_id(hub.id).get('resource_group')
|
||||||
|
result['location'] = hub.location
|
||||||
|
result['tags'] = hub.tags
|
||||||
|
result['unit'] = hub.sku.capacity
|
||||||
|
result['sku'] = hub.sku.name.lower()
|
||||||
|
result['cloud_to_device'] = dict(
|
||||||
|
max_delivery_count=properties.cloud_to_device.feedback.max_delivery_count,
|
||||||
|
ttl_as_iso8601=str(properties.cloud_to_device.feedback.ttl_as_iso8601)
|
||||||
|
)
|
||||||
|
result['enable_file_upload_notifications'] = properties.enable_file_upload_notifications
|
||||||
|
result['event_hub_endpoints'] = self.instance_dict_to_dict(properties.event_hub_endpoints)
|
||||||
|
result['host_name'] = properties.host_name
|
||||||
|
result['ip_filters'] = [x.as_dict() for x in properties.ip_filter_rules]
|
||||||
|
result['routing_endpoints'] = properties.routing.endpoints.as_dict()
|
||||||
|
result['routes'] = [self.route_to_dict(x) for x in properties.routing.routes]
|
||||||
|
result['fallback_route'] = self.route_to_dict(properties.routing.fallback_route)
|
||||||
|
result['status'] = properties.state
|
||||||
|
result['storage_endpoints'] = self.instance_dict_to_dict(properties.storage_endpoints)
|
||||||
|
|
||||||
|
# network overhead part
|
||||||
|
if self.show_stats:
|
||||||
|
result['statistics'] = self.show_hub_stats(result['resource_group'], hub.name)
|
||||||
|
if self.show_quota_metrics:
|
||||||
|
result['quota_metrics'] = self.show_hub_quota_metrics(result['resource_group'], hub.name)
|
||||||
|
if self.show_endpoint_health:
|
||||||
|
result['endpoint_health'] = self.show_hub_endpoint_health(result['resource_group'], hub.name)
|
||||||
|
if self.list_keys:
|
||||||
|
result['keys'] = self.list_hub_keys(result['resource_group'], hub.name)
|
||||||
|
if self.test_route_message:
|
||||||
|
result['test_route_result'] = self.test_all_routes(result['resource_group'], hub.name)
|
||||||
|
if self.list_consumer_groups:
|
||||||
|
result['consumer_groups'] = self.list_event_hub_consumer_groups(result['resource_group'], hub.name)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main module execution code path"""
|
||||||
|
|
||||||
|
AzureRMIoTHubFacts()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
169
lib/ansible/modules/cloud/azure/azure_rm_iothubconsumergroup.py
Normal file
169
lib/ansible/modules/cloud/azure/azure_rm_iothubconsumergroup.py
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
#
|
||||||
|
# Copyright (c) 2019 Yuwei Zhou, <yuwzho@microsoft.com>
|
||||||
|
#
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'}
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: azure_rm_iothubconsumergroup
|
||||||
|
version_added: "2.9"
|
||||||
|
short_description: Manage Azure IoT hub
|
||||||
|
description:
|
||||||
|
- Create, delete an Azure IoT hub.
|
||||||
|
options:
|
||||||
|
resource_group:
|
||||||
|
description:
|
||||||
|
- Name of resource group.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
hub:
|
||||||
|
description:
|
||||||
|
- Name of the IoT hub.
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- State of the IoT hub. Use C(present) to create or update an IoT hub and C(absent) to delete an IoT hub.
|
||||||
|
type: str
|
||||||
|
default: present
|
||||||
|
choices:
|
||||||
|
- absent
|
||||||
|
- present
|
||||||
|
event_hub:
|
||||||
|
description:
|
||||||
|
- Event hub endpoint name.
|
||||||
|
type: str
|
||||||
|
default: events
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the consumer group.
|
||||||
|
type: str
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- azure
|
||||||
|
- azure_tags
|
||||||
|
|
||||||
|
author:
|
||||||
|
- Yuwei Zhou (@yuwzho)
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Create an IoT hub consumer group
|
||||||
|
azure_rm_iothubconsumergroup:
|
||||||
|
name: test
|
||||||
|
resource_group: myResourceGroup
|
||||||
|
hub: Testing
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
id:
|
||||||
|
description:
|
||||||
|
- Resource ID of the consumer group.
|
||||||
|
returned: success
|
||||||
|
type: str
|
||||||
|
sample: "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/myResourceGroup
|
||||||
|
/providers/Microsoft.Devices/IotHubs/Testing/events/ConsumerGroups/%24Default"
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the consumer group.
|
||||||
|
sample: Testing
|
||||||
|
returned: success
|
||||||
|
type: str
|
||||||
|
''' # NOQA
|
||||||
|
|
||||||
|
from ansible.module_utils.azure_rm_common import AzureRMModuleBase, format_resource_id
|
||||||
|
from ansible.module_utils.common.dict_transformations import _snake_to_camel, _camel_to_snake
|
||||||
|
import re
|
||||||
|
|
||||||
|
try:
|
||||||
|
from msrestazure.tools import parse_resource_id
|
||||||
|
from msrestazure.azure_exceptions import CloudError
|
||||||
|
except ImportError:
|
||||||
|
# This is handled in azure_rm_common
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AzureRMIoTHubConsumerGroup(AzureRMModuleBase):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.module_arg_spec = dict(
|
||||||
|
resource_group=dict(type='str', required=True),
|
||||||
|
name=dict(type='str', required=True),
|
||||||
|
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||||
|
hub=dict(type='str', required=True),
|
||||||
|
event_hub=dict(type='str', default='events')
|
||||||
|
)
|
||||||
|
|
||||||
|
self.results = dict(
|
||||||
|
changed=False,
|
||||||
|
id=None
|
||||||
|
)
|
||||||
|
|
||||||
|
self.resource_group = None
|
||||||
|
self.name = None
|
||||||
|
self.state = None
|
||||||
|
self.hub = None
|
||||||
|
self.event_hub = None
|
||||||
|
|
||||||
|
super(AzureRMIoTHubConsumerGroup, self).__init__(self.module_arg_spec, supports_check_mode=True)
|
||||||
|
|
||||||
|
def exec_module(self, **kwargs):
|
||||||
|
|
||||||
|
for key in self.module_arg_spec.keys():
|
||||||
|
setattr(self, key, kwargs[key])
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
cg = self.get_cg()
|
||||||
|
if not cg and self.state == 'present':
|
||||||
|
changed = True
|
||||||
|
if not self.check_mode:
|
||||||
|
cg = self.create_cg()
|
||||||
|
elif cg and self.state == 'absent':
|
||||||
|
changed = True
|
||||||
|
cg = None
|
||||||
|
if not self.check_mode:
|
||||||
|
self.delete_cg()
|
||||||
|
self.results = dict(
|
||||||
|
id=cg.id,
|
||||||
|
name=cg.name
|
||||||
|
) if cg else dict()
|
||||||
|
self.results['changed'] = changed
|
||||||
|
return self.results
|
||||||
|
|
||||||
|
def get_cg(self):
|
||||||
|
try:
|
||||||
|
return self.IoThub_client.iot_hub_resource.get_event_hub_consumer_group(self.resource_group, self.hub, self.event_hub, self.name)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create_cg(self):
|
||||||
|
try:
|
||||||
|
return self.IoThub_client.iot_hub_resource.create_event_hub_consumer_group(self.resource_group, self.hub, self.event_hub, self.name)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when creating the consumer group {0} for IoT Hub {1} event hub {2}: {3}'.format(self.name, self.hub, self.event_hub, str(exc)))
|
||||||
|
|
||||||
|
def delete_cg(self):
|
||||||
|
try:
|
||||||
|
return self.IoThub_client.iot_hub_resource.delete_event_hub_consumer_group(self.resource_group, self.hub, self.event_hub, self.name)
|
||||||
|
except Exception as exc:
|
||||||
|
self.fail('Error when deleting the consumer group {0} for IoT Hub {1} event hub {2}: {3}'.format(self.name, self.hub, self.event_hub, str(exc)))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
AzureRMIoTHubConsumerGroup()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -36,3 +36,4 @@ azure-mgmt-hdinsight==0.1.0
|
||||||
azure-mgmt-devtestlabs==3.0.0
|
azure-mgmt-devtestlabs==3.0.0
|
||||||
azure-mgmt-loganalytics==0.2.0
|
azure-mgmt-loganalytics==0.2.0
|
||||||
azure-mgmt-automation==0.1.1
|
azure-mgmt-automation==0.1.1
|
||||||
|
azure-mgmt-iothub==0.7.0
|
||||||
|
|
3
test/integration/targets/azure_rm_iothub/aliases
Normal file
3
test/integration/targets/azure_rm_iothub/aliases
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
cloud/azure
|
||||||
|
shippable/azure/group2
|
||||||
|
destructive
|
2
test/integration/targets/azure_rm_iothub/meta/main.yml
Normal file
2
test/integration/targets/azure_rm_iothub/meta/main.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
dependencies:
|
||||||
|
- setup_azure
|
172
test/integration/targets/azure_rm_iothub/tasks/main.yml
Normal file
172
test/integration/targets/azure_rm_iothub/tasks/main.yml
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
- set_fact:
|
||||||
|
rpfx: "{{ resource_group | hash('md5') | truncate(8, True, '') }}"
|
||||||
|
|
||||||
|
- name: Create IoT Hub (check mode)
|
||||||
|
azure_rm_iothub:
|
||||||
|
name: "hub{{ rpfx }}"
|
||||||
|
resource_group: "{{ resource_group }}"
|
||||||
|
ip_filters:
|
||||||
|
- name: filter1
|
||||||
|
action: reject
|
||||||
|
ip_mask: 40.60.80.10
|
||||||
|
check_mode: yes
|
||||||
|
register: iothub
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- iothub.changed
|
||||||
|
|
||||||
|
- name: Query IoT Hub
|
||||||
|
azure_rm_iothub_info:
|
||||||
|
name: "hub{{ rpfx }}"
|
||||||
|
resource_group: "{{ resource_group }}"
|
||||||
|
register: iothub
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Create IoT Hub
|
||||||
|
azure_rm_iothub:
|
||||||
|
name: "hub{{ rpfx }}"
|
||||||
|
resource_group: "{{ resource_group }}"
|
||||||
|
ip_filters:
|
||||||
|
- name: filter1
|
||||||
|
action: reject
|
||||||
|
ip_mask: 40.60.80.10
|
||||||
|
register: iothub
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- iothub.changed
|
||||||
|
|
||||||
|
- name: Create IoT Hub (idempontent)
|
||||||
|
azure_rm_iothub:
|
||||||
|
name: "hub{{ rpfx }}"
|
||||||
|
resource_group: "{{ resource_group }}"
|
||||||
|
ip_filters:
|
||||||
|
- name: filter1
|
||||||
|
action: reject
|
||||||
|
ip_mask: 40.60.80.10
|
||||||
|
register: iothub
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not iothub.changed
|
||||||
|
|
||||||
|
- name: Query IoT Hub
|
||||||
|
azure_rm_iothub_info:
|
||||||
|
name: "hub{{ rpfx }}"
|
||||||
|
resource_group: "{{ resource_group }}"
|
||||||
|
list_keys: yes
|
||||||
|
register: iothub
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- iothub.iothubs | length == 1
|
||||||
|
|
||||||
|
- set_fact:
|
||||||
|
registry_write_name: "{{ item.key_name }}"
|
||||||
|
registry_write_key: "{{ item.primary_key }}"
|
||||||
|
with_items: "{{ iothub.iothubs[0]['keys'] }}"
|
||||||
|
when: item.rights == 'RegistryWrite, ServiceConnect, DeviceConnect'
|
||||||
|
|
||||||
|
- name: Create devices
|
||||||
|
azure_rm_iotdevice:
|
||||||
|
hub: "hub{{ rpfx }}"
|
||||||
|
hub_policy_name: "{{ registry_write_name }}"
|
||||||
|
hub_policy_key: "{{ registry_write_key }}"
|
||||||
|
name: "mydevice{{ item }}"
|
||||||
|
twin_tags:
|
||||||
|
location:
|
||||||
|
country: US
|
||||||
|
city: Redmond
|
||||||
|
sensor: humidity
|
||||||
|
with_items:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
|
||||||
|
- name: Query devices
|
||||||
|
azure_rm_iotdevice_info:
|
||||||
|
hub: "hub{{ rpfx }}"
|
||||||
|
hub_policy_name: "{{ registry_write_name }}"
|
||||||
|
hub_policy_key: "{{ registry_write_key }}"
|
||||||
|
register: devices
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- devices.iot_devices | length == 2
|
||||||
|
|
||||||
|
- name: Query devices
|
||||||
|
azure_rm_iotdevice_info:
|
||||||
|
hub: "hub{{ rpfx }}"
|
||||||
|
name: "mydevice1"
|
||||||
|
hub_policy_name: "{{ registry_write_name }}"
|
||||||
|
hub_policy_key: "{{ registry_write_key }}"
|
||||||
|
register: devices
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- devices.iot_devices | length == 1
|
||||||
|
- devices.iot_devices[0].deviceId == 'mydevice1'
|
||||||
|
|
||||||
|
- name: Query devices twin
|
||||||
|
azure_rm_iotdevice_info:
|
||||||
|
hub: "hub{{ rpfx }}"
|
||||||
|
query: "SELECT * FROM devices WHERE tags.location.country = 'US'"
|
||||||
|
hub_policy_name: "{{ registry_write_name }}"
|
||||||
|
hub_policy_key: "{{ registry_write_key }}"
|
||||||
|
register: devices
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- devices.iot_devices | length == 2
|
||||||
|
|
||||||
|
- name: Update devices
|
||||||
|
azure_rm_iotdevice:
|
||||||
|
hub: "hub{{ rpfx }}"
|
||||||
|
hub_policy_name: "{{ registry_write_name }}"
|
||||||
|
hub_policy_key: "{{ registry_write_key }}"
|
||||||
|
name: "mydevice{{ item }}"
|
||||||
|
edge_enabled: yes
|
||||||
|
twin_tags:
|
||||||
|
location:
|
||||||
|
country: China
|
||||||
|
city: Shanghai
|
||||||
|
sensor: humidity
|
||||||
|
with_items:
|
||||||
|
- 1
|
||||||
|
- 3
|
||||||
|
|
||||||
|
- name: Query devices twin
|
||||||
|
azure_rm_iotdevice_info:
|
||||||
|
hub: "hub{{ rpfx }}"
|
||||||
|
query: "SELECT * FROM devices WHERE tags.location.country = 'US'"
|
||||||
|
hub_policy_name: "{{ registry_write_name }}"
|
||||||
|
hub_policy_key: "{{ registry_write_key }}"
|
||||||
|
register: devices
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- devices.iot_devices | length == 1
|
||||||
|
- devices.iot_devices[0].deviceId == 'mydevice2'
|
||||||
|
|
||||||
|
- name: Delete IoT Hub (check mode)
|
||||||
|
azure_rm_iothub:
|
||||||
|
name: "hub{{ rpfx }}"
|
||||||
|
resource_group: "{{ resource_group }}"
|
||||||
|
state: absent
|
||||||
|
check_mode: yes
|
||||||
|
register: iothub
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- iothub.changed
|
||||||
|
|
||||||
|
- name: Delete IoT Hub
|
||||||
|
azure_rm_iothub:
|
||||||
|
name: "hub{{ rpfx }}"
|
||||||
|
resource_group: "{{ resource_group }}"
|
||||||
|
state: absent
|
||||||
|
register: iothub
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- iothub.changed
|
|
@ -36,3 +36,4 @@ azure-mgmt-hdinsight==0.1.0
|
||||||
azure-mgmt-devtestlabs==3.0.0
|
azure-mgmt-devtestlabs==3.0.0
|
||||||
azure-mgmt-loganalytics==0.2.0
|
azure-mgmt-loganalytics==0.2.0
|
||||||
azure-mgmt-automation==0.1.1
|
azure-mgmt-automation==0.1.1
|
||||||
|
azure-mgmt-iothub==0.7.0
|
||||||
|
|
Loading…
Reference in a new issue