1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# (c) 2013, 2014, Jan-Piet Mens <jpmens () gmail.com> 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10 11DOCUMENTATION = ''' 12--- 13module: mqtt 14short_description: Publish a message on an MQTT topic for the IoT 15description: 16 - Publish a message on an MQTT topic. 17options: 18 server: 19 type: str 20 description: 21 - MQTT broker address/name 22 default: localhost 23 port: 24 type: int 25 description: 26 - MQTT broker port number 27 default: 1883 28 username: 29 type: str 30 description: 31 - Username to authenticate against the broker. 32 password: 33 type: str 34 description: 35 - Password for C(username) to authenticate against the broker. 36 client_id: 37 type: str 38 description: 39 - MQTT client identifier 40 - If not specified, a value C(hostname + pid) will be used. 41 topic: 42 type: str 43 description: 44 - MQTT topic name 45 required: true 46 payload: 47 type: str 48 description: 49 - Payload. The special string C("None") may be used to send a NULL 50 (i.e. empty) payload which is useful to simply notify with the I(topic) 51 or to clear previously retained messages. 52 required: true 53 qos: 54 type: str 55 description: 56 - QoS (Quality of Service) 57 default: "0" 58 choices: [ "0", "1", "2" ] 59 retain: 60 description: 61 - Setting this flag causes the broker to retain (i.e. keep) the message so that 62 applications that subsequently subscribe to the topic can received the last 63 retained message immediately. 64 type: bool 65 default: 'no' 66 ca_cert: 67 type: path 68 description: 69 - The path to the Certificate Authority certificate files that are to be 70 treated as trusted by this client. If this is the only option given 71 then the client will operate in a similar manner to a web browser. That 72 is to say it will require the broker to have a certificate signed by the 73 Certificate Authorities in ca_certs and will communicate using TLS v1, 74 but will not attempt any form of authentication. This provides basic 75 network encryption but may not be sufficient depending on how the broker 76 is configured. 77 aliases: [ ca_certs ] 78 client_cert: 79 type: path 80 description: 81 - The path pointing to the PEM encoded client certificate. If this is not 82 None it will be used as client information for TLS based 83 authentication. Support for this feature is broker dependent. 84 aliases: [ certfile ] 85 client_key: 86 type: path 87 description: 88 - The path pointing to the PEM encoded client private key. If this is not 89 None it will be used as client information for TLS based 90 authentication. Support for this feature is broker dependent. 91 aliases: [ keyfile ] 92 tls_version: 93 description: 94 - Specifies the version of the SSL/TLS protocol to be used. 95 - By default (if the python version supports it) the highest TLS version is 96 detected. If unavailable, TLS v1 is used. 97 type: str 98 choices: 99 - tlsv1.1 100 - tlsv1.2 101requirements: [ mosquitto ] 102notes: 103 - This module requires a connection to an MQTT broker such as Mosquitto 104 U(http://mosquitto.org) and the I(Paho) C(mqtt) Python client (U(https://pypi.org/project/paho-mqtt/)). 105author: "Jan-Piet Mens (@jpmens)" 106''' 107 108EXAMPLES = ''' 109- name: Publish a message on an MQTT topic 110 community.general.mqtt: 111 topic: 'service/ansible/{{ ansible_hostname }}' 112 payload: 'Hello at {{ ansible_date_time.iso8601 }}' 113 qos: 0 114 retain: False 115 client_id: ans001 116 delegate_to: localhost 117''' 118 119# =========================================== 120# MQTT module support methods. 121# 122 123import os 124import ssl 125import traceback 126import platform 127from distutils.version import LooseVersion 128 129HAS_PAHOMQTT = True 130PAHOMQTT_IMP_ERR = None 131try: 132 import socket 133 import paho.mqtt.publish as mqtt 134except ImportError: 135 PAHOMQTT_IMP_ERR = traceback.format_exc() 136 HAS_PAHOMQTT = False 137 138from ansible.module_utils.basic import AnsibleModule, missing_required_lib 139from ansible.module_utils.common.text.converters import to_native 140 141 142# =========================================== 143# Main 144# 145 146def main(): 147 tls_map = {} 148 149 try: 150 tls_map['tlsv1.2'] = ssl.PROTOCOL_TLSv1_2 151 except AttributeError: 152 pass 153 154 try: 155 tls_map['tlsv1.1'] = ssl.PROTOCOL_TLSv1_1 156 except AttributeError: 157 pass 158 159 module = AnsibleModule( 160 argument_spec=dict( 161 server=dict(default='localhost'), 162 port=dict(default=1883, type='int'), 163 topic=dict(required=True), 164 payload=dict(required=True), 165 client_id=dict(default=None), 166 qos=dict(default="0", choices=["0", "1", "2"]), 167 retain=dict(default=False, type='bool'), 168 username=dict(default=None), 169 password=dict(default=None, no_log=True), 170 ca_cert=dict(default=None, type='path', aliases=['ca_certs']), 171 client_cert=dict(default=None, type='path', aliases=['certfile']), 172 client_key=dict(default=None, type='path', aliases=['keyfile']), 173 tls_version=dict(default=None, choices=['tlsv1.1', 'tlsv1.2']) 174 ), 175 supports_check_mode=True 176 ) 177 178 if not HAS_PAHOMQTT: 179 module.fail_json(msg=missing_required_lib('paho-mqtt'), exception=PAHOMQTT_IMP_ERR) 180 181 server = module.params.get("server", 'localhost') 182 port = module.params.get("port", 1883) 183 topic = module.params.get("topic") 184 payload = module.params.get("payload") 185 client_id = module.params.get("client_id", '') 186 qos = int(module.params.get("qos", 0)) 187 retain = module.params.get("retain") 188 username = module.params.get("username", None) 189 password = module.params.get("password", None) 190 ca_certs = module.params.get("ca_cert", None) 191 certfile = module.params.get("client_cert", None) 192 keyfile = module.params.get("client_key", None) 193 tls_version = module.params.get("tls_version", None) 194 195 if client_id is None: 196 client_id = "%s_%s" % (socket.getfqdn(), os.getpid()) 197 198 if payload and payload == 'None': 199 payload = None 200 201 auth = None 202 if username is not None: 203 auth = {'username': username, 'password': password} 204 205 tls = None 206 if ca_certs is not None: 207 if tls_version: 208 tls_version = tls_map.get(tls_version, ssl.PROTOCOL_SSLv23) 209 else: 210 if LooseVersion(platform.python_version()) <= "3.5.2": 211 # Specifying `None` on later versions of python seems sufficient to 212 # instruct python to autonegotiate the SSL/TLS connection. On versions 213 # 3.5.2 and lower though we need to specify the version. 214 # 215 # Note that this is an alias for PROTOCOL_TLS, but PROTOCOL_TLS was 216 # not available until 3.5.3. 217 tls_version = ssl.PROTOCOL_SSLv23 218 219 tls = { 220 'ca_certs': ca_certs, 221 'certfile': certfile, 222 'keyfile': keyfile, 223 'tls_version': tls_version, 224 } 225 226 try: 227 mqtt.single( 228 topic, 229 payload, 230 qos=qos, 231 retain=retain, 232 client_id=client_id, 233 hostname=server, 234 port=port, 235 auth=auth, 236 tls=tls 237 ) 238 except Exception as e: 239 module.fail_json( 240 msg="unable to publish to MQTT broker %s" % to_native(e), 241 exception=traceback.format_exc() 242 ) 243 244 module.exit_json(changed=False, topic=topic) 245 246 247if __name__ == '__main__': 248 main() 249