-
-
Save mgedmin/5f8ac034df0c371444be to your computer and use it in GitHub Desktop.
#!/usr/bin/python | |
import subprocess | |
DOCUMENTATION = ''' | |
--- | |
module: postfix | |
short_description: changes postfix configuration parameters | |
description: | |
- The M(postfix) module changes postfix configuration by invoking 'postconf'. | |
This is needed if you don't want to use M(template) for the entire main.cf, | |
because M(lineinfile) cannot handle multi-line configuration values, and | |
solutions involving M(command) are cumbersone or don't work correctly | |
in check mode. | |
- Be sure to run C(postfix reload) (or, for settings like inet_interfaces, | |
C(service postfix restart)) afterwards. | |
options: | |
name: | |
description: | |
- the name of the setting | |
required: true | |
default: null | |
value: | |
description: | |
- the value for that setting | |
required: true | |
default: null | |
author: | |
- Marius Gedminas <[email protected]> | |
''' | |
EXAMPLES = ''' | |
- postfix: name=myhostname value={{ ansible_fqdn }} | |
- postfix: name=mynetworks value="127.0.0.0/8, [::1]/128, 192.168.1.0/24" | |
- postfix: name={{ item.name }} value="{{ item.value }}" | |
with_items: | |
- { name: inet_interfaces, value: loopback-only } | |
- { name: inet_protocols, value: ipv4 } | |
''' | |
def run(args, module): | |
try: | |
cmd = subprocess.Popen(args, stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE) | |
out, err = cmd.communicate() | |
rc = cmd.returncode | |
except (OSError, IOError) as e: | |
module.fail_json(rc=e.errno, msg=str(e), cmd=args) | |
if rc != 0 or err: | |
module.fail_json(rc=rc, msg=err, cmd=args) | |
if not isinstance(out, str): | |
out = out.decode('UTF-8') | |
return out | |
def main(): | |
module = AnsibleModule( | |
argument_spec=dict( | |
name=dict(type='str', required=True), | |
value=dict(type='str', required=True), | |
), | |
supports_check_mode=True, | |
) | |
name = module.params['name'] | |
value = ' '.join(module.params['value'].split()) | |
old_value = run(['postconf', '-h', name], module).strip() | |
if value == old_value: | |
module.exit_json( | |
msg="", | |
changed=False, | |
) | |
if not module.check_mode: | |
run(['postconf', '{}={}'.format(name, value)], module) | |
module.exit_json( | |
msg="setting changed", | |
diff=dict( | |
before_header='postconf -h {}'.format(name), | |
after_header='postconf -h {}'.format(name), | |
before=old_value + '\n', | |
after=value + '\n'), | |
changed=True, | |
) | |
from ansible.module_utils.basic import * # noqa | |
if __name__ == '__main__': | |
main() |
Thanks for sharing this, it really is annoying that this is not part of ansible-core. Still works running Debian Buster provided Ansible 2.7.7.
Thanks a lot for this module, saved me a bunch of work! Only getting a small exception running it:
Resolved (see comment below), expand for details
Traceback (most recent call last):
File \"/root/.ansible/tmp/ansible-tmp-1620216737.1389186-12444-250592887051818/AnsiballZ_postfix.py\", line 102, in <module>
_ansiballz_main()
File \"/root/.ansible/tmp/ansible-tmp-1620216737.1389186-12444-250592887051818/AnsiballZ_postfix.py\", line 94, in _ansiballz_main
invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
File \"/root/.ansible/tmp/ansible-tmp-1620216737.1389186-12444-250592887051818/AnsiballZ_postfix.py\", line 40, in invoke_module
runpy.run_module(mod_name='ansible.modules.postfix', init_globals=None, run_name='__main__', alter_sys=True)
File \"/usr/lib/python3.8/runpy.py\", line 207, in run_module
return _run_module_code(code, init_globals, run_name, mod_spec)
File \"/usr/lib/python3.8/runpy.py\", line 97, in _run_module_code
_run_code(code, mod_globals, init_globals,
File \"/usr/lib/python3.8/runpy.py\", line 87, in _run_code
exec(code, run_globals)
File \"/tmp/ansible_postfix_payload_8ia_r6da/ansible_postfix_payload.zip/ansible/modules/postfix.py\", line 89, in <module>
File \"/tmp/ansible_postfix_payload_8ia_r6da/ansible_postfix_payload.zip/ansible/modules/postfix.py\", line 79, in main
TypeError: can't concat str to bytes
I think this is caused by some changes in subprocess
in Python3.6/3.8. It returns bytes instead of strings by default now:
If encoding or errors are specified, or text is true, the file objects stdin, stdout and stderr are opened in text mode with the specified encoding and errors, as described above in Frequently Used Arguments. The universal_newlines argument is equivalent to text and is provided for backwards compatibility. By default, file objects are opened in binary mode.
- New in version 3.6: encoding and errors were added.
- New in version 3.7: text was added as a more readable alias for universal_newlines.
https://docs.python.org/3/library/subprocess.html#popen-constructor
It was easy enough to fix by setting text=True
in the subprocess.Popen()
call:
diff --git a/library/postfix.py b/library/postfix.py
index ba95d12..7ccfc1a 100644
--- a/library/postfix.py
+++ b/library/postfix.py
@@ -43,7 +43,7 @@ EXAMPLES = '''
def run(args, module):
try:
- cmd = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ cmd = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
out, err = cmd.communicate()
rc = cmd.returncode
except (OSError, IOError) as e:
My environment:
[ansible-host] $ ansible --version
ansible 2.10.5
python version = 3.9.4 (default, Apr 4 2021, 19:38:44) [GCC 10.2.1 20210401]
[target-server] $ python3 --version
Python 3.8.5
Ahh, yes, sorry, I've fixed this in my private git repo but forgot to update the gist. I've pushed the latest version that works for me now, it should support both Python 2 and Python 3 without relying on subprocess.Popen() having a text
argument.
You can drop this into
library/postfix.py
and then use it like this: