mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-02-04 16:01:55 +00:00
Sort imports with ruff check --fix (#11400)
Sort imports with ruff check --fix.
(cherry picked from commit 236b9c0e04)
Co-authored-by: Felix Fontein <felix@fontein.de>
425 lines
15 KiB
Python
425 lines
15 KiB
Python
#!/usr/bin/python
|
|
|
|
# Copyright (c) 2012, Dag Wieers (@dagwieers) <dag@wieers.com>
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from __future__ import annotations
|
|
|
|
DOCUMENTATION = r"""
|
|
author:
|
|
- Dag Wieers (@dagwieers)
|
|
module: mail
|
|
short_description: Send an email
|
|
description:
|
|
- This module is useful for sending emails from playbooks.
|
|
- One may wonder why automate sending emails? In complex environments there are from time to time processes that cannot
|
|
be automated, either because you lack the authority to make it so, or because not everyone agrees to a common approach.
|
|
- If you cannot automate a specific step, but the step is non-blocking, sending out an email to the responsible party to
|
|
make them perform their part of the bargain is an elegant way to put the responsibility in someone else's lap.
|
|
- Of course sending out a mail can be equally useful as a way to notify one or more people in a team that a specific action
|
|
has been (successfully) taken.
|
|
extends_documentation_fragment:
|
|
- community.general.attributes
|
|
attributes:
|
|
check_mode:
|
|
support: none
|
|
diff_mode:
|
|
support: none
|
|
options:
|
|
sender:
|
|
description:
|
|
- The email-address the mail is sent from. May contain address and phrase.
|
|
type: str
|
|
default: root
|
|
aliases: [from]
|
|
to:
|
|
description:
|
|
- The email-address(es) the mail is being sent to.
|
|
- This is a list, which may contain address and phrase portions.
|
|
type: list
|
|
elements: str
|
|
default: root
|
|
aliases: [recipients]
|
|
cc:
|
|
description:
|
|
- The email-address(es) the mail is being copied to.
|
|
- This is a list, which may contain address and phrase portions.
|
|
type: list
|
|
elements: str
|
|
default: []
|
|
bcc:
|
|
description:
|
|
- The email-address(es) the mail is being 'blind' copied to.
|
|
- This is a list, which may contain address and phrase portions.
|
|
type: list
|
|
elements: str
|
|
default: []
|
|
subject:
|
|
description:
|
|
- The subject of the email being sent.
|
|
required: true
|
|
type: str
|
|
aliases: [msg]
|
|
body:
|
|
description:
|
|
- The body of the email being sent.
|
|
type: str
|
|
username:
|
|
description:
|
|
- If SMTP requires username.
|
|
type: str
|
|
password:
|
|
description:
|
|
- If SMTP requires password.
|
|
type: str
|
|
host:
|
|
description:
|
|
- The mail server.
|
|
type: str
|
|
default: localhost
|
|
port:
|
|
description:
|
|
- The mail server port.
|
|
- This must be a valid integer between V(1) and V(65534).
|
|
type: int
|
|
default: 25
|
|
attach:
|
|
description:
|
|
- A list of pathnames of files to attach to the message.
|
|
- Attached files have their content-type set to C(application/octet-stream).
|
|
type: list
|
|
elements: path
|
|
default: []
|
|
headers:
|
|
description:
|
|
- A list of headers which should be added to the message.
|
|
- Each individual header is specified as V(header=value) (see example below).
|
|
type: list
|
|
elements: str
|
|
default: []
|
|
charset:
|
|
description:
|
|
- The character set of email being sent.
|
|
type: str
|
|
default: utf-8
|
|
subtype:
|
|
description:
|
|
- The minor mime type, can be either V(plain) or V(html).
|
|
- The major type is always V(text).
|
|
type: str
|
|
choices: [html, plain]
|
|
default: plain
|
|
secure:
|
|
description:
|
|
- If V(always), the connection only sends email if the connection is Encrypted. If the server does not accept the encrypted
|
|
connection it fails.
|
|
- If V(try), the connection attempts to setup a secure SSL/TLS session, before trying to send.
|
|
- If V(never), the connection does not attempt to setup a secure SSL/TLS session, before sending.
|
|
- If V(starttls), the connection tries to upgrade to a secure SSL/TLS connection, before sending. If it is unable to
|
|
do so it fails.
|
|
type: str
|
|
choices: [always, never, starttls, try]
|
|
default: try
|
|
timeout:
|
|
description:
|
|
- Sets the timeout in seconds for connection attempts.
|
|
type: int
|
|
default: 20
|
|
ehlohost:
|
|
description:
|
|
- Allows for manual specification of host for EHLO.
|
|
type: str
|
|
version_added: 3.8.0
|
|
message_id_domain:
|
|
description:
|
|
- The domain name to use for the L(Message-ID header, https://en.wikipedia.org/wiki/Message-ID).
|
|
type: str
|
|
default: ansible
|
|
version_added: 8.2.0
|
|
"""
|
|
|
|
EXAMPLES = r"""
|
|
- name: Example playbook sending mail to root
|
|
community.general.mail:
|
|
subject: System {{ ansible_hostname }} has been successfully provisioned.
|
|
delegate_to: localhost
|
|
|
|
- name: Sending an e-mail using Gmail SMTP servers
|
|
community.general.mail:
|
|
host: smtp.gmail.com
|
|
port: 587
|
|
username: username@gmail.com
|
|
password: mysecret
|
|
to: John Smith <john.smith@example.com>
|
|
subject: Ansible-report
|
|
body: System {{ ansible_hostname }} has been successfully provisioned.
|
|
delegate_to: localhost
|
|
|
|
- name: Send e-mail to a bunch of users, attaching files
|
|
community.general.mail:
|
|
host: 127.0.0.1
|
|
port: 2025
|
|
subject: Ansible-report
|
|
body: Hello, this is an e-mail. I hope you like it ;-)
|
|
from: jane@example.net (Jane Jolie)
|
|
to:
|
|
- John Doe <j.d@example.org>
|
|
- Suzie Something <sue@example.com>
|
|
cc: Charlie Root <root@localhost>
|
|
attach:
|
|
- /etc/group
|
|
- /tmp/avatar2.png
|
|
headers:
|
|
- Reply-To=john@example.com
|
|
- X-Special="Something or other"
|
|
charset: us-ascii
|
|
delegate_to: localhost
|
|
|
|
- name: Sending an e-mail using the remote machine, not the Ansible controller node
|
|
community.general.mail:
|
|
host: localhost
|
|
port: 25
|
|
to: John Smith <john.smith@example.com>
|
|
subject: Ansible-report
|
|
body: System {{ ansible_hostname }} has been successfully provisioned.
|
|
|
|
- name: Sending an e-mail using Legacy SSL to the remote machine
|
|
community.general.mail:
|
|
host: localhost
|
|
port: 25
|
|
to: John Smith <john.smith@example.com>
|
|
subject: Ansible-report
|
|
body: System {{ ansible_hostname }} has been successfully provisioned.
|
|
secure: always
|
|
|
|
- name: Sending an e-mail using StartTLS to the remote machine
|
|
community.general.mail:
|
|
host: localhost
|
|
port: 25
|
|
to: John Smith <john.smith@example.com>
|
|
subject: Ansible-report
|
|
body: System {{ ansible_hostname }} has been successfully provisioned.
|
|
secure: starttls
|
|
|
|
- name: Sending an e-mail using StartTLS, remote server, custom EHLO, and timeout of 10 seconds
|
|
community.general.mail:
|
|
host: some.smtp.host.tld
|
|
port: 25
|
|
timeout: 10
|
|
ehlohost: my-resolvable-hostname.tld
|
|
to: John Smith <john.smith@example.com>
|
|
subject: Ansible-report
|
|
body: System {{ ansible_hostname }} has been successfully provisioned.
|
|
secure: starttls
|
|
"""
|
|
|
|
import os
|
|
import smtplib
|
|
import ssl
|
|
import traceback
|
|
from email import encoders
|
|
from email.header import Header
|
|
from email.mime.base import MIMEBase
|
|
from email.mime.multipart import MIMEMultipart
|
|
from email.mime.text import MIMEText
|
|
from email.utils import formataddr, formatdate, make_msgid, parseaddr
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.common.text.converters import to_native
|
|
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec=dict(
|
|
username=dict(type="str"),
|
|
password=dict(type="str", no_log=True),
|
|
host=dict(type="str", default="localhost"),
|
|
port=dict(type="int", default=25),
|
|
ehlohost=dict(type="str"),
|
|
sender=dict(type="str", default="root", aliases=["from"]),
|
|
to=dict(type="list", elements="str", default=["root"], aliases=["recipients"]),
|
|
cc=dict(type="list", elements="str", default=[]),
|
|
bcc=dict(type="list", elements="str", default=[]),
|
|
subject=dict(type="str", required=True, aliases=["msg"]),
|
|
body=dict(type="str"),
|
|
attach=dict(type="list", elements="path", default=[]),
|
|
headers=dict(type="list", elements="str", default=[]),
|
|
charset=dict(type="str", default="utf-8"),
|
|
subtype=dict(type="str", default="plain", choices=["html", "plain"]),
|
|
secure=dict(type="str", default="try", choices=["always", "never", "starttls", "try"]),
|
|
timeout=dict(type="int", default=20),
|
|
message_id_domain=dict(type="str", default="ansible"),
|
|
),
|
|
required_together=[["password", "username"]],
|
|
)
|
|
|
|
username = module.params.get("username")
|
|
password = module.params.get("password")
|
|
host = module.params.get("host")
|
|
port = module.params.get("port")
|
|
local_hostname = module.params.get("ehlohost")
|
|
sender = module.params.get("sender")
|
|
recipients = module.params.get("to")
|
|
copies = module.params.get("cc")
|
|
blindcopies = module.params.get("bcc")
|
|
subject = module.params.get("subject")
|
|
body = module.params.get("body")
|
|
attach_files = module.params.get("attach")
|
|
headers = module.params.get("headers")
|
|
charset = module.params.get("charset")
|
|
subtype = module.params.get("subtype")
|
|
secure = module.params.get("secure")
|
|
timeout = module.params.get("timeout")
|
|
message_id_domain = module.params["message_id_domain"]
|
|
|
|
code = 0
|
|
secure_state = False
|
|
sender_phrase, sender_addr = parseaddr(sender)
|
|
|
|
if not body:
|
|
body = subject
|
|
|
|
try:
|
|
if secure != "never":
|
|
try:
|
|
smtp = smtplib.SMTP_SSL(host=host, port=port, local_hostname=local_hostname, timeout=timeout)
|
|
code, smtpmessage = smtp.connect(host, port)
|
|
secure_state = True
|
|
except ssl.SSLError as e:
|
|
if secure == "always":
|
|
module.fail_json(
|
|
rc=1,
|
|
msg=f"Unable to start an encrypted session to {host}:{port}: {e}",
|
|
exception=traceback.format_exc(),
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
if not secure_state:
|
|
smtp = smtplib.SMTP(host=host, port=port, local_hostname=local_hostname, timeout=timeout)
|
|
code, smtpmessage = smtp.connect(host, port)
|
|
|
|
except smtplib.SMTPException as e:
|
|
module.fail_json(rc=1, msg=f"Unable to Connect {host}:{port}: {e}", exception=traceback.format_exc())
|
|
|
|
try:
|
|
smtp.ehlo()
|
|
except smtplib.SMTPException as e:
|
|
module.fail_json(rc=1, msg=f"Helo failed for host {host}:{port}: {e}", exception=traceback.format_exc())
|
|
|
|
if int(code) > 0:
|
|
if not secure_state and secure in ("starttls", "try"):
|
|
if smtp.has_extn("STARTTLS"):
|
|
try:
|
|
smtp.starttls()
|
|
secure_state = True
|
|
except smtplib.SMTPException as e:
|
|
module.fail_json(
|
|
rc=1,
|
|
msg=f"Unable to start an encrypted session to {host}:{port}: {e}",
|
|
exception=traceback.format_exc(),
|
|
)
|
|
try:
|
|
smtp.ehlo()
|
|
except smtplib.SMTPException as e:
|
|
module.fail_json(
|
|
rc=1, msg=f"Helo failed for host {host}:{port}: {e}", exception=traceback.format_exc()
|
|
)
|
|
else:
|
|
if secure == "starttls":
|
|
module.fail_json(rc=1, msg=f"StartTLS is not offered on server {host}:{port}")
|
|
|
|
if username and password:
|
|
if smtp.has_extn("AUTH"):
|
|
try:
|
|
smtp.login(username, password)
|
|
except smtplib.SMTPAuthenticationError:
|
|
module.fail_json(
|
|
rc=1, msg=f"Authentication to {host}:{port} failed, please check your username and/or password"
|
|
)
|
|
except smtplib.SMTPException:
|
|
module.fail_json(rc=1, msg=f"No Suitable authentication method was found on {host}:{port}")
|
|
else:
|
|
module.fail_json(rc=1, msg=f"No Authentication on the server at {host}:{port}")
|
|
|
|
if not secure_state and (username and password):
|
|
module.warn("Username and Password was sent without encryption")
|
|
|
|
msg = MIMEMultipart(_charset=charset)
|
|
msg["From"] = formataddr((sender_phrase, sender_addr))
|
|
msg["Date"] = formatdate(localtime=True)
|
|
msg["Subject"] = Header(subject, charset)
|
|
msg["Message-ID"] = make_msgid(domain=message_id_domain)
|
|
msg.preamble = "Multipart message"
|
|
|
|
for header in headers:
|
|
# NOTE: Backward compatible with old syntax using '|' as delimiter
|
|
for hdr in [x.strip() for x in header.split("|")]:
|
|
try:
|
|
h_key, h_val = hdr.split("=", 1)
|
|
h_val = to_native(Header(h_val, charset)) # TODO: use str() instead?
|
|
msg.add_header(h_key, h_val)
|
|
except Exception:
|
|
module.warn(f"Skipping header '{hdr}', unable to parse")
|
|
|
|
if "X-Mailer" not in msg:
|
|
msg.add_header("X-Mailer", "Ansible mail module")
|
|
|
|
addr_list = []
|
|
for addr in [x.strip() for x in blindcopies]:
|
|
addr_list.append(parseaddr(addr)[1]) # address only, w/o phrase
|
|
|
|
to_list = []
|
|
for addr in [x.strip() for x in recipients]:
|
|
to_list.append(formataddr(parseaddr(addr)))
|
|
addr_list.append(parseaddr(addr)[1]) # address only, w/o phrase
|
|
msg["To"] = ", ".join(to_list)
|
|
|
|
cc_list = []
|
|
for addr in [x.strip() for x in copies]:
|
|
cc_list.append(formataddr(parseaddr(addr)))
|
|
addr_list.append(parseaddr(addr)[1]) # address only, w/o phrase
|
|
msg["Cc"] = ", ".join(cc_list)
|
|
|
|
part = MIMEText(f"{body}\n\n", _subtype=subtype, _charset=charset)
|
|
msg.attach(part)
|
|
|
|
# NOTE: Backward compatibility with old syntax using space as delimiter is not retained
|
|
# This breaks files with spaces in it :-(
|
|
for filename in attach_files:
|
|
try:
|
|
part = MIMEBase("application", "octet-stream")
|
|
with open(filename, "rb") as fp:
|
|
part.set_payload(fp.read())
|
|
encoders.encode_base64(part)
|
|
part.add_header("Content-disposition", "attachment", filename=os.path.basename(filename))
|
|
msg.attach(part)
|
|
except Exception as e:
|
|
module.fail_json(
|
|
rc=1,
|
|
msg=f"Failed to send community.general.mail: can't attach file {filename}: {e}",
|
|
exception=traceback.format_exc(),
|
|
)
|
|
|
|
composed = msg.as_string()
|
|
|
|
try:
|
|
result = smtp.sendmail(sender_addr, set(addr_list), composed)
|
|
except Exception as e:
|
|
module.fail_json(
|
|
rc=1, msg=f"Failed to send mail to '{', '.join(set(addr_list))}': {e}", exception=traceback.format_exc()
|
|
)
|
|
|
|
smtp.quit()
|
|
|
|
if result:
|
|
for key in result:
|
|
module.warn(f"Failed to send mail to '{key}': {result[key][0]} {result[key][1]}")
|
|
module.exit_json(msg="Failed to send mail to at least one recipient", result=result)
|
|
|
|
module.exit_json(msg="Mail sent successfully", result=result)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|