1#!/usr/bin/python
2#
3# Copyright (c) 2019 Yuwei Zhou, <yuwzho@microsoft.com>
4#
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7
8from __future__ import absolute_import, division, print_function
9__metaclass__ = type
10
11
12ANSIBLE_METADATA = {'metadata_version': '1.1',
13                    'status': ['preview'],
14                    'supported_by': 'community'}
15
16DOCUMENTATION = '''
17---
18module: azure_rm_iothub_info
19
20version_added: "2.9"
21
22short_description: Get IoT Hub facts
23
24description:
25    - Get facts for a specific IoT Hub or all IoT Hubs.
26
27options:
28    name:
29        description:
30            - Limit results to a specific resource group.
31        type: str
32    resource_group:
33        description:
34            - The resource group to search for the desired IoT Hub.
35        type: str
36    tags:
37        description:
38            - Limit results by providing a list of tags. Format tags as 'key' or 'key:value'.
39        type: list
40    show_stats:
41        description:
42            - Show the statistics for IoT Hub.
43            - Note this will have network overhead for each IoT Hub.
44        type: bool
45    show_quota_metrics:
46        description:
47            - Get the quota metrics for an IoT hub.
48            - Note this will have network overhead for each IoT Hub.
49        type: bool
50    show_endpoint_health:
51        description:
52            - Get the health for routing endpoints.
53            - Note this will have network overhead for each IoT Hub.
54        type: bool
55    test_route_message:
56        description:
57            - Test routes message. It will be used to test all routes.
58        type: str
59    list_consumer_groups:
60        description:
61            - List the consumer group of the built-in event hub.
62        type: bool
63    list_keys:
64        description:
65            - List the keys of IoT Hub.
66            - Note this will have network overhead for each IoT Hub.
67        type: bool
68extends_documentation_fragment:
69    - azure
70
71author:
72    - Yuwei Zhou (@yuwzho)
73'''
74
75EXAMPLES = '''
76    - name: Get facts for one IoT Hub
77      azure_rm_iothub_info:
78        name: Testing
79        resource_group: myResourceGroup
80
81    - name: Get facts for all IoT Hubs
82      azure_rm_iothub_info:
83
84    - name: Get facts for all IoT Hubs in a specific resource group
85      azure_rm_iothub_info:
86        resource_group: myResourceGroup
87
88    - name: Get facts by tags
89      azure_rm_iothub_info:
90        tags:
91          - testing
92'''
93
94RETURN = '''
95azure_iothubs:
96    description:
97        - List of IoT Hub dicts.
98    returned: always
99    type: complex
100    contains:
101        id:
102            description:
103                - Resource ID of the IoT hub.
104            type: str
105            returned: always
106            sample: "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/myResourceGroup/providers/Microsoft.Devices/IotHubs/Testing"
107        name:
108            description:
109                - Name of the IoT hub.
110            type: str
111            returned: always
112            sample: Testing
113        resource_group:
114            description:
115                - Resource group of the IoT hub.
116            type: str
117            returned: always
118            sample: myResourceGroup.
119        location:
120            description:
121                - Location of the IoT hub.
122            type: str
123            returned: always
124            sample: eastus
125        unit:
126            description:
127                - Units in the IoT Hub.
128            type: int
129            returned: always
130            sample: 1
131        sku:
132            description:
133                - Pricing tier for Azure IoT Hub.
134            type: str
135            returned: always
136            sample: f1
137        cloud_to_device:
138            description:
139                - Cloud to device message properties.
140            type: complex
141            returned: always
142            contains:
143                max_delivery_count:
144                    description:
145                        - The number of times the IoT hub attempts to deliver a message on the feedback queue.
146                        - "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#cloud-to-device-messages)."
147                    type: int
148                    returned: always
149                    sample: 10
150                ttl_as_iso8601:
151                    description:
152                        - The period of time for which a message is available to consume before it is expired by the IoT hub.
153                        - "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#cloud-to-device-messages)."
154                    type: str
155                    returned: always
156                    sample: "1:00:00"
157        enable_file_upload_notifications:
158            description:
159                - Whether file upload notifications are enabled.
160            type: str
161            returned: always
162            sample: True
163        event_endpoints:
164            description:
165                - Built-in endpoint where to deliver device message.
166            type: complex
167            returned: always
168            contains:
169                endpoint:
170                    description:
171                        - The Event Hub-compatible endpoint.
172                    type: str
173                    returned: always
174                    sample: "sb://iothub-ns-testing-1478811-9bbc4a15f0.servicebus.windows.net/"
175                partition_count:
176                    description:
177                        - The number of partitions for receiving device-to-cloud messages in the Event Hub-compatible endpoint.
178                        - "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#device-to-cloud-messages)."
179                    type: int
180                    returned: always
181                    sample: 2
182                retention_time_in_days:
183                    description:
184                        - The retention time for device-to-cloud messages in days.
185                        - "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#device-to-cloud-messages)."
186                    type: int
187                    returned: always
188                    sample: 1
189                partition_ids:
190                    description:
191                        - List of the partition id for the event endpoint.
192                    type: list
193                    returned: always
194                    sample: ["0", "1"]
195        host_name:
196            description:
197                - Host of the IoT hub.
198            type: str
199            returned: always
200            sample: "testing.azure-devices.net"
201        ip_filters:
202            description:
203                - Configure rules for rejecting or accepting traffic from specific IPv4 addresses.
204            type: complex
205            returned: always
206            contains:
207                name:
208                    description:
209                        - Name of the filter.
210                    type: str
211                    returned: always
212                    sample: filter
213                ip_mask:
214                    description:
215                        - A string that contains the IP address range in CIDR notation for the rule.
216                    type: str
217                    returned: always
218                    sample: 40.54.7.3
219                action:
220                    description:
221                        - The desired action for requests captured by this rule.
222                    type: str
223                    returned: always
224                    sample: Reject
225        routing_endpoints:
226            description:
227                - Custom endpoints.
228            type: complex
229            returned: always
230            contains:
231                event_hubs:
232                    description:
233                        - List of custom endpoints of event hubs.
234                    type: complex
235                    returned: always
236                    contains:
237                        name:
238                            description:
239                                - Name of the custom endpoint.
240                            type: str
241                            returned: always
242                            sample: foo
243                        resource_group:
244                            description:
245                                - Resource group of the endpoint.
246                            type: str
247                            returned: always
248                            sample: bar
249                        subscription:
250                            description:
251                                - Subscription ID of the endpoint.
252                            type: str
253                            returned: always
254                            sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
255                        connection_string:
256                            description:
257                                - Connection string of the custom endpoint.
258                            type: str
259                            returned: always
260                            sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
261                service_bus_queues:
262                    description:
263                        - List of custom endpoints of service bus queue.
264                    type: complex
265                    returned: always
266                    contains:
267                        name:
268                            description:
269                                - Name of the custom endpoint.
270                            type: str
271                            returned: always
272                            sample: foo
273                        resource_group:
274                            description:
275                                - Resource group of the endpoint.
276                            type: str
277                            returned: always
278                            sample: bar
279                        subscription:
280                            description:
281                                - Subscription ID of the endpoint.
282                            type: str
283                            returned: always
284                            sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
285                        connection_string:
286                            description:
287                                - Connection string of the custom endpoint.
288                            type: str
289                            returned: always
290                            sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
291                service_bus_topics:
292                    description:
293                        - List of custom endpoints of service bus topic.
294                    type: complex
295                    returned: always
296                    contains:
297                        name:
298                            description:
299                                - Name of the custom endpoint.
300                            type: str
301                            returned: always
302                            sample: foo
303                        resource_group:
304                            description:
305                                - Resource group of the endpoint.
306                            type: str
307                            returned: always
308                            sample: bar
309                        subscription:
310                            description:
311                                - Subscription ID of the endpoint.
312                            type: str
313                            returned: always
314                            sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
315                        connection_string:
316                            description:
317                                - Connection string of the custom endpoint.
318                            type: str
319                            returned: always
320                            sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
321                storage_containers:
322                    description:
323                        - List of custom endpoints of storage.
324                    type: complex
325                    returned: always
326                    contains:
327                        name:
328                            description:
329                                - Name of the custom endpoint.
330                            type: str
331                            returned: always
332                            sample: foo
333                        resource_group:
334                            description:
335                                - Resource group of the endpoint.
336                            type: str
337                            returned: always
338                            sample: bar
339                        subscription:
340                            description:
341                                - Subscription ID of the endpoint.
342                            type: str
343                            returned: always
344                            sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
345                        connection_string:
346                            description:
347                                - Connection string of the custom endpoint.
348                            type: str
349                            returned: always
350                            sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
351        routes:
352            description:
353                - Route device-to-cloud messages to service-facing endpoints.
354            type: complex
355            returned: always
356            contains:
357                name:
358                    description:
359                        - Name of the route.
360                    type: str
361                    returned: always
362                    sample: route1
363                source:
364                    description:
365                        - The origin of the data stream to be acted upon.
366                    type: str
367                    returned: always
368                    sample: device_messages
369                enabled:
370                    description:
371                        - Whether to enable the route.
372                    type: bool
373                    returned: always
374                    sample: true
375                endpoint_name:
376                    description:
377                        - The name of the endpoint in I(routing_endpoints) where IoT Hub sends messages that match the query.
378                    type: str
379                    returned: always
380                    sample: foo
381                condition:
382                    description:
383                        - "The query expression for the routing query that is run against the message application properties,
384                           system properties, message body, device twin tags, and device twin properties to determine if it is a match for the endpoint."
385                        - "For more information about constructing a query,
386                           see U(https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-routing-query-syntax)"
387                    type: bool
388                    returned: always
389                    sample: "true"
390        tags:
391            description:
392                - Limit results by providing a list of tags. Format tags as 'key' or 'key:value'.
393            type: dict
394            returned: always
395            sample: { 'key1': 'value1' }
396'''
397
398from ansible.module_utils.azure_rm_common import AzureRMModuleBase
399from ansible.module_utils.common.dict_transformations import _camel_to_snake
400
401try:
402    from msrestazure.azure_exceptions import CloudError
403    from msrestazure.tools import parse_resource_id
404    from azure.common import AzureHttpError
405except Exception:
406    # handled in azure_rm_common
407    pass
408
409
410class AzureRMIoTHubFacts(AzureRMModuleBase):
411    """Utility class to get IoT Hub facts"""
412
413    def __init__(self):
414
415        self.module_args = dict(
416            name=dict(type='str'),
417            resource_group=dict(type='str'),
418            tags=dict(type='list'),
419            show_stats=dict(type='bool'),
420            show_quota_metrics=dict(type='bool'),
421            show_endpoint_health=dict(type='bool'),
422            list_keys=dict(type='bool'),
423            test_route_message=dict(type='str'),
424            list_consumer_groups=dict(type='bool')
425        )
426
427        self.results = dict(
428            changed=False,
429            azure_iothubs=[]
430        )
431
432        self.name = None
433        self.resource_group = None
434        self.tags = None
435        self.show_stats = None
436        self.show_quota_metrics = None
437        self.show_endpoint_health = None
438        self.list_keys = None
439        self.test_route_message = None
440        self.list_consumer_groups = None
441
442        super(AzureRMIoTHubFacts, self).__init__(
443            derived_arg_spec=self.module_args,
444            supports_tags=False,
445            facts_module=True
446        )
447
448    def exec_module(self, **kwargs):
449
450        for key in self.module_args:
451            setattr(self, key, kwargs[key])
452
453        response = []
454        if self.name:
455            response = self.get_item()
456        elif self.resource_group:
457            response = self.list_by_resource_group()
458        else:
459            response = self.list_all()
460        self.results['iothubs'] = [self.to_dict(x) for x in response if self.has_tags(x.tags, self.tags)]
461        return self.results
462
463    def get_item(self):
464        """Get a single IoT Hub"""
465
466        self.log('Get properties for {0}'.format(self.name))
467
468        item = None
469
470        try:
471            item = self.IoThub_client.iot_hub_resource.get(self.resource_group, self.name)
472            return [item]
473        except Exception as exc:
474            self.fail('Error when getting IoT Hub {0}: {1}'.format(self.name, exc.message or str(exc)))
475
476    def list_all(self):
477        """Get all IoT Hubs"""
478
479        self.log('List all IoT Hubs')
480
481        try:
482            return self.IoThub_client.iot_hub_resource.list_by_subscription()
483        except Exception as exc:
484            self.fail('Failed to list all IoT Hubs - {0}'.format(str(exc)))
485
486    def list_by_resource_group(self):
487        try:
488            return self.IoThub_client.iot_hub_resource.list(self.resource_group)
489        except Exception as exc:
490            self.fail('Failed to list IoT Hub in resource group {0} - {1}'.format(self.resource_group, exc.message or str(exc)))
491
492    def show_hub_stats(self, resource_group, name):
493        try:
494            return self.IoThub_client.iot_hub_resource.get_stats(resource_group, name).as_dict()
495        except Exception as exc:
496            self.fail('Failed to getting statistics for IoT Hub {0}/{1}: {2}'.format(resource_group, name, str(exc)))
497
498    def show_hub_quota_metrics(self, resource_group, name):
499        result = []
500        try:
501            resp = self.IoThub_client.iot_hub_resource.get_quota_metrics(resource_group, name)
502            while True:
503                result.append(resp.next().as_dict())
504        except StopIteration:
505            pass
506        except Exception as exc:
507            self.fail('Failed to getting quota metrics for IoT Hub {0}/{1}: {2}'.format(resource_group, name, str(exc)))
508        return result
509
510    def show_hub_endpoint_health(self, resource_group, name):
511        result = []
512        try:
513            resp = self.IoThub_client.iot_hub_resource.get_endpoint_health(resource_group, name)
514            while True:
515                result.append(resp.next().as_dict())
516        except StopIteration:
517            pass
518        except Exception as exc:
519            self.fail('Failed to getting health for IoT Hub {0}/{1} routing endpoint: {2}'.format(resource_group, name, str(exc)))
520        return result
521
522    def test_all_routes(self, resource_group, name):
523        try:
524            return self.IoThub_client.iot_hub_resource.test_all_routes(self.test_route_message, resource_group, name).routes.as_dict()
525        except Exception as exc:
526            self.fail('Failed to getting statistics for IoT Hub {0}/{1}: {2}'.format(resource_group, name, str(exc)))
527
528    def list_hub_keys(self, resource_group, name):
529        result = []
530        try:
531            resp = self.IoThub_client.iot_hub_resource.list_keys(resource_group, name)
532            while True:
533                result.append(resp.next().as_dict())
534        except StopIteration:
535            pass
536        except Exception as exc:
537            self.fail('Failed to getting health for IoT Hub {0}/{1} routing endpoint: {2}'.format(resource_group, name, str(exc)))
538        return result
539
540    def list_event_hub_consumer_groups(self, resource_group, name, event_hub_endpoint='events'):
541        result = []
542        try:
543            resp = self.IoThub_client.iot_hub_resource.list_event_hub_consumer_groups(resource_group, name, event_hub_endpoint)
544            while True:
545                cg = resp.next()
546                result.append(dict(
547                    id=cg.id,
548                    name=cg.name
549                ))
550        except StopIteration:
551            pass
552        except Exception as exc:
553            self.fail('Failed to listing consumer group for IoT Hub {0}/{1} routing endpoint: {2}'.format(resource_group, name, str(exc)))
554        return result
555
556    def route_to_dict(self, route):
557        return dict(
558            name=route.name,
559            source=_camel_to_snake(route.source),
560            endpoint_name=route.endpoint_names[0],
561            enabled=route.is_enabled,
562            condition=route.condition
563        )
564
565    def instance_dict_to_dict(self, instance_dict):
566        result = dict()
567        for key in instance_dict.keys():
568            result[key] = instance_dict[key].as_dict()
569        return result
570
571    def to_dict(self, hub):
572        result = dict()
573        properties = hub.properties
574        result['id'] = hub.id
575        result['name'] = hub.name
576        result['resource_group'] = parse_resource_id(hub.id).get('resource_group')
577        result['location'] = hub.location
578        result['tags'] = hub.tags
579        result['unit'] = hub.sku.capacity
580        result['sku'] = hub.sku.name.lower()
581        result['cloud_to_device'] = dict(
582            max_delivery_count=properties.cloud_to_device.feedback.max_delivery_count,
583            ttl_as_iso8601=str(properties.cloud_to_device.feedback.ttl_as_iso8601)
584        )
585        result['enable_file_upload_notifications'] = properties.enable_file_upload_notifications
586        result['event_hub_endpoints'] = self.instance_dict_to_dict(properties.event_hub_endpoints)
587        result['host_name'] = properties.host_name
588        result['ip_filters'] = [x.as_dict() for x in properties.ip_filter_rules]
589        result['routing_endpoints'] = properties.routing.endpoints.as_dict()
590        result['routes'] = [self.route_to_dict(x) for x in properties.routing.routes]
591        result['fallback_route'] = self.route_to_dict(properties.routing.fallback_route)
592        result['status'] = properties.state
593        result['storage_endpoints'] = self.instance_dict_to_dict(properties.storage_endpoints)
594
595        # network overhead part
596        if self.show_stats:
597            result['statistics'] = self.show_hub_stats(result['resource_group'], hub.name)
598        if self.show_quota_metrics:
599            result['quota_metrics'] = self.show_hub_quota_metrics(result['resource_group'], hub.name)
600        if self.show_endpoint_health:
601            result['endpoint_health'] = self.show_hub_endpoint_health(result['resource_group'], hub.name)
602        if self.list_keys:
603            result['keys'] = self.list_hub_keys(result['resource_group'], hub.name)
604        if self.test_route_message:
605            result['test_route_result'] = self.test_all_routes(result['resource_group'], hub.name)
606        if self.list_consumer_groups:
607            result['consumer_groups'] = self.list_event_hub_consumer_groups(result['resource_group'], hub.name)
608        return result
609
610
611def main():
612    """Main module execution code path"""
613
614    AzureRMIoTHubFacts()
615
616
617if __name__ == '__main__':
618    main()
619