Last active
January 23, 2018 14:02
-
-
Save felixfontein/af3944b40228f8333a93ad604a5fe878 to your computer and use it in GitHub Desktop.
Using module.run_command() and writing into tempfile (context: https://github.com/ansible/ansible/pull/32620)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85,126c85 | |
< | |
< """Note: We also monkey-patch subprocess for python 2.6 to | |
< give feature parity with later versions. | |
< Monkey-patch from http://pydoc.net/pep8radius/0.9.0/ with slight modifications | |
< MIT Licence | |
< | |
< Remove patch when Python 2.6 is no longer supported | |
< """ | |
< import subprocess | |
< try: | |
< from subprocess import check_output, CalledProcessError | |
< except ImportError: # pragma: no cover | |
< # python 2.6 doesn't include check_output | |
< # monkey patch it in! | |
< def check_output(*popenargs, **kwargs): | |
< if 'stdout' in kwargs: # pragma: no cover | |
< raise ValueError('stdout argument not allowed, ' | |
< 'it will be overridden.') | |
< process = subprocess.Popen(stdout=subprocess.PIPE, | |
< *popenargs, **kwargs) | |
< output = process.communicate()[0] | |
< retcode = process.poll() | |
< if retcode: | |
< cmd = kwargs.get("args") | |
< if cmd is None: | |
< cmd = popenargs[0] | |
< raise CalledProcessError(retcode, cmd, output=output) | |
< return output | |
< | |
< # overwrite CalledProcessError due to `output` | |
< # keyword not being available (in 2.6) | |
< class CalledProcessError(Exception): | |
< """Exception raised for call errors""" | |
< | |
< def __init__(self, returncode, cmd, output=None): | |
< self.returncode = returncode | |
< self.cmd = cmd | |
< self.output = output | |
< | |
< def __str__(self): | |
< return "Command '%s' returned non-zero exit status %d" % ( | |
< self.cmd, self.returncode) | |
--- | |
> import tempfile | |
143a103 | |
> self.openssl_bin = module.get_bin_path('openssl', True) | |
150a111,118 | |
> # create a tempfile | |
> _, tmpsrc = tempfile.mkstemp() | |
> module.add_cleanup_file(tmpsrc) # Ansible will delete the file on exit | |
> # openssl dhparam -out <path> <bits> | |
> command = [self.openssl_bin, 'dhparam', '-out', tmpsrc, str(self.size)] | |
> rc, _, err = module.run_command(command, check_rc=False) | |
> if rc != 0: | |
> raise DHParameterError(to_native(err)) | |
152,159c120,123 | |
< # openssl dhparam -out <path> <bits> | |
< subprocess.check_call( | |
< ["openssl", "dhparam", "-out", self.path, str(self.size)]) | |
< except CalledProcessError as exc: | |
< os.remove(self.path) | |
< raise DHParameterError(str(exc)) | |
< finally: | |
< changed = True | |
--- | |
> module.atomic_move(tmpsrc, self.path) | |
> except Exception as e: | |
> module.fail_json(msg="Failed to write to file %s: %s" % (self.path, str(e))) | |
> changed = True | |
177,182c141,144 | |
< try: | |
< result = to_native(check_output( | |
< ["openssl", "dhparam", "-check", "-text", "-noout", "-in", self.path], | |
< stderr=subprocess.STDOUT, | |
< )).strip() | |
< except CalledProcessError as e: | |
--- | |
> command = [self.openssl_bin, 'dhparam', '-check', '-text', '-noout', '-in', self.path] | |
> rc, out, err = module.run_command(command, check_rc=False) | |
> result = to_native(out) | |
> if rc != 0: | |
194c156 | |
< if "WARNING" in result: | |
--- | |
> if "WARNING" in result or "WARNING" in to_native(err): |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
# (c) 2017, Thom Wiggers <[email protected]> | |
# 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: openssl_dhparam | |
author: "Thom Wiggers (@thomwiggers)" | |
version_added: "2.5" | |
short_description: Generate OpenSSL Diffie-Hellman Parameters | |
description: | |
- "This module allows one to (re)generate OpenSSL DH-params. | |
This module uses file common arguments to specify generated file permissions." | |
requirements: | |
- OpenSSL | |
options: | |
state: | |
required: false | |
default: "present" | |
choices: [ present, absent ] | |
description: | |
- Whether the parameters should exist or not, | |
taking action if the state is different from what is stated. | |
size: | |
required: false | |
default: 4096 | |
description: | |
- Size (in bits) of the generated DH-params | |
force: | |
required: false | |
default: False | |
choices: [ True, False ] | |
description: | |
- Should the parameters be regenerated even it it already exists | |
path: | |
required: true | |
description: | |
- Name of the file in which the generated parameters will be saved. | |
extends_documentation_fragment: files | |
''' | |
EXAMPLES = ''' | |
# Generate Diffie-Hellman parameters with the default size (4096 bits) | |
- openssl_dhparam: | |
path: /etc/ssl/dhparams.pem | |
# Generate DH Parameters with a different size (2048 bits) | |
- openssl_dhparam: | |
path: /etc/ssl/dhparams.pem | |
size: 2048 | |
# Force regenerate an DH parameters if they already exist | |
- openssl_dhparam: | |
path: /etc/ssl/dhparams.pem | |
force: True | |
''' | |
RETURN = ''' | |
size: | |
description: Size (in bits) of the Diffie-Hellman parameters | |
returned: changed or success | |
type: int | |
sample: 4096 | |
filename: | |
description: Path to the generated Diffie-Hellman parameters | |
returned: changed or success | |
type: string | |
sample: /etc/ssl/dhparams.pem | |
''' | |
import os | |
import re | |
import tempfile | |
from ansible.module_utils.basic import AnsibleModule | |
from ansible.module_utils._text import to_native | |
class DHParameterError(Exception): | |
pass | |
class DHParameter(object): | |
def __init__(self, module): | |
self.state = module.params['state'] | |
self.path = module.params['path'] | |
self.size = int(module.params['size']) | |
self.force = module.params['force'] | |
self.changed = False | |
self.openssl_bin = module.get_bin_path('openssl', True) | |
def generate(self, module): | |
"""Generate a keypair.""" | |
changed = False | |
# ony generate when necessary | |
if self.force or not self._check_params_valid(module): | |
# create a tempfile | |
_, tmpsrc = tempfile.mkstemp() | |
module.add_cleanup_file(tmpsrc) # Ansible will delete the file on exit | |
# openssl dhparam -out <path> <bits> | |
command = [self.openssl_bin, 'dhparam', '-out', tmpsrc, str(self.size)] | |
rc, _, err = module.run_command(command, check_rc=False) | |
if rc != 0: | |
raise DHParameterError(to_native(err)) | |
try: | |
module.atomic_move(tmpsrc, self.path) | |
except Exception as e: | |
module.fail_json(msg="Failed to write to file %s: %s" % (self.path, str(e))) | |
changed = True | |
# fix permissions (checking force not necessary as done above) | |
if not self._check_fs_attributes(module): | |
# Fix done implicitly by | |
# AnsibleModule.set_fs_attributes_if_different | |
changed = True | |
self.changed = changed | |
def check(self, module): | |
"""Ensure the resource is in its desired state.""" | |
if self.force: | |
return False | |
return self._check_params_valid(module) and self._check_fs_attributes(module) | |
def _check_params_valid(self, module): | |
"""Check if the params are in the correct state""" | |
command = [self.openssl_bin, 'dhparam', '-check', '-text', '-noout', '-in', self.path] | |
rc, out, err = module.run_command(command, check_rc=False) | |
result = to_native(out) | |
if rc != 0: | |
# If the call failed the file probably doesn't exist or is | |
# unreadable | |
return False | |
# output contains "(xxxx bit)" | |
match = re.search(r"Parameters:\s+\((\d+) bit\).*", result) | |
if not match: | |
return False # No "xxxx bit" in output | |
else: | |
bits = int(match.group(1)) | |
# if output contains "WARNING" we've got a problem | |
if "WARNING" in result or "WARNING" in to_native(err): | |
return False | |
return bits == self.size | |
def _check_fs_attributes(self, module): | |
"""Checks (and changes if not in check mode!) fs attributes""" | |
file_args = module.load_file_common_arguments(module.params) | |
attrs_changed = module.set_fs_attributes_if_different(file_args, False) | |
return not attrs_changed | |
def dump(self): | |
"""Serialize the object into a dictionary.""" | |
result = { | |
'size': self.size, | |
'filename': self.path, | |
'changed': self.changed, | |
} | |
return result | |
def main(): | |
"""Main function""" | |
module = AnsibleModule( | |
argument_spec=dict( | |
state=dict(default='present', choices=['present', 'absent'], type='str'), | |
size=dict(default=4096, type='int'), | |
force=dict(default=False, type='bool'), | |
path=dict(required=True, type='path'), | |
), | |
supports_check_mode=True, | |
add_file_common_args=True, | |
) | |
base_dir = os.path.dirname(module.params['path']) | |
if not os.path.isdir(base_dir): | |
module.fail_json( | |
name=base_dir, | |
msg='The directory %s does not exist or the file is not a directory' % base_dir | |
) | |
dhparam = DHParameter(module) | |
if dhparam.state == 'present': | |
if module.check_mode: | |
result = dhparam.dump() | |
result['changed'] = module.params['force'] or not dhparam.check(module) | |
module.exit_json(**result) | |
try: | |
dhparam.generate(module) | |
except DHParameterError as exc: | |
module.fail_json(msg=to_native(exc)) | |
else: | |
if module.check_mode: | |
result = dhparam.dump() | |
result['changed'] = os.path.exists(module.params['path']) | |
module.exit_json(**result) | |
try: | |
os.remove(module.params['path']) | |
except OSError as exc: | |
module.fail_json(msg=to_native(exc)) | |
result = dhparam.dump() | |
module.exit_json(**result) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment