diff --git a/lib/ansible/modules/network/f5/bigip_ssl_certificate.py b/lib/ansible/modules/network/f5/bigip_ssl_certificate.py
index b1536ae9eeb..4e808002bb5 100644
--- a/lib/ansible/modules/network/f5/bigip_ssl_certificate.py
+++ b/lib/ansible/modules/network/f5/bigip_ssl_certificate.py
@@ -1,6 +1,6 @@
#!/usr/bin/python
#
-# (c) 2016, Kevin Coming (@waffie1)
+# Copyright 2017 F5 Networks Inc.
#
# This file is part of Ansible
#
@@ -17,14 +17,15 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see .
-ANSIBLE_METADATA = {'metadata_version': '1.0',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
+ANSIBLE_METADATA = {
+ 'status': ['preview'],
+ 'supported_by': 'community',
+ 'metadata_version': '1.0'
+}
DOCUMENTATION = '''
module: bigip_ssl_certificate
-short_description: Import/Delete certificates from BIG-IP
+short_description: Import/Delete certificates from BIG-IP.
description:
- This module will import/delete SSL certificates on BIG-IP LTM.
Certificates can be imported from certificate and key files on the local
@@ -38,7 +39,6 @@ options:
with formatting or templating. Either one of C(key_src),
C(key_content), C(cert_src) or C(cert_content) must be provided when
C(state) is C(present).
- required: false
key_content:
description:
- When used instead of 'key_src', sets the contents of a certificate key
@@ -46,57 +46,47 @@ options:
anything with formatting or templating. Either one of C(key_src),
C(key_content), C(cert_src) or C(cert_content) must be provided when
C(state) is C(present).
- required: false
state:
description:
- Certificate and key state. This determines if the provided certificate
and key is to be made C(present) on the device or C(absent).
- required: true
default: present
choices:
- present
- absent
- partition:
- description:
- - BIG-IP partition to use when adding/deleting certificate.
- required: false
- default: Common
name:
description:
- SSL Certificate Name. This is the cert/key pair name used
when importing a certificate/key into the F5. It also
determines the filenames of the objects on the LTM
(:Partition:name.cer_11111_1 and :Partition_name.key_11111_1).
- required: true
+ required: True
cert_src:
description:
- This is the local filename of the certificate. Either one of C(key_src),
C(key_content), C(cert_src) or C(cert_content) must be provided when
C(state) is C(present).
- required: false
key_src:
description:
- This is the local filename of the private key. Either one of C(key_src),
C(key_content), C(cert_src) or C(cert_content) must be provided when
C(state) is C(present).
- required: false
passphrase:
description:
- Passphrase on certificate private key
- required: false
notes:
- Requires the f5-sdk Python package on the host. This is as easy as pip
install f5-sdk.
- - Requires the netaddr Python package on the host.
- - If you use this module, you will not be able to remove the certificates
- and keys that are managed, via the web UI. You can only remove them via
- tmsh or these modules.
+ - This module does not behave like other modules that you might include in
+ roles where referencing files or templates first looks in the role's
+ files or templates directory. To have it behave that way, use the Ansible
+ file or template lookup (see Examples). The lookups behave as expected in
+ a role context.
extends_documentation_fragment: f5
requirements:
- f5-sdk >= 1.5.0
- - BigIP >= v12
+ - BIG-IP >= v12
author:
- - Kevin Coming (@waffie1)
- Tim Rupp (@caphrim007)
'''
@@ -135,279 +125,263 @@ EXAMPLES = '''
RETURN = '''
cert_name:
- description: >
- The name of the SSL certificate. The C(cert_name) and
- C(key_name) will be equal to each other.
- returned: created, changed or deleted
+ description: The name of the certificate that the user provided
+ returned: created
type: string
sample: "cert1"
-key_name:
- description: >
- The name of the SSL certificate key. The C(key_name) and
- C(cert_name) will be equal to each other.
- returned: created, changed or deleted
+key_filename:
+ description:
+ - The name of the SSL certificate key. The C(key_filename) and
+ C(cert_filename) will be similar to each other, however the
+ C(key_filename) will have a C(.key) extension.
+ returned: created
type: string
- sample: "key1"
-partition:
- description: Partition in which the cert/key was created
- returned: created, changed or deleted
- type: string
- sample: "Common"
+ sample: "cert1.key"
key_checksum:
- description: SHA1 checksum of the key that was provided
- returned: created or changed
+ description: SHA1 checksum of the key that was provided.
+ returned: changed and created
type: string
sample: "cf23df2207d99a74fbe169e3eba035e633b65d94"
+key_source_path:
+ description: Path on BIG-IP where the source of the key is stored
+ returned: created
+ type: string
+ sample: "/var/config/rest/downloads/cert1.key"
+cert_filename:
+ description:
+ - The name of the SSL certificate. The C(cert_filename) and
+ C(key_filename) will be similar to each other, however the
+ C(cert_filename) will have a C(.crt) extension.
+ returned: created
+ type: string
+ sample: "cert1.crt"
cert_checksum:
- description: SHA1 checksum of the cert that was provided
- returned: created or changed
+ description: SHA1 checksum of the cert that was provided.
+ returned: changed and created
type: string
sample: "f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0"
+cert_source_path:
+ description: Path on BIG-IP where the source of the certificate is stored.
+ returned: created
+ type: string
+ sample: "/var/config/rest/downloads/cert1.crt"
'''
-try:
- from f5.bigip.contexts import TransactionContextManager
- from f5.bigip import ManagementRoot
- from icontrol.session import iControlUnexpectedHTTPError
- HAS_F5SDK = True
-except ImportError:
- HAS_F5SDK = False
-
-
import hashlib
-import StringIO
+import os
+import re
+
+try:
+ from StringIO import StringIO
+except ImportError:
+ from io import StringIO
+
+from ansible.module_utils.f5_utils import (
+ AnsibleF5Client,
+ AnsibleF5Parameters,
+ HAS_F5SDK,
+ F5ModuleError,
+ iControlUnexpectedHTTPError,
+ iteritems
+)
-class BigIpSslCertificate(object):
- def __init__(self, *args, **kwargs):
- if not HAS_F5SDK:
- raise F5ModuleError("The python f5-sdk module is required")
+class Parameters(AnsibleF5Parameters):
+ def __init__(self, params=None):
+ super(Parameters, self).__init__(params)
+ self._values['__warnings'] = []
- required_args = ['key_content', 'key_src', 'cert_content', 'cert_src']
+ def to_return(self):
+ result = {}
+ try:
+ for returnable in self.returnables:
+ result[returnable] = getattr(self, returnable)
+ result = self._filter_params(result)
+ except Exception:
+ pass
+ return result
- ksource = kwargs['key_src']
- if ksource:
- with open(ksource) as f:
- kwargs['key_content'] = f.read()
+ def api_params(self):
+ result = {}
+ for api_attribute in self.api_attributes:
+ if self.api_map is not None and api_attribute in self.api_map:
+ result[api_attribute] = getattr(self, self.api_map[api_attribute])
+ else:
+ result[api_attribute] = getattr(self, api_attribute)
+ result = self._filter_params(result)
+ return result
- csource = kwargs['cert_src']
- if csource:
- with open(csource) as f:
- kwargs['cert_content'] = f.read()
-
- if kwargs['state'] == 'present':
- if not any(kwargs[k] is not None for k in required_args):
- raise F5ModuleError(
- "Either 'key_content', 'key_src', 'cert_content' or "
- "'cert_src' must be provided"
- )
-
- # This is the remote BIG-IP path from where it will look for certs
- # to install.
- self.dlpath = '/var/config/rest/downloads'
-
- # The params that change in the module
- self.cparams = dict()
-
- # Stores the params that are sent to the module
- self.params = kwargs
- self.api = ManagementRoot(kwargs['server'],
- kwargs['user'],
- kwargs['password'],
- port=kwargs['server_port'])
-
- def exists(self):
- cert = self.cert_exists()
- key = self.key_exists()
-
- if cert and key:
- return True
- else:
- return False
-
- def get_hash(self, content):
+ def _get_hash(self, content):
k = hashlib.sha1()
- s = StringIO.StringIO(content)
+ s = StringIO(content)
while True:
data = s.read(1024)
if not data:
break
- k.update(data)
+ k.update(data.encode('utf-8'))
return k.hexdigest()
- def present(self):
- current = self.read()
+ @property
+ def checksum(self):
+ if self._values['checksum'] is None:
+ return None
+ pattern = r'SHA1:\d+:(?P[\w+]{40})'
+ matches = re.match(pattern, self._values['checksum'])
+ if matches:
+ return matches.group('value')
+ else:
+ return None
+
+
+class KeyParameters(Parameters):
+ api_map = {
+ 'sourcePath': 'key_source_path'
+ }
+
+ updatables = ['key_source_path']
+
+ returnables = ['key_filename', 'key_checksum', 'key_source_path']
+
+ api_attributes = ['passphrase', 'sourcePath']
+
+ @property
+ def key_filename(self):
+ fname, fext = os.path.splitext(self.name)
+ if fext == '':
+ return fname + '.key'
+ else:
+ return self.name
+
+ @property
+ def key_checksum(self):
+ if self.key_content is None:
+ return None
+ return self._get_hash(self.key_content)
+
+ @property
+ def key_src(self):
+ if self._values['key_src'] is None:
+ return None
+
+ self._values['__warnings'].append(
+ dict(
+ msg="The key_src param is deprecated",
+ version='2.4'
+ )
+ )
+
+ try:
+ with open(self._values['key_src']) as fh:
+ self.key_content = fh.read()
+ except IOError:
+ raise F5ModuleError(
+ "The specified 'key_src' does not exist"
+ )
+
+ @property
+ def key_source_path(self):
+ result = 'file://' + os.path.join(
+ BaseManager.download_path,
+ self.key_filename
+ )
+ return result
+
+
+class CertParameters(Parameters):
+ api_map = {
+ 'sourcePath': 'cert_source_path'
+ }
+
+ updatables = ['cert_source_path']
+
+ returnables = ['cert_filename', 'cert_checksum', 'cert_source_path']
+
+ api_attributes = ['sourcePath']
+
+ @property
+ def cert_checksum(self):
+ if self.cert_content is None:
+ return None
+ return self._get_hash(self.cert_content)
+
+ @property
+ def cert_filename(self):
+ fname, fext = os.path.splitext(self.name)
+ if fext == '':
+ return fname + '.crt'
+ else:
+ return self.name
+
+ @property
+ def cert_src(self):
+ if self._values['cert_src'] is None:
+ return None
+
+ self._values['__warnings'].append(
+ dict(
+ msg="The cert_src param is deprecated",
+ version='2.4'
+ )
+ )
+
+ try:
+ with open(self._value['cert_src']) as fh:
+ self.cert_content = fh.read()
+ except IOError:
+ raise F5ModuleError(
+ "The specified 'cert_src' does not exist"
+ )
+
+ @property
+ def cert_source_path(self):
+ result = 'file://' + os.path.join(
+ BaseManager.download_path,
+ self.cert_filename
+ )
+ return result
+
+
+class ModuleManager(object):
+ def __init__(self, client):
+ self.client = client
+
+ def exec_module(self):
+ manager1 = self.get_manager('certificate')
+ manager2 = self.get_manager('key')
+ result = self.execute_managers([manager1, manager2])
+ return result
+
+ def execute_managers(self, managers):
+ results = dict(changed=False)
+ for manager in managers:
+ result = manager.exec_module()
+ for k, v in iteritems(result):
+ if k == 'changed':
+ if v is True:
+ results['changed'] = True
+ else:
+ results[k] = v
+ return results
+
+ def get_manager(self, type):
+ if type == 'certificate':
+ return CertificateManager(self.client)
+ elif type == 'key':
+ return KeyManager(self.client)
+
+
+class BaseManager(object):
+ download_path = '/var/config/rest/downloads'
+
+ def __init__(self, client):
+ self.client = client
+ self.have = None
+
+ def exec_module(self):
changed = False
- do_key = False
- do_cert = False
- chash = None
- khash = None
-
- check_mode = self.params['check_mode']
- name = self.params['name']
- partition = self.params['partition']
- cert_content = self.params['cert_content']
- key_content = self.params['key_content']
- passphrase = self.params['passphrase']
-
- # Technically you don't need to provide us with anything in the form
- # of content for your cert, but that's kind of illogical, so we just
- # return saying you didn't "do" anything if you left the cert and keys
- # empty.
- if not cert_content and not key_content:
- return False
-
- if key_content is not None:
- if 'key_checksum' in current:
- khash = self.get_hash(key_content)
- if khash not in current['key_checksum']:
- do_key = "update"
- else:
- do_key = "create"
-
- if cert_content is not None:
- if 'cert_checksum' in current:
- chash = self.get_hash(cert_content)
- if chash not in current['cert_checksum']:
- do_cert = "update"
- else:
- do_cert = "create"
-
- if do_cert or do_key:
- changed = True
- params = dict()
- params['cert_name'] = name
- params['key_name'] = name
- params['partition'] = partition
- if khash:
- params['key_checksum'] = khash
- if chash:
- params['cert_checksum'] = chash
- self.cparams = params
-
- if check_mode:
- return changed
-
- if not do_cert and not do_key:
- return False
-
- tx = self.api.tm.transactions.transaction
- with TransactionContextManager(tx) as api:
- if do_cert:
- # Upload the content of a certificate as a StringIO object
- cstring = StringIO.StringIO(cert_content)
- filename = "%s.crt" % (name)
- filepath = os.path.join(self.dlpath, filename)
- api.shared.file_transfer.uploads.upload_stringio(
- cstring,
- filename
- )
-
- if do_cert == "update":
- # Install the certificate
- params = {
- 'name': name,
- 'partition': partition
- }
- cert = api.tm.sys.file.ssl_certs.ssl_cert.load(**params)
-
- # This works because, while the source path is the same,
- # calling update causes the file to be re-read
- cert.update()
- changed = True
- elif do_cert == "create":
- # Install the certificate
- params = {
- 'sourcePath': "file://" + filepath,
- 'name': name,
- 'partition': partition
- }
- api.tm.sys.file.ssl_certs.ssl_cert.create(**params)
- changed = True
-
- if do_key:
- # Upload the content of a certificate key as a StringIO object
- kstring = StringIO.StringIO(key_content)
- filename = "%s.key" % (name)
- filepath = os.path.join(self.dlpath, filename)
- api.shared.file_transfer.uploads.upload_stringio(
- kstring,
- filename
- )
-
- if do_key == "update":
- # Install the key
- params = {
- 'name': name,
- 'partition': partition
- }
- key = api.tm.sys.file.ssl_keys.ssl_key.load(**params)
-
- params = dict()
-
- if passphrase:
- params['passphrase'] = passphrase
- else:
- params['passphrase'] = None
-
- key.update(**params)
- changed = True
- elif do_key == "create":
- # Install the key
- params = {
- 'sourcePath': "file://" + filepath,
- 'name': name,
- 'partition': partition
- }
- if passphrase:
- params['passphrase'] = self.params['passphrase']
- else:
- params['passphrase'] = None
-
- api.tm.sys.file.ssl_keys.ssl_key.create(**params)
- changed = True
- return changed
-
- def key_exists(self):
- return self.api.tm.sys.file.ssl_keys.ssl_key.exists(
- name=self.params['name'],
- partition=self.params['partition']
- )
-
- def cert_exists(self):
- return self.api.tm.sys.file.ssl_certs.ssl_cert.exists(
- name=self.params['name'],
- partition=self.params['partition']
- )
-
- def read(self):
- p = dict()
- name = self.params['name']
- partition = self.params['partition']
-
- if self.key_exists():
- key = self.api.tm.sys.file.ssl_keys.ssl_key.load(
- name=name,
- partition=partition
- )
- if hasattr(key, 'checksum'):
- p['key_checksum'] = str(key.checksum)
-
- if self.cert_exists():
- cert = self.api.tm.sys.file.ssl_certs.ssl_cert.load(
- name=name,
- partition=partition
- )
- if hasattr(cert, 'checksum'):
- p['cert_checksum'] = str(cert.checksum)
-
- p['name'] = name
- return p
-
- def flush(self):
result = dict()
- state = self.params['state']
+ state = self.want.state
try:
if state == "present":
@@ -417,94 +391,311 @@ class BigIpSslCertificate(object):
except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e))
- result.update(**self.cparams)
+ changes = self.changes.to_return()
+ result.update(**changes)
result.update(dict(changed=changed))
+ self._announce_deprecations()
return result
- def absent(self):
- changed = False
+ def _announce_deprecations(self):
+ warnings = []
+ if self.want:
+ warnings += self.want._values.get('__warnings', [])
+ if self.have:
+ warnings += self.have._values.get('__warnings', [])
+ for warning in warnings:
+ self.client.module.deprecate(
+ msg=warning['msg'],
+ version=warning['version']
+ )
+ def present(self):
if self.exists():
- changed = self.delete()
+ return self.update()
+ else:
+ return self.create()
- return changed
-
- def delete(self):
- changed = False
- name = self.params['name']
- partition = self.params['partition']
-
- check_mode = self.params['check_mode']
-
- delete_cert = self.cert_exists()
- delete_key = self.key_exists()
-
- if not delete_cert and not delete_key:
- return changed
-
- if check_mode:
- params = dict()
- params['cert_name'] = name
- params['key_name'] = name
- params['partition'] = partition
- self.cparams = params
+ def create(self):
+ self._set_changed_options()
+ if self.client.check_mode:
return True
+ self.create_on_device()
+ return True
- tx = self.api.tm.transactions.transaction
- with TransactionContextManager(tx) as api:
- if delete_cert:
- # Delete the certificate
- c = api.tm.sys.file.ssl_certs.ssl_cert.load(
- name=self.params['name'],
- partition=self.params['partition']
- )
- c.delete()
- changed = True
+ def should_update(self):
+ result = self._update_changed_options()
+ if result:
+ return True
+ return False
- if delete_key:
- # Delete the certificate key
- k = self.api.tm.sys.file.ssl_keys.ssl_key.load(
- name=self.params['name'],
- partition=self.params['partition']
- )
- k.delete()
- changed = True
- return changed
+ def update(self):
+ self.have = self.read_current_from_device()
+ if not self.should_update():
+ return False
+ if self.client.check_mode:
+ return True
+ self.update_on_device()
+ return True
+
+ def absent(self):
+ if self.exists():
+ return self.remove()
+ return False
+
+ def remove(self):
+ if self.client.check_mode:
+ return True
+ self.remove_from_device()
+ return True
-def main():
- argument_spec = f5_argument_spec()
+class CertificateManager(BaseManager):
+ def __init__(self, client):
+ super(CertificateManager, self).__init__(client)
+ self.want = CertParameters(self.client.module.params)
+ self.changes = CertParameters()
- meta_args = dict(
- name=dict(type='str', required=True),
- cert_content=dict(type='str', default=None),
- cert_src=dict(type='path', default=None),
- key_content=dict(type='str', default=None),
- key_src=dict(type='path', default=None),
- passphrase=dict(type='str', default=None, no_log=True)
- )
+ def _set_changed_options(self):
+ changed = {}
+ try:
+ for key in CertParameters.returnables:
+ if getattr(self.want, key) is not None:
+ changed[key] = getattr(self.want, key)
+ if changed:
+ self.changes = CertParameters(changed)
+ except Exception:
+ pass
- argument_spec.update(meta_args)
+ def _update_changed_options(self):
+ changed = {}
+ try:
+ for key in CertParameters.updatables:
+ if getattr(self.want, key) is not None:
+ attr1 = getattr(self.want, key)
+ attr2 = getattr(self.have, key)
+ if attr1 != attr2:
+ changed[key] = attr1
+ if self.want.cert_checksum != self.have.checksum:
+ changed['cert_checksum'] = self.want.cert_checksum
+ if changed:
+ self.changes = CertParameters(changed)
+ return True
+ except Exception:
+ pass
+ return False
- module = AnsibleModule(
- argument_spec=argument_spec,
- supports_check_mode=True,
- mutually_exclusive=[
+ def exists(self):
+ result = self.client.api.tm.sys.file.ssl_certs.ssl_cert.exists(
+ name=self.want.cert_filename,
+ partition=self.want.partition
+ )
+ return result
+
+ def present(self):
+ if self.want.cert_content is None:
+ return False
+ return super(CertificateManager, self).present()
+
+ def should_update(self):
+ result = self._update_changed_options()
+ if result:
+ return True
+ return False
+
+ def update_on_device(self):
+ content = StringIO(self.want.cert_content)
+ self.client.api.shared.file_transfer.uploads.upload_stringio(
+ content, self.want.cert_filename
+ )
+ resource = self.client.api.tm.sys.file.ssl_certs.ssl_cert.load(
+ name=self.want.cert_filename,
+ partition=self.want.partition
+ )
+ resource.update()
+
+ def create_on_device(self):
+ content = StringIO(self.want.cert_content)
+ self.client.api.shared.file_transfer.uploads.upload_stringio(
+ content, self.want.cert_filename
+ )
+ self.client.api.tm.sys.file.ssl_certs.ssl_cert.create(
+ sourcePath=self.want.cert_source_path,
+ name=self.want.cert_filename,
+ partition=self.want.partition
+ )
+
+ def read_current_from_device(self):
+ resource = self.client.api.tm.sys.file.ssl_certs.ssl_cert.load(
+ name=self.want.cert_filename,
+ partition=self.want.partition
+ )
+ result = resource.attrs
+ return CertParameters(result)
+
+ def remove_from_device(self):
+ resource = self.client.api.tm.sys.file.ssl_certs.ssl_cert.load(
+ name=self.want.cert_filename,
+ partition=self.want.partition
+ )
+ resource.delete()
+
+ def remove(self):
+ result = super(CertificateManager, self).remove()
+ if self.exists() and not self.client.check_mode:
+ raise F5ModuleError("Failed to delete the certificate")
+ return result
+
+
+class KeyManager(BaseManager):
+ def __init__(self, client):
+ super(KeyManager, self).__init__(client)
+ self.want = KeyParameters(self.client.module.params)
+ self.changes = KeyParameters()
+
+ def _set_changed_options(self):
+ changed = {}
+ try:
+ for key in KeyParameters.returnables:
+ if getattr(self.want, key) is not None:
+ changed[key] = getattr(self.want, key)
+ if changed:
+ self.changes = Parameters(changed)
+ except Exception:
+ pass
+
+ def _update_changed_options(self):
+ changed = {}
+ try:
+ for key in CertParameters.updatables:
+ if getattr(self.want, key) is not None:
+ attr1 = getattr(self.want, key)
+ attr2 = getattr(self.have, key)
+ if attr1 != attr2:
+ changed[key] = attr1
+ if self.want.key_checksum != self.have.checksum:
+ changed['key_checksum'] = self.want.key_checksum
+ if changed:
+ self.changes = CertParameters(changed)
+ return True
+ except Exception:
+ pass
+ return False
+
+ def should_update(self):
+ result = self._update_changed_options()
+ if result:
+ return True
+ return False
+
+ def update_on_device(self):
+ content = StringIO(self.want.key_content)
+ self.client.api.shared.file_transfer.uploads.upload_stringio(
+ content, self.want.key_filename
+ )
+ resource = self.client.api.tm.sys.file.ssl_keys.ssl_key.load(
+ name=self.want.key_filename,
+ partition=self.want.partition
+ )
+ resource.update()
+
+ def exists(self):
+ result = self.client.api.tm.sys.file.ssl_keys.ssl_key.exists(
+ name=self.want.key_filename,
+ partition=self.want.partition
+ )
+ return result
+
+ def present(self):
+ if self.want.key_content is None:
+ return False
+ return super(KeyManager, self).present()
+
+ def read_current_from_device(self):
+ resource = self.client.api.tm.sys.file.ssl_keys.ssl_key.load(
+ name=self.want.key_filename,
+ partition=self.want.partition
+ )
+ result = resource.attrs
+ return KeyParameters(result)
+
+ def create_on_device(self):
+ content = StringIO(self.want.key_content)
+ self.client.api.shared.file_transfer.uploads.upload_stringio(
+ content, self.want.key_filename
+ )
+ self.client.api.tm.sys.file.ssl_keys.ssl_key.create(
+ sourcePath=self.want.key_source_path,
+ name=self.want.key_filename,
+ partition=self.want.partition
+ )
+
+ def remove_from_device(self):
+ resource = self.client.api.tm.sys.file.ssl_keys.ssl_key.load(
+ name=self.want.key_filename,
+ partition=self.want.partition
+ )
+ resource.delete()
+
+ def remove(self):
+ result = super(KeyManager, self).remove()
+ if self.exists() and not self.client.check_mode:
+ raise F5ModuleError("Failed to delete the key")
+ return result
+
+
+class ArgumentSpec(object):
+ def __init__(self):
+ self.supports_check_mode = True
+ self.argument_spec = dict(
+ name=dict(
+ required=True
+ ),
+ cert_content=dict(),
+ cert_src=dict(
+ type='path',
+ removed_in_version='2.4'
+ ),
+ key_content=dict(),
+ key_src=dict(
+ type='path',
+ removed_in_version='2.4'
+ ),
+ passphrase=dict(
+ no_log=True
+ ),
+ state=dict(
+ required=False,
+ default='present',
+ choices=['absent', 'present']
+ )
+ )
+ self.mutually_exclusive = [
['key_content', 'key_src'],
['cert_content', 'cert_src']
]
+ self.f5_product_name = 'bigip'
+
+
+def main():
+ if not HAS_F5SDK:
+ raise F5ModuleError("The python f5-sdk module is required")
+
+ spec = ArgumentSpec()
+
+ client = AnsibleF5Client(
+ argument_spec=spec.argument_spec,
+ mutually_exclusive=spec.mutually_exclusive,
+ supports_check_mode=spec.supports_check_mode,
+ f5_product_name=spec.f5_product_name
)
try:
- obj = BigIpSslCertificate(check_mode=module.check_mode,
- **module.params)
- result = obj.flush()
- module.exit_json(**result)
+ mm = ModuleManager(client)
+ results = mm.exec_module()
+ client.module.exit_json(**results)
except F5ModuleError as e:
- module.fail_json(msg=str(e))
+ client.module.fail_json(msg=str(e))
-from ansible.module_utils.basic import *
-from ansible.module_utils.f5_utils import *
if __name__ == '__main__':
main()
diff --git a/test/units/modules/network/f5/fixtures/cert1.crt b/test/units/modules/network/f5/fixtures/cert1.crt
new file mode 100644
index 00000000000..1d22f30289f
--- /dev/null
+++ b/test/units/modules/network/f5/fixtures/cert1.crt
@@ -0,0 +1,101 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=US, ST=New York, L=New York, O=ACME CA, OU=Coyote, CN=ourca.domain.local
+ Validity
+ Not Before: Jun 30 16:46:09 2016 GMT
+ Not After : Jun 25 16:46:09 2036 GMT
+ Subject: C=US, ST=New York, O=ACME, OU=Coyote, CN=cert1.domain.local
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d6:0f:bd:26:ef:14:4d:09:f6:db:8b:01:f5:4e:
+ 6c:03:b1:35:20:16:b8:1b:7c:e6:b6:8d:97:1b:b0:
+ 4f:8a:b6:cb:54:7e:7a:ff:fd:af:02:db:bf:9d:cf:
+ 9a:4c:0d:87:93:8b:cc:61:f3:23:a9:6f:8e:d4:82:
+ 2c:93:b6:e2:fa:37:ed:8a:d3:23:8f:6d:b5:78:4a:
+ 38:ba:93:f9:4a:1c:40:06:33:d7:c0:98:20:d4:16:
+ ac:a4:a5:6b:41:20:4c:3a:55:7e:c7:50:e7:95:07:
+ 4e:86:15:86:7a:0f:6c:57:d2:07:1c:97:24:51:5b:
+ 4e:f5:52:3a:f8:4f:95:6b:6c:83:1f:34:4e:ee:b0:
+ ae:fe:46:90:38:f1:4d:85:72:8b:46:bc:d1:62:37:
+ 65:5a:de:bb:16:51:1e:f5:cb:a0:ef:d6:7b:11:6f:
+ 3b:0c:49:17:bc:4d:8c:f5:d9:f0:35:6b:f7:b6:4d:
+ 50:eb:47:81:e3:06:f2:bd:ec:67:4f:ab:2b:03:aa:
+ e2:1e:42:22:a9:c9:59:dc:0d:19:fb:c5:02:1d:d7:
+ 58:e4:04:53:0a:1d:79:bb:c1:33:f1:cd:b7:10:2e:
+ b4:6e:9b:dc:60:66:05:50:9f:20:66:a1:71:00:51:
+ 54:cf:0a:70:f4:7c:45:c6:f0:a7:1c:11:2f:3e:a3:
+ 1f:bf
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ 2D:FB:27:C7:B4:32:FF:F7:87:DB:2D:A7:76:AE:F0:96:7E:DA:DC:17
+ X509v3 Authority Key Identifier:
+ keyid:4F:2A:15:49:E6:CC:05:2F:2B:F4:0E:CC:BA:2E:4C:DF:13:90:F0:78
+
+ Signature Algorithm: sha256WithRSAEncryption
+ 3f:46:1c:3b:58:b4:99:f3:75:00:47:d2:fe:ba:ba:9a:04:46:
+ 62:b6:2d:a0:0f:8f:c0:95:2a:58:8b:61:f5:14:90:30:26:37:
+ 94:a1:a6:29:20:c9:b5:08:d7:f9:15:cb:9d:9c:19:ed:2f:a4:
+ e6:91:48:85:1a:f7:ab:17:5e:79:23:69:b8:3c:0c:48:ae:c8:
+ ba:90:d0:05:fb:33:7e:86:fd:12:f8:2d:0f:ff:16:15:9a:dc:
+ 76:48:7d:65:5b:4e:93:14:e8:be:37:d1:13:f7:a7:b1:cd:ad:
+ ae:4f:e1:72:b9:53:2d:cd:e6:42:76:44:93:21:28:58:c0:44:
+ ab:3c:da:5b:e5:55:ab:04:86:4d:9c:4c:33:f4:4e:13:98:e9:
+ 0f:d1:a3:70:2b:1d:11:20:47:26:f6:d8:45:7f:88:ad:f2:c1:
+ 81:0f:be:cd:6c:79:80:94:30:eb:8d:cc:f3:7d:a1:3e:6c:6f:
+ fa:8f:f3:1f:2e:76:97:3f:8a:1b:67:3b:e0:f9:b1:3c:6b:dc:
+ 64:1b:00:73:e9:89:81:f6:7f:51:f3:51:c8:b9:96:5f:fd:55:
+ f8:77:6f:88:bc:65:b3:e2:30:a4:00:7a:79:68:e0:36:8b:a9:
+ 1b:06:9b:20:fe:fe:98:aa:56:58:c8:08:a4:7b:12:59:ff:3d:
+ bd:5e:13:3b:c6:c7:8a:00:5b:cb:27:18:02:ee:cb:38:c2:b7:
+ a9:51:04:ef:31:ca:49:09:48:14:13:eb:91:e2:26:8c:88:5f:
+ 1c:78:e1:0d:90:29:d7:c1:fc:c8:89:fd:4d:53:0b:99:58:c2:
+ 1a:24:3d:c0:a2:4c:a3:d9:c7:95:c5:bc:72:fa:02:f1:ab:dd:
+ aa:2b:9e:a0:bb:1a:68:2d:09:8c:a2:99:0d:26:ec:9e:30:19:
+ 01:5a:41:45:63:b3:c5:db:24:32:4c:fe:7f:f3:ce:e9:4d:00:
+ 64:cf:bb:15:34:2d:31:6e:4f:c0:96:40:9b:32:35:65:92:01:
+ 29:7e:74:02:50:fd:3b:3b:3a:a3:9f:6a:c0:a5:be:3f:c3:07:
+ d6:8c:2a:c6:f4:0f:32:bd:3b:fc:45:90:d2:46:ee:6f:c3:2f:
+ 26:8c:97:0c:e8:da:9a:97:03:0b:86:17:45:a6:62:69:4e:8d:
+ cf:f8:bf:ea:2f:dc:ff:95:14:15:bd:92:2d:8a:08:cf:ce:8a:
+ b0:f6:34:0a:a2:0e:49:31:44:e1:47:fb:37:52:53:59:93:25:
+ 40:cc:ac:67:2d:a2:b6:9b:75:fd:13:a5:a7:93:4f:72:05:75:
+ cd:b1:37:f6:3b:69:3b:24:a1:1f:23:f0:cd:bb:ae:18:b3:aa:
+ eb:9f:d7:97:06:ba:fd:44
+-----BEGIN CERTIFICATE-----
+MIIExjCCAq6gAwIBAgIBATANBgkqhkiG9w0BAQsFADBzMQswCQYDVQQGEwJVUzER
+MA8GA1UECAwITmV3IFlvcmsxETAPBgNVBAcMCE5ldyBZb3JrMRAwDgYDVQQKDAdB
+Q01FIENBMQ8wDQYDVQQLDAZDb3lvdGUxGzAZBgNVBAMMEm91cmNhLmRvbWFpbi5s
+b2NhbDAeFw0xNjA2MzAxNjQ2MDlaFw0zNjA2MjUxNjQ2MDlaMF0xCzAJBgNVBAYT
+AlVTMREwDwYDVQQIDAhOZXcgWW9yazENMAsGA1UECgwEQUNNRTEPMA0GA1UECwwG
+Q295b3RlMRswGQYDVQQDDBJjZXJ0MS5kb21haW4ubG9jYWwwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDWD70m7xRNCfbbiwH1TmwDsTUgFrgbfOa2jZcb
+sE+KtstUfnr//a8C27+dz5pMDYeTi8xh8yOpb47UgiyTtuL6N+2K0yOPbbV4Sji6
+k/lKHEAGM9fAmCDUFqykpWtBIEw6VX7HUOeVB06GFYZ6D2xX0gcclyRRW071Ujr4
+T5VrbIMfNE7usK7+RpA48U2FcotGvNFiN2Va3rsWUR71y6Dv1nsRbzsMSRe8TYz1
+2fA1a/e2TVDrR4HjBvK97GdPqysDquIeQiKpyVncDRn7xQId11jkBFMKHXm7wTPx
+zbcQLrRum9xgZgVQnyBmoXEAUVTPCnD0fEXG8KccES8+ox+/AgMBAAGjezB5MAkG
+A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp
+ZmljYXRlMB0GA1UdDgQWBBQt+yfHtDL/94fbLad2rvCWftrcFzAfBgNVHSMEGDAW
+gBRPKhVJ5swFLyv0Dsy6LkzfE5DweDANBgkqhkiG9w0BAQsFAAOCAgEAP0YcO1i0
+mfN1AEfS/rq6mgRGYrYtoA+PwJUqWIth9RSQMCY3lKGmKSDJtQjX+RXLnZwZ7S+k
+5pFIhRr3qxdeeSNpuDwMSK7IupDQBfszfob9EvgtD/8WFZrcdkh9ZVtOkxTovjfR
+E/ensc2trk/hcrlTLc3mQnZEkyEoWMBEqzzaW+VVqwSGTZxMM/ROE5jpD9GjcCsd
+ESBHJvbYRX+IrfLBgQ++zWx5gJQw643M832hPmxv+o/zHy52lz+KG2c74PmxPGvc
+ZBsAc+mJgfZ/UfNRyLmWX/1V+HdviLxls+IwpAB6eWjgNoupGwabIP7+mKpWWMgI
+pHsSWf89vV4TO8bHigBbyycYAu7LOMK3qVEE7zHKSQlIFBPrkeImjIhfHHjhDZAp
+18H8yIn9TVMLmVjCGiQ9wKJMo9nHlcW8cvoC8avdqiueoLsaaC0JjKKZDSbsnjAZ
+AVpBRWOzxdskMkz+f/PO6U0AZM+7FTQtMW5PwJZAmzI1ZZIBKX50AlD9Ozs6o59q
+wKW+P8MH1owqxvQPMr07/EWQ0kbub8MvJoyXDOjampcDC4YXRaZiaU6Nz/i/6i/c
+/5UUFb2SLYoIz86KsPY0CqIOSTFE4Uf7N1JTWZMlQMysZy2itpt1/ROlp5NPcgV1
+zbE39jtpOyShHyPwzbuuGLOq65/Xlwa6/UQ=
+-----END CERTIFICATE-----
diff --git a/test/units/modules/network/f5/fixtures/cert1.key b/test/units/modules/network/f5/fixtures/cert1.key
new file mode 100644
index 00000000000..a89a29161c8
--- /dev/null
+++ b/test/units/modules/network/f5/fixtures/cert1.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA1g+9Ju8UTQn224sB9U5sA7E1IBa4G3zmto2XG7BPirbLVH56
+//2vAtu/nc+aTA2Hk4vMYfMjqW+O1IIsk7bi+jftitMjj221eEo4upP5ShxABjPX
+wJgg1BaspKVrQSBMOlV+x1DnlQdOhhWGeg9sV9IHHJckUVtO9VI6+E+Va2yDHzRO
+7rCu/kaQOPFNhXKLRrzRYjdlWt67FlEe9cug79Z7EW87DEkXvE2M9dnwNWv3tk1Q
+60eB4wbyvexnT6srA6riHkIiqclZ3A0Z+8UCHddY5ARTCh15u8Ez8c23EC60bpvc
+YGYFUJ8gZqFxAFFUzwpw9HxFxvCnHBEvPqMfvwIDAQABAoIBAQCjQ7PP+y8vpvbp
+8bbXoy2ND15mkA1xoazR9WIYEzxHny2rzx//GTyfYH1gXtPfR75tEYYb+vbrJxP4
+DyTysN2jXH7HkEwh+9oZ2fo0i+Hp3WwTjvzyftUjDfw1Q5lvPbQGFekxGgrXRpBk
+ggxkEllfDeiwrLJdftfVEhe6BfD/0YibwQeHN7VoC4V8wOanKtDmx74W/1f7WhwQ
+nKQnCrbYqNJa2nGvWiKU5Suvfb0v7tCnQYlfnCpUfj+wcnxlgmGkcyq1L+qC1qC8
+PO5i3T3LM5Yg8CSeGhO/q6gw/fUowuBN1cluTqN97oLHiEM5tLdjeVWwa1Vp0liv
+1WXGT4eBAoGBAPtumMmyVTIorvV6KGNI/Eo6jfE0HOXVdXtm4iToDDuiYwto7/Ge
+/kV+11Fpu0lV+eYPfZn175Of8FnQPwczQF1OOH/aQ/ViY8j87bZUbCy25mWrfNkh
+2rRlyI3/OsSfL5SkyWpYB0yhSJZV9mSQJTZolB4GQRNPKtqi7NpB4WxBAoGBANnz
+VS4JBJO75yeSG5BzPp5VVKm+nu0Betlva8GsHdEic8OM9bGpVozGysAW3Xdxp7q6
+gLJGyyuzpsxldCc/IdIlF5fz7gkLl4NoYanz9PSEr2XZLh9+2yXGkPFlC3IeHAUB
+E+2UO9MFpWrmfKoAnYZCR6vJDxtQBpAlTUvJEYv/AoGBAPha62K32327P+7MJl7D
+9ijgI9rwjebcbbpiCtlHuOWi5lCb6/7v/NvqiYcqeEvdOAXuoTNWAbsBTel5UPis
+wFQp8pcfouccs9IRPEFQrLWSSIx+0sirrxtoOq1AQe18DAS4rRd1MmiYG1ocOVBm
+LcvLixsJNHh9R6hFLM3+K0vBAoGANkmJ+gF9Bl9TYGPgQcay3jVa9Tzp0RcBRo+e
+Q4tfkewG8bp2qF4JlN8fOWF4oHvKz5QM4lsH2EbTUS4kFHKBNhrPGaZEsDQW9UBW
+s0J0zUMPfUrvViD+7RXcnIQSqcYeLJDsKc02aYWKgmoOuzmUAxEXUQ6vmJoCSH1C
+F5JpsHkCgYEArwTSzb1+/ThQhK1JN8hJ4jMjQ8E7PzLTMILrdDALn2g1T4VzL7N7
+UG6oUieMlo/UH6cv6330dwaGVklXZbyDKSDROIafFcOpVfcvDUgJCjp3CaY9A2zG
++EPkRpeHKXAIgG+QuOwVOtYWcWltnBf61slTqiY2vKX1+ZGmrMrw1Zw=
+-----END RSA PRIVATE KEY-----
diff --git a/test/units/modules/network/f5/fixtures/cert2.crt b/test/units/modules/network/f5/fixtures/cert2.crt
new file mode 100644
index 00000000000..30674bc8e7c
--- /dev/null
+++ b/test/units/modules/network/f5/fixtures/cert2.crt
@@ -0,0 +1,101 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 2 (0x2)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=US, ST=New York, L=New York, O=ACME CA, OU=Coyote, CN=ourca.domain.local
+ Validity
+ Not Before: Jun 30 16:49:00 2016 GMT
+ Not After : Jun 25 16:49:00 2036 GMT
+ Subject: C=US, ST=New York, O=ACME, OU=Coyote, CN=cert2.domain.local
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:c6:9e:84:99:4d:69:98:c2:42:95:ed:43:ca:24:
+ 05:64:9d:67:81:1c:ff:56:7b:ad:d1:cb:09:39:28:
+ 4f:ac:aa:1b:34:61:3a:b1:e3:57:d4:9e:15:40:77:
+ 91:20:2b:e8:7e:d3:91:1e:46:50:6c:2f:4b:00:c2:
+ f2:3a:43:89:d9:81:73:84:5f:02:db:49:ac:3b:9e:
+ fe:c0:77:2e:53:ea:ce:da:ff:49:98:21:1d:31:4d:
+ 0f:14:20:30:36:9a:23:b4:28:08:06:59:81:30:03:
+ 86:09:0b:5b:e1:72:63:5e:54:ac:90:b1:82:55:b8:
+ 12:00:d5:01:26:be:6a:eb:fc:58:5b:8a:7a:fe:46:
+ 23:a3:eb:5d:6c:e0:f6:79:00:5d:5b:49:82:42:62:
+ e2:58:e8:65:54:14:be:99:25:8b:b7:df:cf:53:26:
+ f2:7a:fd:b9:f9:f3:d5:af:06:d6:1e:ba:66:4d:41:
+ 8c:5d:aa:23:41:7f:f4:27:21:a0:30:09:86:13:c4:
+ 57:1b:13:45:63:6b:3b:a3:7f:d1:1a:cd:fd:07:51:
+ 0f:1a:e1:d9:25:3e:d2:77:e1:c7:60:db:12:df:ef:
+ 71:65:c8:c7:1a:42:94:6f:57:2a:d7:67:30:0f:33:
+ 31:ba:90:4d:d1:80:38:08:e7:90:7a:04:0e:8f:b0:
+ 2a:73
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ 43:71:A9:16:2B:DA:DC:5F:FD:82:87:78:26:48:4E:77:21:47:44:D6
+ X509v3 Authority Key Identifier:
+ keyid:4F:2A:15:49:E6:CC:05:2F:2B:F4:0E:CC:BA:2E:4C:DF:13:90:F0:78
+
+ Signature Algorithm: sha256WithRSAEncryption
+ 15:ac:f9:cb:bc:88:c0:d3:74:83:88:cd:19:94:bb:87:7e:fd:
+ 4d:25:09:9b:08:84:64:c5:37:c7:99:b3:25:ee:6e:82:46:b0:
+ 13:f9:05:ad:4d:4a:b2:e3:29:5d:0d:55:9e:9c:62:1d:95:f2:
+ 19:49:5e:d3:5b:58:98:ce:e8:f5:e5:c1:ce:b5:a8:7a:b1:f8:
+ 14:fe:25:10:12:5b:41:53:d2:47:ab:20:e5:50:da:b6:ba:00:
+ 21:94:6b:dd:0b:24:15:dc:c0:4e:b8:1d:cc:9e:5f:10:5e:46:
+ 3f:96:c9:f8:28:bb:13:31:d6:d2:6c:48:41:bb:23:ab:23:64:
+ 73:d6:2b:2e:9a:77:d4:08:fb:e0:e8:50:2c:49:7f:98:9e:f6:
+ 37:30:2b:7c:97:c6:a7:1e:5b:dc:ce:bb:1e:58:e4:bd:05:4c:
+ ad:07:d6:03:c5:a9:57:a4:26:e2:10:f7:f9:63:1a:2a:6a:9c:
+ 52:98:33:bf:ea:70:cd:c0:86:32:80:6e:70:54:87:74:3c:41:
+ 53:a1:c6:53:44:c7:74:a6:11:b6:48:66:86:f9:04:ca:ec:5d:
+ 4f:ce:7f:64:51:34:52:53:98:a8:70:62:f7:3b:fb:39:11:9a:
+ e1:e2:d3:00:0b:6b:d2:33:3c:44:de:c3:6b:e1:6f:c9:be:d2:
+ 2c:8a:f0:b3:d3:4c:12:2f:ad:9d:6b:40:89:23:94:93:6d:12:
+ 6c:38:89:fa:fe:ad:02:55:55:8b:c3:86:7f:15:c4:3a:a9:70:
+ e9:06:6c:26:09:28:9f:6e:94:f2:a1:27:5c:89:4c:42:ac:65:
+ 90:92:d2:6d:09:7c:d8:a1:bf:5b:25:e4:db:ed:71:41:d7:e2:
+ 61:47:89:9e:46:29:9d:f9:f4:94:cf:f5:b3:e8:df:6a:47:34:
+ d1:ed:fc:a4:58:fe:82:e1:6e:e9:05:65:f5:d2:57:9a:d1:42:
+ 64:ae:0c:bb:07:14:39:a2:c0:85:e4:25:a5:c4:e6:3f:e6:da:
+ d0:18:4f:e0:01:ba:99:2e:1f:75:35:c3:fa:a3:e7:e1:75:1b:
+ 1c:19:93:cc:96:eb:3f:ce:8b:10:40:36:63:f5:66:dc:6d:75:
+ 31:ba:db:27:21:b4:15:00:e9:ce:d0:08:e3:b0:1c:e3:29:c9:
+ 63:5a:c8:5c:ca:db:ce:51:b7:87:22:c6:ba:42:d7:ab:29:b4:
+ 87:fa:27:9a:18:22:90:9f:da:c0:90:c4:49:64:38:38:2e:a2:
+ ea:87:c1:8b:4e:8b:ff:a7:53:45:4f:d8:8b:86:69:ea:87:1d:
+ f6:e6:44:14:1f:69:ee:2c:de:5a:a1:df:a8:57:13:65:4d:5b:
+ ce:6e:f2:15:2a:c5:32:08
+-----BEGIN CERTIFICATE-----
+MIIExjCCAq6gAwIBAgIBAjANBgkqhkiG9w0BAQsFADBzMQswCQYDVQQGEwJVUzER
+MA8GA1UECAwITmV3IFlvcmsxETAPBgNVBAcMCE5ldyBZb3JrMRAwDgYDVQQKDAdB
+Q01FIENBMQ8wDQYDVQQLDAZDb3lvdGUxGzAZBgNVBAMMEm91cmNhLmRvbWFpbi5s
+b2NhbDAeFw0xNjA2MzAxNjQ5MDBaFw0zNjA2MjUxNjQ5MDBaMF0xCzAJBgNVBAYT
+AlVTMREwDwYDVQQIDAhOZXcgWW9yazENMAsGA1UECgwEQUNNRTEPMA0GA1UECwwG
+Q295b3RlMRswGQYDVQQDDBJjZXJ0Mi5kb21haW4ubG9jYWwwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDGnoSZTWmYwkKV7UPKJAVknWeBHP9We63Rywk5
+KE+sqhs0YTqx41fUnhVAd5EgK+h+05EeRlBsL0sAwvI6Q4nZgXOEXwLbSaw7nv7A
+dy5T6s7a/0mYIR0xTQ8UIDA2miO0KAgGWYEwA4YJC1vhcmNeVKyQsYJVuBIA1QEm
+vmrr/Fhbinr+RiOj611s4PZ5AF1bSYJCYuJY6GVUFL6ZJYu3389TJvJ6/bn589Wv
+BtYeumZNQYxdqiNBf/QnIaAwCYYTxFcbE0Vjazujf9Eazf0HUQ8a4dklPtJ34cdg
+2xLf73FlyMcaQpRvVyrXZzAPMzG6kE3RgDgI55B6BA6PsCpzAgMBAAGjezB5MAkG
+A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp
+ZmljYXRlMB0GA1UdDgQWBBRDcakWK9rcX/2Ch3gmSE53IUdE1jAfBgNVHSMEGDAW
+gBRPKhVJ5swFLyv0Dsy6LkzfE5DweDANBgkqhkiG9w0BAQsFAAOCAgEAFaz5y7yI
+wNN0g4jNGZS7h379TSUJmwiEZMU3x5mzJe5ugkawE/kFrU1KsuMpXQ1VnpxiHZXy
+GUle01tYmM7o9eXBzrWoerH4FP4lEBJbQVPSR6sg5VDatroAIZRr3QskFdzATrgd
+zJ5fEF5GP5bJ+Ci7EzHW0mxIQbsjqyNkc9YrLpp31Aj74OhQLEl/mJ72NzArfJfG
+px5b3M67HljkvQVMrQfWA8WpV6Qm4hD3+WMaKmqcUpgzv+pwzcCGMoBucFSHdDxB
+U6HGU0THdKYRtkhmhvkEyuxdT85/ZFE0UlOYqHBi9zv7ORGa4eLTAAtr0jM8RN7D
+a+Fvyb7SLIrws9NMEi+tnWtAiSOUk20SbDiJ+v6tAlVVi8OGfxXEOqlw6QZsJgko
+n26U8qEnXIlMQqxlkJLSbQl82KG/WyXk2+1xQdfiYUeJnkYpnfn0lM/1s+jfakc0
+0e38pFj+guFu6QVl9dJXmtFCZK4MuwcUOaLAheQlpcTmP+ba0BhP4AG6mS4fdTXD
++qPn4XUbHBmTzJbrP86LEEA2Y/Vm3G11MbrbJyG0FQDpztAI47Ac4ynJY1rIXMrb
+zlG3hyLGukLXqym0h/onmhgikJ/awJDESWQ4OC6i6ofBi06L/6dTRU/Yi4Zp6ocd
+9uZEFB9p7izeWqHfqFcTZU1bzm7yFSrFMgg=
+-----END CERTIFICATE-----
diff --git a/test/units/modules/network/f5/fixtures/cert2.key b/test/units/modules/network/f5/fixtures/cert2.key
new file mode 100644
index 00000000000..6d4bdf15263
--- /dev/null
+++ b/test/units/modules/network/f5/fixtures/cert2.key
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-256-CBC,C56C8101A9C8D6B9AD0975807D4793BB
+
+rhV1cJee3XAlY83zIXytJOlvXFrsHmzyTVoOn26eOwgza8CuE5gQQzLXiT12zK0q
+YAbYyyyEXJJogVU61s1vuCQRNiezUCwT4SBj7ni4rXqu5BYUxKh0wD0gjE519yNP
+nqnPUYKdLkFY7I6RJjqCqkk8xnJm64g5zCqN58aR98Mkqr1898+lZ2OHqAsAYBNH
+dM/SE7B7E4Mr1sAjpsn6L4PJ93WSwmEtH3nZTnPF9qtFuJwjUcHCN/r/s3QXki95
+eFX+7qW460lBfDeRUKXKqz4gO017AXu1kccrlHhdQoJGf3D+x9zwofG/uFeAH3iN
+f9IRaiR2IN6SS67QFmOkI9S95tsFb4N8bmmfGV4w8wfxvDzGJuxzIb4gByX5xov4
+S22pDpkfn5YqxgC5ItSiFYpg01HEi2l79HwZqAn1kowLsuF1JJKAYL5IMS3DlrdH
+AyA9CN28G6pYEjwFBbFgpOg64UNmrkxRncHxC4FuH7iGZNJL9+HQve/J5nlrnx6M
+IU2myiZZhgbsl/V45ddXBDSlEdWFLHtEhcG+ICJP3EZAXHR0e9vyrWDk7T5zKhLP
+ch9PNmIw+5zzpRuPu5NYw7V0ax8UOf2AydyBHeIQWuY52bai+QMDyQauomqpPXRY
+tpCcW85P9jstY/F6TV32XQu/cHWolziJXI/QzWF5+uvnLMAsb3p5mriCG4DOTWF3
+KFSytTGnDQUUCLgaYSSKXL5Z52PVYmTjoqX8M6cvqSEdjK84wILQE0JMItQjGSIM
+y5qHD7Mthf9YOJy1D86qtVumbaOBLw/rGPQS5QlK/m256xZ10LUslYczMpw1orN3
+3Uv8zHKk790XduHTllR0LwQXMJXG59hgiWAu3V3rsAkVSRpC3MI6IUZ2cfJvZ0Ds
+FmUhCJ34JQxD4E/sT9uGAk6VIq/fAmM7/gq0oF4oqOFg4Zy1r3rc1Kvdoy1yKUi6
+JCI5bKCkgIthx4XUKQVtFMkHBDZAHr6i5Lzy4nM6I4S4/qL3JH4Q+739D1rjGVlq
+OWcaeOzkkbJrE8h+A94UQao4R50LavKgq/o2n56tHG0RhXXyV5MC/X9rbSVipihR
+rwNKnogdhAjY96IrOzdiHTArg8qZBGvHPoGUl3zjWFqNbHEs4NLSrEl6oEs6F/vC
+zEZmi8gxqraw4u1GJnpoMuLO45PuhcxcXgJSvTh/OKDaR1u0ggEn7TxfAygm0ahP
+i6NBgoZ/upTHAWqWht2JjSmQHQW7doVkp/BgNJq13oYF7FEUEg/ZtBTPKPR3CjM0
+ZKDGvKqWRVRyrw9FSwXn6WlSFfT3vhPMoW2jq1Kq5o/ZyhcquCVE8i+xq6hilcb5
+sNiV1tPWsZOFHx4T5hBVK+QnC8t7pCj38YpyEoY4/gffMtY85jsrLMlPYd5bmJ6O
+x1tKiQauK+aX6IMu38YnHjCGnCkw1fF2OMSohbG2QfaKsmfkt8YLRuf2PTtjLtke
+xGt0Irjac/sEZPc4SEIqnehNfXadiuMV3+4v6ey9vf782r76KH8gInY2gDsQ4X6d
+1LVNCNAd/AGlitopL4hYomaeTjTzqIy5fMlGmTrpZjokenu/ILXsljZVAX2iyOAs
+-----END RSA PRIVATE KEY-----
diff --git a/test/units/modules/network/f5/fixtures/create_insecure_cert1.crt b/test/units/modules/network/f5/fixtures/create_insecure_cert1.crt
new file mode 100644
index 00000000000..1d22f30289f
--- /dev/null
+++ b/test/units/modules/network/f5/fixtures/create_insecure_cert1.crt
@@ -0,0 +1,101 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: C=US, ST=New York, L=New York, O=ACME CA, OU=Coyote, CN=ourca.domain.local
+ Validity
+ Not Before: Jun 30 16:46:09 2016 GMT
+ Not After : Jun 25 16:46:09 2036 GMT
+ Subject: C=US, ST=New York, O=ACME, OU=Coyote, CN=cert1.domain.local
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (2048 bit)
+ Modulus:
+ 00:d6:0f:bd:26:ef:14:4d:09:f6:db:8b:01:f5:4e:
+ 6c:03:b1:35:20:16:b8:1b:7c:e6:b6:8d:97:1b:b0:
+ 4f:8a:b6:cb:54:7e:7a:ff:fd:af:02:db:bf:9d:cf:
+ 9a:4c:0d:87:93:8b:cc:61:f3:23:a9:6f:8e:d4:82:
+ 2c:93:b6:e2:fa:37:ed:8a:d3:23:8f:6d:b5:78:4a:
+ 38:ba:93:f9:4a:1c:40:06:33:d7:c0:98:20:d4:16:
+ ac:a4:a5:6b:41:20:4c:3a:55:7e:c7:50:e7:95:07:
+ 4e:86:15:86:7a:0f:6c:57:d2:07:1c:97:24:51:5b:
+ 4e:f5:52:3a:f8:4f:95:6b:6c:83:1f:34:4e:ee:b0:
+ ae:fe:46:90:38:f1:4d:85:72:8b:46:bc:d1:62:37:
+ 65:5a:de:bb:16:51:1e:f5:cb:a0:ef:d6:7b:11:6f:
+ 3b:0c:49:17:bc:4d:8c:f5:d9:f0:35:6b:f7:b6:4d:
+ 50:eb:47:81:e3:06:f2:bd:ec:67:4f:ab:2b:03:aa:
+ e2:1e:42:22:a9:c9:59:dc:0d:19:fb:c5:02:1d:d7:
+ 58:e4:04:53:0a:1d:79:bb:c1:33:f1:cd:b7:10:2e:
+ b4:6e:9b:dc:60:66:05:50:9f:20:66:a1:71:00:51:
+ 54:cf:0a:70:f4:7c:45:c6:f0:a7:1c:11:2f:3e:a3:
+ 1f:bf
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ 2D:FB:27:C7:B4:32:FF:F7:87:DB:2D:A7:76:AE:F0:96:7E:DA:DC:17
+ X509v3 Authority Key Identifier:
+ keyid:4F:2A:15:49:E6:CC:05:2F:2B:F4:0E:CC:BA:2E:4C:DF:13:90:F0:78
+
+ Signature Algorithm: sha256WithRSAEncryption
+ 3f:46:1c:3b:58:b4:99:f3:75:00:47:d2:fe:ba:ba:9a:04:46:
+ 62:b6:2d:a0:0f:8f:c0:95:2a:58:8b:61:f5:14:90:30:26:37:
+ 94:a1:a6:29:20:c9:b5:08:d7:f9:15:cb:9d:9c:19:ed:2f:a4:
+ e6:91:48:85:1a:f7:ab:17:5e:79:23:69:b8:3c:0c:48:ae:c8:
+ ba:90:d0:05:fb:33:7e:86:fd:12:f8:2d:0f:ff:16:15:9a:dc:
+ 76:48:7d:65:5b:4e:93:14:e8:be:37:d1:13:f7:a7:b1:cd:ad:
+ ae:4f:e1:72:b9:53:2d:cd:e6:42:76:44:93:21:28:58:c0:44:
+ ab:3c:da:5b:e5:55:ab:04:86:4d:9c:4c:33:f4:4e:13:98:e9:
+ 0f:d1:a3:70:2b:1d:11:20:47:26:f6:d8:45:7f:88:ad:f2:c1:
+ 81:0f:be:cd:6c:79:80:94:30:eb:8d:cc:f3:7d:a1:3e:6c:6f:
+ fa:8f:f3:1f:2e:76:97:3f:8a:1b:67:3b:e0:f9:b1:3c:6b:dc:
+ 64:1b:00:73:e9:89:81:f6:7f:51:f3:51:c8:b9:96:5f:fd:55:
+ f8:77:6f:88:bc:65:b3:e2:30:a4:00:7a:79:68:e0:36:8b:a9:
+ 1b:06:9b:20:fe:fe:98:aa:56:58:c8:08:a4:7b:12:59:ff:3d:
+ bd:5e:13:3b:c6:c7:8a:00:5b:cb:27:18:02:ee:cb:38:c2:b7:
+ a9:51:04:ef:31:ca:49:09:48:14:13:eb:91:e2:26:8c:88:5f:
+ 1c:78:e1:0d:90:29:d7:c1:fc:c8:89:fd:4d:53:0b:99:58:c2:
+ 1a:24:3d:c0:a2:4c:a3:d9:c7:95:c5:bc:72:fa:02:f1:ab:dd:
+ aa:2b:9e:a0:bb:1a:68:2d:09:8c:a2:99:0d:26:ec:9e:30:19:
+ 01:5a:41:45:63:b3:c5:db:24:32:4c:fe:7f:f3:ce:e9:4d:00:
+ 64:cf:bb:15:34:2d:31:6e:4f:c0:96:40:9b:32:35:65:92:01:
+ 29:7e:74:02:50:fd:3b:3b:3a:a3:9f:6a:c0:a5:be:3f:c3:07:
+ d6:8c:2a:c6:f4:0f:32:bd:3b:fc:45:90:d2:46:ee:6f:c3:2f:
+ 26:8c:97:0c:e8:da:9a:97:03:0b:86:17:45:a6:62:69:4e:8d:
+ cf:f8:bf:ea:2f:dc:ff:95:14:15:bd:92:2d:8a:08:cf:ce:8a:
+ b0:f6:34:0a:a2:0e:49:31:44:e1:47:fb:37:52:53:59:93:25:
+ 40:cc:ac:67:2d:a2:b6:9b:75:fd:13:a5:a7:93:4f:72:05:75:
+ cd:b1:37:f6:3b:69:3b:24:a1:1f:23:f0:cd:bb:ae:18:b3:aa:
+ eb:9f:d7:97:06:ba:fd:44
+-----BEGIN CERTIFICATE-----
+MIIExjCCAq6gAwIBAgIBATANBgkqhkiG9w0BAQsFADBzMQswCQYDVQQGEwJVUzER
+MA8GA1UECAwITmV3IFlvcmsxETAPBgNVBAcMCE5ldyBZb3JrMRAwDgYDVQQKDAdB
+Q01FIENBMQ8wDQYDVQQLDAZDb3lvdGUxGzAZBgNVBAMMEm91cmNhLmRvbWFpbi5s
+b2NhbDAeFw0xNjA2MzAxNjQ2MDlaFw0zNjA2MjUxNjQ2MDlaMF0xCzAJBgNVBAYT
+AlVTMREwDwYDVQQIDAhOZXcgWW9yazENMAsGA1UECgwEQUNNRTEPMA0GA1UECwwG
+Q295b3RlMRswGQYDVQQDDBJjZXJ0MS5kb21haW4ubG9jYWwwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDWD70m7xRNCfbbiwH1TmwDsTUgFrgbfOa2jZcb
+sE+KtstUfnr//a8C27+dz5pMDYeTi8xh8yOpb47UgiyTtuL6N+2K0yOPbbV4Sji6
+k/lKHEAGM9fAmCDUFqykpWtBIEw6VX7HUOeVB06GFYZ6D2xX0gcclyRRW071Ujr4
+T5VrbIMfNE7usK7+RpA48U2FcotGvNFiN2Va3rsWUR71y6Dv1nsRbzsMSRe8TYz1
+2fA1a/e2TVDrR4HjBvK97GdPqysDquIeQiKpyVncDRn7xQId11jkBFMKHXm7wTPx
+zbcQLrRum9xgZgVQnyBmoXEAUVTPCnD0fEXG8KccES8+ox+/AgMBAAGjezB5MAkG
+A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp
+ZmljYXRlMB0GA1UdDgQWBBQt+yfHtDL/94fbLad2rvCWftrcFzAfBgNVHSMEGDAW
+gBRPKhVJ5swFLyv0Dsy6LkzfE5DweDANBgkqhkiG9w0BAQsFAAOCAgEAP0YcO1i0
+mfN1AEfS/rq6mgRGYrYtoA+PwJUqWIth9RSQMCY3lKGmKSDJtQjX+RXLnZwZ7S+k
+5pFIhRr3qxdeeSNpuDwMSK7IupDQBfszfob9EvgtD/8WFZrcdkh9ZVtOkxTovjfR
+E/ensc2trk/hcrlTLc3mQnZEkyEoWMBEqzzaW+VVqwSGTZxMM/ROE5jpD9GjcCsd
+ESBHJvbYRX+IrfLBgQ++zWx5gJQw643M832hPmxv+o/zHy52lz+KG2c74PmxPGvc
+ZBsAc+mJgfZ/UfNRyLmWX/1V+HdviLxls+IwpAB6eWjgNoupGwabIP7+mKpWWMgI
+pHsSWf89vV4TO8bHigBbyycYAu7LOMK3qVEE7zHKSQlIFBPrkeImjIhfHHjhDZAp
+18H8yIn9TVMLmVjCGiQ9wKJMo9nHlcW8cvoC8avdqiueoLsaaC0JjKKZDSbsnjAZ
+AVpBRWOzxdskMkz+f/PO6U0AZM+7FTQtMW5PwJZAmzI1ZZIBKX50AlD9Ozs6o59q
+wKW+P8MH1owqxvQPMr07/EWQ0kbub8MvJoyXDOjampcDC4YXRaZiaU6Nz/i/6i/c
+/5UUFb2SLYoIz86KsPY0CqIOSTFE4Uf7N1JTWZMlQMysZy2itpt1/ROlp5NPcgV1
+zbE39jtpOyShHyPwzbuuGLOq65/Xlwa6/UQ=
+-----END CERTIFICATE-----
diff --git a/test/units/modules/network/f5/fixtures/create_insecure_key1.key b/test/units/modules/network/f5/fixtures/create_insecure_key1.key
new file mode 100644
index 00000000000..a89a29161c8
--- /dev/null
+++ b/test/units/modules/network/f5/fixtures/create_insecure_key1.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEA1g+9Ju8UTQn224sB9U5sA7E1IBa4G3zmto2XG7BPirbLVH56
+//2vAtu/nc+aTA2Hk4vMYfMjqW+O1IIsk7bi+jftitMjj221eEo4upP5ShxABjPX
+wJgg1BaspKVrQSBMOlV+x1DnlQdOhhWGeg9sV9IHHJckUVtO9VI6+E+Va2yDHzRO
+7rCu/kaQOPFNhXKLRrzRYjdlWt67FlEe9cug79Z7EW87DEkXvE2M9dnwNWv3tk1Q
+60eB4wbyvexnT6srA6riHkIiqclZ3A0Z+8UCHddY5ARTCh15u8Ez8c23EC60bpvc
+YGYFUJ8gZqFxAFFUzwpw9HxFxvCnHBEvPqMfvwIDAQABAoIBAQCjQ7PP+y8vpvbp
+8bbXoy2ND15mkA1xoazR9WIYEzxHny2rzx//GTyfYH1gXtPfR75tEYYb+vbrJxP4
+DyTysN2jXH7HkEwh+9oZ2fo0i+Hp3WwTjvzyftUjDfw1Q5lvPbQGFekxGgrXRpBk
+ggxkEllfDeiwrLJdftfVEhe6BfD/0YibwQeHN7VoC4V8wOanKtDmx74W/1f7WhwQ
+nKQnCrbYqNJa2nGvWiKU5Suvfb0v7tCnQYlfnCpUfj+wcnxlgmGkcyq1L+qC1qC8
+PO5i3T3LM5Yg8CSeGhO/q6gw/fUowuBN1cluTqN97oLHiEM5tLdjeVWwa1Vp0liv
+1WXGT4eBAoGBAPtumMmyVTIorvV6KGNI/Eo6jfE0HOXVdXtm4iToDDuiYwto7/Ge
+/kV+11Fpu0lV+eYPfZn175Of8FnQPwczQF1OOH/aQ/ViY8j87bZUbCy25mWrfNkh
+2rRlyI3/OsSfL5SkyWpYB0yhSJZV9mSQJTZolB4GQRNPKtqi7NpB4WxBAoGBANnz
+VS4JBJO75yeSG5BzPp5VVKm+nu0Betlva8GsHdEic8OM9bGpVozGysAW3Xdxp7q6
+gLJGyyuzpsxldCc/IdIlF5fz7gkLl4NoYanz9PSEr2XZLh9+2yXGkPFlC3IeHAUB
+E+2UO9MFpWrmfKoAnYZCR6vJDxtQBpAlTUvJEYv/AoGBAPha62K32327P+7MJl7D
+9ijgI9rwjebcbbpiCtlHuOWi5lCb6/7v/NvqiYcqeEvdOAXuoTNWAbsBTel5UPis
+wFQp8pcfouccs9IRPEFQrLWSSIx+0sirrxtoOq1AQe18DAS4rRd1MmiYG1ocOVBm
+LcvLixsJNHh9R6hFLM3+K0vBAoGANkmJ+gF9Bl9TYGPgQcay3jVa9Tzp0RcBRo+e
+Q4tfkewG8bp2qF4JlN8fOWF4oHvKz5QM4lsH2EbTUS4kFHKBNhrPGaZEsDQW9UBW
+s0J0zUMPfUrvViD+7RXcnIQSqcYeLJDsKc02aYWKgmoOuzmUAxEXUQ6vmJoCSH1C
+F5JpsHkCgYEArwTSzb1+/ThQhK1JN8hJ4jMjQ8E7PzLTMILrdDALn2g1T4VzL7N7
+UG6oUieMlo/UH6cv6330dwaGVklXZbyDKSDROIafFcOpVfcvDUgJCjp3CaY9A2zG
++EPkRpeHKXAIgG+QuOwVOtYWcWltnBf61slTqiY2vKX1+ZGmrMrw1Zw=
+-----END RSA PRIVATE KEY-----
diff --git a/test/units/modules/network/f5/test_bigip_ssl_certificate.py b/test/units/modules/network/f5/test_bigip_ssl_certificate.py
new file mode 100644
index 00000000000..5a34ae2cef4
--- /dev/null
+++ b/test/units/modules/network/f5/test_bigip_ssl_certificate.py
@@ -0,0 +1,248 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2017 F5 Networks Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import os
+import json
+import sys
+
+from nose.plugins.skip import SkipTest
+if sys.version_info < (2, 7):
+ raise SkipTest("F5 Ansible modules require Python >= 2.7")
+
+from ansible.compat.tests import unittest
+from ansible.module_utils import basic
+from ansible.compat.tests.mock import patch, Mock
+from ansible.module_utils._text import to_bytes
+from ansible.module_utils.f5_utils import AnsibleF5Client
+
+try:
+ from library.bigip_ssl_certificate import ArgumentSpec
+ from library.bigip_ssl_certificate import KeyParameters
+ from library.bigip_ssl_certificate import CertParameters
+ from library.bigip_ssl_certificate import CertificateManager
+ from library.bigip_ssl_certificate import KeyManager
+except ImportError:
+ try:
+ from ansible.modules.network.f5.bigip_ssl_certificate import ArgumentSpec
+ from ansible.modules.network.f5.bigip_ssl_certificate import KeyParameters
+ from ansible.modules.network.f5.bigip_ssl_certificate import CertParameters
+ from ansible.modules.network.f5.bigip_ssl_certificate import CertificateManager
+ from ansible.modules.network.f5.bigip_ssl_certificate import KeyManager
+ except ImportError:
+ raise SkipTest("F5 Ansible modules require the f5-sdk Python library")
+
+fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
+fixture_data = {}
+
+
+def set_module_args(args):
+ args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
+ basic._ANSIBLE_ARGS = to_bytes(args)
+
+
+def load_fixture(name):
+ path = os.path.join(fixture_path, name)
+
+ if path in fixture_data:
+ return fixture_data[path]
+
+ with open(path) as f:
+ data = f.read()
+
+ try:
+ data = json.loads(data)
+ except Exception:
+ pass
+
+ fixture_data[path] = data
+ return data
+
+
+class TestParameters(unittest.TestCase):
+ def test_module_parameters_key(self):
+ key_content = load_fixture('create_insecure_key1.key')
+ args = dict(
+ key_content=key_content,
+ name="cert1",
+ partition="Common",
+ state="present",
+ password='password',
+ server='localhost',
+ user='admin'
+ )
+ p = KeyParameters(args)
+ assert p.name == 'cert1'
+ assert p.key_filename == 'cert1.key'
+ assert '-----BEGIN RSA PRIVATE KEY-----' in p.key_content
+ assert '-----END RSA PRIVATE KEY-----' in p.key_content
+ assert p.key_checksum == '91bdddcf0077e2bb2a0258aae2ae3117be392e83'
+ assert p.state == 'present'
+ assert p.user == 'admin'
+ assert p.server == 'localhost'
+ assert p.password == 'password'
+ assert p.partition == 'Common'
+
+ def test_module_parameters_cert(self):
+ cert_content = load_fixture('create_insecure_cert1.crt')
+ args = dict(
+ cert_content=cert_content,
+ name="cert1",
+ partition="Common",
+ state="present",
+ password='password',
+ server='localhost',
+ user='admin'
+ )
+ p = CertParameters(args)
+ assert p.name == 'cert1'
+ assert p.cert_filename == 'cert1.crt'
+ assert 'Signature Algorithm' in p.cert_content
+ assert '-----BEGIN CERTIFICATE-----' in p.cert_content
+ assert '-----END CERTIFICATE-----' in p.cert_content
+ assert p.cert_checksum == '1e55aa57ee166a380e756b5aa4a835c5849490fe'
+ assert p.state == 'present'
+ assert p.user == 'admin'
+ assert p.server == 'localhost'
+ assert p.password == 'password'
+ assert p.partition == 'Common'
+
+
+@patch('ansible.module_utils.f5_utils.AnsibleF5Client._get_mgmt_root',
+ return_value=True)
+class TestCertificateManager(unittest.TestCase):
+
+ def setUp(self):
+ self.spec = ArgumentSpec()
+
+ def test_import_certificate_and_key_no_key_passphrase(self, *args):
+ set_module_args(dict(
+ name='foo',
+ cert_content=load_fixture('cert1.crt'),
+ key_content=load_fixture('cert1.key'),
+ state='present',
+ password='passsword',
+ server='localhost',
+ user='admin'
+ ))
+
+ client = AnsibleF5Client(
+ argument_spec=self.spec.argument_spec,
+ supports_check_mode=self.spec.supports_check_mode,
+ f5_product_name=self.spec.f5_product_name
+ )
+
+ # Override methods in the specific type of manager
+ cm = CertificateManager(client)
+ cm.exists = Mock(side_effect=[False, True])
+ cm.create_on_device = Mock(return_value=True)
+
+ results = cm.exec_module()
+
+ assert results['changed'] is True
+
+ def test_update_certificate_new_certificate_and_key_password_protected_key(self, *args):
+ set_module_args(dict(
+ name='foo',
+ cert_content=load_fixture('cert2.crt'),
+ key_content=load_fixture('cert2.key'),
+ state='present',
+ passphrase='keypass',
+ password='passsword',
+ server='localhost',
+ user='admin'
+ ))
+
+ client = AnsibleF5Client(
+ argument_spec=self.spec.argument_spec,
+ supports_check_mode=self.spec.supports_check_mode,
+ f5_product_name=self.spec.f5_product_name
+ )
+
+ # Override methods in the specific type of manager
+ cm = CertificateManager(client)
+ cm.exists = Mock(side_effect=[False, True])
+ cm.create_on_device = Mock(return_value=True)
+
+ results = cm.exec_module()
+
+ assert results['changed'] is True
+
+
+@patch('ansible.module_utils.f5_utils.AnsibleF5Client._get_mgmt_root',
+ return_value=True)
+class TestKeyManager(unittest.TestCase):
+
+ def setUp(self):
+ self.spec = ArgumentSpec()
+
+ def test_import_certificate_and_key_no_key_passphrase(self, *args):
+ set_module_args(dict(
+ name='foo',
+ cert_content=load_fixture('cert1.crt'),
+ key_content=load_fixture('cert1.key'),
+ state='present',
+ password='passsword',
+ server='localhost',
+ user='admin'
+ ))
+
+ client = AnsibleF5Client(
+ argument_spec=self.spec.argument_spec,
+ supports_check_mode=self.spec.supports_check_mode,
+ f5_product_name=self.spec.f5_product_name
+ )
+
+ # Override methods in the specific type of manager
+ cm = KeyManager(client)
+ cm.exists = Mock(side_effect=[False, True])
+ cm.create_on_device = Mock(return_value=True)
+
+ results = cm.exec_module()
+
+ assert results['changed'] is True
+
+ def test_update_certificate_new_certificate_and_key_password_protected_key(self, *args):
+ set_module_args(dict(
+ name='foo',
+ cert_content=load_fixture('cert2.crt'),
+ key_content=load_fixture('cert2.key'),
+ state='present',
+ passphrase='keypass',
+ password='passsword',
+ server='localhost',
+ user='admin'
+ ))
+
+ client = AnsibleF5Client(
+ argument_spec=self.spec.argument_spec,
+ supports_check_mode=self.spec.supports_check_mode,
+ f5_product_name=self.spec.f5_product_name
+ )
+
+ # Override methods in the specific type of manager
+ cm = KeyManager(client)
+ cm.exists = Mock(side_effect=[False, True])
+ cm.create_on_device = Mock(return_value=True)
+
+ results = cm.exec_module()
+
+ assert results['changed'] is True