mirror of https://github.com/ansible/ansible.git
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 mergepull/61364/head
parent
6fb7073adc
commit
b4732dd2e6
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -0,0 +1,3 @@
|
||||
cloud/azure
|
||||
shippable/azure/group2
|
||||
destructive
|
@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
- setup_azure
|
@ -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
|
Loading…
Reference in New Issue