diff --git a/lib/ansible/module_utils/netscaler.py b/lib/ansible/module_utils/netscaler.py index eaef706ddba..486cfbd9535 100644 --- a/lib/ansible/module_utils/netscaler.py +++ b/lib/ansible/module_utils/netscaler.py @@ -30,6 +30,7 @@ import json import re +import sys from ansible.module_utils.basic import env_fallback from ansible.module_utils.six import binary_type, text_type @@ -57,12 +58,20 @@ class ConfigProxy(object): self.attribute_values_processed = {} for attribute, value in self.attribute_values_dict.items(): + if value is None: + continue if attribute in transforms: for transform in self.transforms[attribute]: if transform == 'bool_yes_no': - value = 'YES' if value is True else 'NO' + if value is True: + value = 'YES' + elif value is False: + value = 'NO' elif transform == 'bool_on_off': - value = 'ON' if value is True else 'OFF' + if value is True: + value = 'ON' + elif value is False: + value = 'OFF' elif callable(transform): value = transform(value) else: @@ -130,7 +139,7 @@ class ConfigProxy(object): # Compare values param_type = self.attribute_values_processed[attribute].__class__ - if param_type(attribute_value) != self.attribute_values_processed[attribute]: + if attribute_value is None or param_type(attribute_value) != self.attribute_values_processed[attribute]: str_tuple = ( type(self.attribute_values_processed[attribute]), self.attribute_values_processed[attribute], @@ -180,6 +189,10 @@ def get_immutables_intersection(config_proxy, keys): def ensure_feature_is_enabled(client, feature_str): enabled_features = client.get_enabled_features() + + if enabled_features is None: + enabled_features = [] + if feature_str not in enabled_features: client.enable_features(feature_str) client.save_config() @@ -257,6 +270,12 @@ def get_ns_version(client): return int(m.group(1)), int(m.group(2)) +def get_ns_hardware(client): + from nssrc.com.citrix.netscaler.nitro.resource.config.ns.nshardware import nshardware + result = nshardware.get(client) + return result + + def monkey_patch_nitro_api(): from nssrc.com.citrix.netscaler.nitro.resource.base.Json import Json diff --git a/lib/ansible/modules/network/netscaler/netscaler_ssl_certkey.py b/lib/ansible/modules/network/netscaler/netscaler_ssl_certkey.py new file mode 100644 index 00000000000..006208ac015 --- /dev/null +++ b/lib/ansible/modules/network/netscaler/netscaler_ssl_certkey.py @@ -0,0 +1,370 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Citrix Systems +# 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 = {'status': ['preview'], + 'supported_by': 'community', + 'metadata_version': '1.0'} + + +DOCUMENTATION = ''' +--- +module: netscaler_ssl_certkey +short_description: Manage ssl cerificate keys. +description: + - Manage ssl cerificate keys. + +version_added: "2.4.0" + +author: George Nikolopoulos (@giorgos-nikolopoulos) + +options: + + certkey: + description: + - >- + Name for the certificate and private-key pair. Must begin with an ASCII alphanumeric or underscore + C(_) character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), + colon C(:), at C(@), equals C(=), and hyphen C(-) characters. Cannot be changed after the certificate-key + pair is created. + - "The following requirement applies only to the NetScaler CLI:" + - >- + If the name includes one or more spaces, enclose the name in double or single quotation marks (for + example, "my cert" or 'my cert'). + - "Minimum length = 1" + + cert: + description: + - >- + Name of and, optionally, path to the X509 certificate file that is used to form the certificate-key + pair. The certificate file should be present on the appliance's hard-disk drive or solid-state drive. + Storing a certificate in any location other than the default might cause inconsistency in a high + availability setup. /nsconfig/ssl/ is the default path. + - "Minimum length = 1" + + key: + description: + - >- + Name of and, optionally, path to the private-key file that is used to form the certificate-key pair. + The certificate file should be present on the appliance's hard-disk drive or solid-state drive. + Storing a certificate in any location other than the default might cause inconsistency in a high + availability setup. /nsconfig/ssl/ is the default path. + - "Minimum length = 1" + + password: + description: + - >- + Passphrase that was used to encrypt the private-key. Use this option to load encrypted private-keys + in PEM format. + + inform: + choices: + - 'DER' + - 'PEM' + - 'PFX' + description: + - >- + Input format of the certificate and the private-key files. The three formats supported by the + appliance are: + - "PEM - Privacy Enhanced Mail" + - "DER - Distinguished Encoding Rule" + - "PFX - Personal Information Exchange." + + passplain: + description: + - >- + Pass phrase used to encrypt the private-key. Required when adding an encrypted private-key in PEM + format. + - "Minimum length = 1" + + expirymonitor: + choices: + - 'enabled' + - 'disabled' + description: + - "Issue an alert when the certificate is about to expire." + + notificationperiod: + description: + - >- + Time, in number of days, before certificate expiration, at which to generate an alert that the + certificate is about to expire. + - "Minimum value = C(10)" + - "Maximum value = C(100)" + + +extends_documentation_fragment: netscaler +requirements: + - nitro python sdk +''' + +EXAMPLES = ''' + +- name: Setup ssl certkey + delegate_to: localhost + netscaler_ssl_certkey: + nitro_user: nsroot + nitro_pass: nsroot + nsip: 172.18.0.2 + + certkey: certirificate_1 + cert: server.crt + key: server.key + expirymonitor: enabled + notificationperiod: 30 + inform: PEM + password: False + passplain: somesecret +''' + +RETURN = ''' +loglines: + description: list of logged messages by the module + returned: always + type: list + sample: "['message 1', 'message 2']" + +msg: + description: Message detailing the failure reason + returned: failure + type: string + sample: "Action does not exist" + +diff: + description: List of differences between the actual configured object and the configuration specified in the module + returned: failure + type: dictionary + sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }" +''' + +try: + from nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslcertkey import sslcertkey + from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception + PYTHON_SDK_IMPORTED = True +except ImportError as e: + PYTHON_SDK_IMPORTED = False + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.netscaler import ConfigProxy, get_nitro_client, netscaler_common_arguments, log, loglines, get_immutables_intersection + + +def key_exists(client, module): + log('Checking if key exists') + log('certkey is %s' % module.params['certkey']) + all_certificates = sslcertkey.get(client) + certkeys = [item.certkey for item in all_certificates] + if module.params['certkey'] in certkeys: + return True + else: + return False + + +def key_identical(client, module, sslcertkey_proxy): + log('Checking if configured key is identical') + sslcertkey_list = sslcertkey.get_filtered(client, 'certkey:%s' % module.params['certkey']) + diff_dict = sslcertkey_proxy.diff_object(sslcertkey_list[0]) + if 'password' in diff_dict: + del diff_dict['password'] + if 'passplain' in diff_dict: + del diff_dict['passplain'] + if len(diff_dict) == 0: + return True + else: + return False + + +def diff_list(client, module, sslcertkey_proxy): + sslcertkey_list = sslcertkey.get_filtered(client, 'certkey:%s' % module.params['certkey']) + return sslcertkey_proxy.diff_object(sslcertkey_list[0]) + + +def main(): + + module_specific_arguments = dict( + certkey=dict(type='str'), + cert=dict(type='str'), + key=dict(type='str'), + password=dict(type='bool'), + inform=dict( + type='str', + choices=[ + 'DER', + 'PEM', + 'PFX', + ] + ), + passplain=dict( + type='str', + no_log=True, + ), + expirymonitor=dict( + type='str', + choices=[ + 'enabled', + 'disabled', + ] + ), + notificationperiod=dict(type='float'), + ) + + argument_spec = dict() + + argument_spec.update(netscaler_common_arguments) + + argument_spec.update(module_specific_arguments) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + module_result = dict( + changed=False, + failed=False, + loglines=loglines, + ) + + # Fail the module if imports failed + if not PYTHON_SDK_IMPORTED: + module.fail_json(msg='Could not load nitro python sdk') + + # Fallthrough to rest of execution + client = get_nitro_client(module) + + try: + client.login() + except nitro_exception as e: + msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg) + except Exception as e: + if str(type(e)) == "": + module.fail_json(msg='Connection error %s' % str(e)) + elif str(type(e)) == "": + module.fail_json(msg='SSL Error %s' % str(e)) + else: + module.fail_json(msg='Unexpected error during login %s' % str(e)) + + readwrite_attrs = [ + 'certkey', + 'cert', + 'key', + 'password', + 'inform', + 'passplain', + 'expirymonitor', + 'notificationperiod', + ] + + readonly_attrs = [ + 'signaturealg', + 'certificatetype', + 'serial', + 'issuer', + 'clientcertnotbefore', + 'clientcertnotafter', + 'daystoexpiration', + 'subject', + 'publickey', + 'publickeysize', + 'version', + 'priority', + 'status', + 'passcrypt', + 'data', + 'servicename', + ] + + immutable_attrs = [ + 'certkey', + 'cert', + 'key', + 'password', + 'inform', + 'passplain', + ] + + transforms = { + 'expirymonitor': [lambda v: v.upper()], + } + + # Instantiate config proxy + sslcertkey_proxy = ConfigProxy( + actual=sslcertkey(), + client=client, + attribute_values_dict=module.params, + readwrite_attrs=readwrite_attrs, + readonly_attrs=readonly_attrs, + immutable_attrs=immutable_attrs, + transforms=transforms, + ) + + try: + + if module.params['state'] == 'present': + log('Applying actions for state present') + if not key_exists(client, module): + if not module.check_mode: + log('Adding certificate key') + sslcertkey_proxy.add() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + elif not key_identical(client, module, sslcertkey_proxy): + + # Check if we try to change value of immutable attributes + immutables_changed = get_immutables_intersection(sslcertkey_proxy, diff_list(client, module, sslcertkey_proxy).keys()) + if immutables_changed != []: + module.fail_json( + msg='Cannot update immutable attributes %s' % (immutables_changed,), + diff=diff_list(client, module, sslcertkey_proxy), + **module_result + ) + + if not module.check_mode: + sslcertkey_proxy.update() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state present') + if not key_exists(client, module): + module.fail_json(msg='SSL certkey does not exist') + if not key_identical(client, module, sslcertkey_proxy): + module.fail_json(msg='SSL certkey differs from configured', diff=diff_list(client, module, sslcertkey_proxy)) + + elif module.params['state'] == 'absent': + log('Applying actions for state absent') + if key_exists(client, module): + if not module.check_mode: + sslcertkey_proxy.delete() + if module.params['save_config']: + client.save_config() + module_result['changed'] = True + else: + module_result['changed'] = False + + # Sanity check for state + if not module.check_mode: + log('Sanity checks for state absent') + if key_exists(client, module): + module.fail_json(msg='SSL certkey still exists') + + except nitro_exception as e: + msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message) + module.fail_json(msg=msg, **module_result) + + client.logout() + module.exit_json(**module_result) + + +if __name__ == "__main__": + main() diff --git a/test/integration/roles/netscaler_ssl_certkey/defaults/main.yaml b/test/integration/roles/netscaler_ssl_certkey/defaults/main.yaml new file mode 100644 index 00000000000..641801f6600 --- /dev/null +++ b/test/integration/roles/netscaler_ssl_certkey/defaults/main.yaml @@ -0,0 +1,6 @@ +--- +testcase: "*" +test_cases: [] + +nitro_user: nsroot +nitro_pass: nsroot diff --git a/test/integration/roles/netscaler_ssl_certkey/sample_inventory b/test/integration/roles/netscaler_ssl_certkey/sample_inventory new file mode 100644 index 00000000000..42635796914 --- /dev/null +++ b/test/integration/roles/netscaler_ssl_certkey/sample_inventory @@ -0,0 +1,5 @@ + + +[netscaler] + +netscaler01 nsip=172.18.0.2 nitro_user=nsroot nitro_pass=nsroot diff --git a/test/integration/roles/netscaler_ssl_certkey/tasks/main.yaml b/test/integration/roles/netscaler_ssl_certkey/tasks/main.yaml new file mode 100644 index 00000000000..84af8dca9e9 --- /dev/null +++ b/test/integration/roles/netscaler_ssl_certkey/tasks/main.yaml @@ -0,0 +1,3 @@ +--- + +- { include: nitro.yaml, tags: ['nitro'] } diff --git a/test/integration/roles/netscaler_ssl_certkey/tasks/nitro.yaml b/test/integration/roles/netscaler_ssl_certkey/tasks/nitro.yaml new file mode 100644 index 00000000000..00ab502dda9 --- /dev/null +++ b/test/integration/roles/netscaler_ssl_certkey/tasks/nitro.yaml @@ -0,0 +1,14 @@ +- name: collect all nitro test cases + find: + paths: "{{ role_path }}/tests/nitro" + patterns: "{{ testcase }}.yaml" + register: test_cases + +- name: set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: run test case + include: "{{ test_case_to_run }}" + with_items: "{{ test_items }}" + loop_control: + loop_var: test_case_to_run diff --git a/test/integration/roles/netscaler_ssl_certkey/tests/nitro/certkey.yaml b/test/integration/roles/netscaler_ssl_certkey/tests/nitro/certkey.yaml new file mode 100644 index 00000000000..cda63836438 --- /dev/null +++ b/test/integration/roles/netscaler_ssl_certkey/tests/nitro/certkey.yaml @@ -0,0 +1,57 @@ +--- + +- include: "{{ role_path }}/tests/nitro/certkey/setup.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/certkey/setup.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/certkey/setup.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/certkey/setup.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/certkey/remove.yaml" + vars: + check_mode: yes + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/certkey/remove.yaml" + vars: + check_mode: no + +- assert: + that: result|changed + +- include: "{{ role_path }}/tests/nitro/certkey/remove.yaml" + vars: + check_mode: yes + +- assert: + that: not result|changed + +- include: "{{ role_path }}/tests/nitro/certkey/remove.yaml" + vars: + check_mode: no + +- assert: + that: not result|changed diff --git a/test/integration/roles/netscaler_ssl_certkey/tests/nitro/certkey/remove.yaml b/test/integration/roles/netscaler_ssl_certkey/tests/nitro/certkey/remove.yaml new file mode 100644 index 00000000000..9224a6ad7dc --- /dev/null +++ b/test/integration/roles/netscaler_ssl_certkey/tests/nitro/certkey/remove.yaml @@ -0,0 +1,13 @@ +--- + +- name: Setup cs action + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_ssl_certkey: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: absent + certkey: certirificate_1 diff --git a/test/integration/roles/netscaler_ssl_certkey/tests/nitro/certkey/setup.yaml b/test/integration/roles/netscaler_ssl_certkey/tests/nitro/certkey/setup.yaml new file mode 100644 index 00000000000..1a0a00e2ac0 --- /dev/null +++ b/test/integration/roles/netscaler_ssl_certkey/tests/nitro/certkey/setup.yaml @@ -0,0 +1,22 @@ +--- + + +- name: Setup cs action + delegate_to: localhost + register: result + check_mode: "{{ check_mode }}" + netscaler_ssl_certkey: + nitro_user: "{{nitro_user}}" + nitro_pass: "{{nitro_pass}}" + nsip: "{{nsip}}" + + state: present + + certkey: certirificate_1 + cert: server.crt + key: server.key + expirymonitor: enabled + notificationperiod: 30 + inform: PEM + password: False + passplain: somesecret diff --git a/test/units/modules/network/netscaler/test_netscaler_ssl_certkey.py b/test/units/modules/network/netscaler/test_netscaler_ssl_certkey.py new file mode 100644 index 00000000000..311a15aa81f --- /dev/null +++ b/test/units/modules/network/netscaler/test_netscaler_ssl_certkey.py @@ -0,0 +1,618 @@ + +# Copyright (c) 2017 Citrix Systems +# +# 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 ansible.compat.tests.mock import patch, Mock, MagicMock, call +from .netscaler_module import TestModule, nitro_base_patcher, set_module_args + +import sys + +if sys.version_info[:2] != (2, 6): + import requests + + +class TestNetscalerSSLCertkeyModule(TestModule): + + @classmethod + def setUpClass(cls): + class MockException(Exception): + pass + + cls.MockException = MockException + + m = MagicMock() + cls.server_mock = MagicMock() + cls.server_mock.__class__ = MagicMock(add=Mock()) + nssrc_modules_mock = { + 'nssrc.com.citrix.netscaler.nitro.resource.config.ssl': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslcertkey': m, + 'nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslcertkey.sslcertkey': m, + } + + cls.nitro_specific_patcher = patch.dict(sys.modules, nssrc_modules_mock) + cls.nitro_base_patcher = nitro_base_patcher + + @classmethod + def tearDownClass(cls): + cls.nitro_base_patcher.stop() + cls.nitro_specific_patcher.stop() + + def setUp(self): + self.nitro_base_patcher.start() + self.nitro_specific_patcher.start() + + # Setup minimal required arguments to pass AnsibleModule argument parsing + + def tearDown(self): + self.nitro_base_patcher.stop() + self.nitro_specific_patcher.stop() + + def test_graceful_nitro_api_import_error(self): + # Stop nitro api patching to cause ImportError + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + self.nitro_base_patcher.stop() + self.nitro_specific_patcher.stop() + from ansible.modules.network.netscaler import netscaler_ssl_certkey + self.module = netscaler_ssl_certkey + result = self.failed() + self.assertEqual(result['msg'], 'Could not load nitro python sdk') + + def test_graceful_nitro_error_on_login(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + class MockException(Exception): + def __init__(self, *args, **kwargs): + self.errorcode = 0 + self.message = '' + + client_mock = Mock() + client_mock.login = Mock(side_effect=MockException) + m = Mock(return_value=client_mock) + with patch('ansible.modules.network.netscaler.netscaler_ssl_certkey.get_nitro_client', m): + with patch('ansible.modules.network.netscaler.netscaler_ssl_certkey.nitro_exception', MockException): + self.module = netscaler_ssl_certkey + result = self.failed() + self.assertTrue(result['msg'].startswith('nitro exception'), msg='nitro exception during login not handled properly') + + def test_graceful_no_connection_error(self): + + if sys.version_info[:2] == (2, 6): + self.skipTest('requests library not available under python2.6') + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + class MockException(Exception): + pass + client_mock = Mock() + attrs = {'login.side_effect': requests.exceptions.ConnectionError} + client_mock.configure_mock(**attrs) + m = Mock(return_value=client_mock) + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + get_nitro_client=m, + nitro_exception=MockException, + ): + self.module = netscaler_ssl_certkey + result = self.failed() + self.assertTrue(result['msg'].startswith('Connection error'), msg='Connection error was not handled gracefully') + + def test_graceful_login_error(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + if sys.version_info[:2] == (2, 6): + self.skipTest('requests library not available under python2.6') + + class MockException(Exception): + pass + client_mock = Mock() + attrs = {'login.side_effect': requests.exceptions.SSLError} + client_mock.configure_mock(**attrs) + m = Mock(return_value=client_mock) + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + get_nitro_client=m, + nitro_exception=MockException, + ): + self.module = netscaler_ssl_certkey + result = self.failed() + self.assertTrue(result['msg'].startswith('SSL Error'), msg='SSL Error was not handled gracefully') + + def test_save_config_called_on_state_present(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + get_nitro_client=m, + key_exists=Mock(side_effect=[False, True]), + key_identical=Mock(side_effect=[True]), + ConfigProxy=Mock(return_value=ssl_certkey_proxy_mock), + nitro_exception=self.MockException, + ): + self.module = netscaler_ssl_certkey + self.exited() + self.assertIn(call.save_config(), client_mock.mock_calls) + + def test_save_config_called_on_state_absent(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='absent', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + get_nitro_client=m, + key_exists=Mock(side_effect=[True, False]), + ConfigProxy=Mock(return_value=ssl_certkey_proxy_mock), + ): + self.module = netscaler_ssl_certkey + self.exited() + self.assertIn(call.save_config(), client_mock.mock_calls) + + def test_save_config_not_called_on_state_present(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + save_config=False, + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + get_nitro_client=m, + key_exists=Mock(side_effect=[False, True]), + key_identical=Mock(side_effect=[True]), + ConfigProxy=Mock(return_value=ssl_certkey_proxy_mock), + nitro_exception=self.MockException, + ): + self.module = netscaler_ssl_certkey + self.exited() + self.assertNotIn(call.save_config(), client_mock.mock_calls) + + def test_save_config_not_called_on_state_absent(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='absent', + save_config=False, + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_mock = Mock() + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + get_nitro_client=m, + key_exists=Mock(side_effect=[True, False]), + ConfigProxy=Mock(return_value=ssl_certkey_proxy_mock), + ): + self.module = netscaler_ssl_certkey + self.exited() + self.assertNotIn(call.save_config(), client_mock.mock_calls) + + def test_new_ssl_certkey_execution_flow(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_attrs = { + 'diff_object.return_value': {}, + } + ssl_certkey_proxy_mock = Mock() + ssl_certkey_proxy_mock.configure_mock(**ssl_certkey_proxy_attrs) + config_proxy_mock = Mock(return_value=ssl_certkey_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + get_nitro_client=m, + key_exists=Mock(side_effect=[False, True]), + key_identical=Mock(side_effect=[True]), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_ssl_certkey + self.exited() + ssl_certkey_proxy_mock.assert_has_calls([call.add()]) + + def test_modified_server_execution_flow(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_attrs = { + 'diff_object.return_value': {}, + } + ssl_certkey_proxy_mock = Mock() + ssl_certkey_proxy_mock.configure_mock(**ssl_certkey_proxy_attrs) + config_proxy_mock = Mock(return_value=ssl_certkey_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + key_exists=Mock(side_effect=[True, True]), + key_identical=Mock(side_effect=[False, True]), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_ssl_certkey + self.exited() + ssl_certkey_proxy_mock.assert_has_calls([call.update()]) + + def test_absent_server_execution_flow(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='absent', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_attrs = { + 'diff_object.return_value': {}, + } + ssl_certkey_proxy_mock = Mock() + ssl_certkey_proxy_mock.configure_mock(**ssl_certkey_proxy_attrs) + config_proxy_mock = Mock(return_value=ssl_certkey_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + key_exists=Mock(side_effect=[True, False]), + key_identical=Mock(side_effect=[False, True]), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_ssl_certkey + self.exited() + ssl_certkey_proxy_mock.assert_has_calls([call.delete()]) + + def test_present_key_identical_flow(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_attrs = { + 'diff_object.return_value': {}, + } + ssl_certkey_proxy_mock = Mock() + ssl_certkey_proxy_mock.configure_mock(**ssl_certkey_proxy_attrs) + config_proxy_mock = Mock(return_value=ssl_certkey_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + key_exists=Mock(side_effect=[True, True]), + key_identical=Mock(side_effect=[True, True]), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_ssl_certkey + self.exited() + ssl_certkey_proxy_mock.assert_not_called() + + def test_absent_server_noop_flow(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='absent', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_attrs = { + 'diff_object.return_value': {}, + } + ssl_certkey_proxy_mock = Mock() + ssl_certkey_proxy_mock.configure_mock(**ssl_certkey_proxy_attrs) + config_proxy_mock = Mock(return_value=ssl_certkey_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + key_exists=Mock(side_effect=[False, False]), + key_identical=Mock(side_effect=[False, False]), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_ssl_certkey + self.exited() + ssl_certkey_proxy_mock.assert_not_called() + + def test_present_server_failed_update(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_attrs = { + 'diff_object.return_value': {}, + } + ssl_certkey_proxy_mock = Mock() + ssl_certkey_proxy_mock.configure_mock(**ssl_certkey_proxy_attrs) + config_proxy_mock = Mock(return_value=ssl_certkey_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + key_exists=Mock(side_effect=[True, True]), + key_identical=Mock(side_effect=[False, False]), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_ssl_certkey + result = self.failed() + self.assertEqual(result['msg'], 'SSL certkey differs from configured') + self.assertTrue(result['failed']) + + def test_present_server_failed_create(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_attrs = { + 'diff_object.return_value': {}, + } + ssl_certkey_proxy_mock = Mock() + ssl_certkey_proxy_mock.configure_mock(**ssl_certkey_proxy_attrs) + config_proxy_mock = Mock(return_value=ssl_certkey_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + key_exists=Mock(side_effect=[False, False]), + key_identical=Mock(side_effect=[False, False]), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_ssl_certkey + result = self.failed() + self.assertEqual(result['msg'], 'SSL certkey does not exist') + self.assertTrue(result['failed']) + + def test_present_server_update_immutable_attribute(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_attrs = { + 'diff_object.return_value': {}, + } + ssl_certkey_proxy_mock = Mock() + ssl_certkey_proxy_mock.configure_mock(**ssl_certkey_proxy_attrs) + config_proxy_mock = Mock(return_value=ssl_certkey_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=['domain']), + key_exists=Mock(side_effect=[True, True]), + key_identical=Mock(side_effect=[False, False]), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_ssl_certkey + result = self.failed() + self.assertEqual(result['msg'], 'Cannot update immutable attributes [\'domain\']') + self.assertTrue(result['failed']) + + def test_absent_server_failed_delete(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='absent', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + client_mock = Mock() + + m = Mock(return_value=client_mock) + + ssl_certkey_proxy_attrs = { + 'diff_object.return_value': {}, + } + ssl_certkey_proxy_mock = Mock() + ssl_certkey_proxy_mock.configure_mock(**ssl_certkey_proxy_attrs) + config_proxy_mock = Mock(return_value=ssl_certkey_proxy_mock) + + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + nitro_exception=self.MockException, + get_nitro_client=m, + diff_list=Mock(return_value={}), + get_immutables_intersection=Mock(return_value=[]), + key_exists=Mock(side_effect=[True, True]), + key_identical=Mock(side_effect=[False, False]), + ConfigProxy=config_proxy_mock, + ): + self.module = netscaler_ssl_certkey + result = self.failed() + self.assertEqual(result['msg'], 'SSL certkey still exists') + self.assertTrue(result['failed']) + + def test_graceful_nitro_exception_state_present(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='present', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + class MockException(Exception): + def __init__(self, *args, **kwargs): + self.errorcode = 0 + self.message = '' + + m = Mock(side_effect=MockException) + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + key_exists=m, + nitro_exception=MockException + ): + self.module = netscaler_ssl_certkey + result = self.failed() + self.assertTrue( + result['msg'].startswith('nitro exception'), + msg='Nitro exception not caught on operation absent' + ) + + def test_graceful_nitro_exception_state_absent(self): + set_module_args(dict( + nitro_user='user', + nitro_pass='pass', + nsip='1.1.1.1', + state='absent', + )) + from ansible.modules.network.netscaler import netscaler_ssl_certkey + + class MockException(Exception): + def __init__(self, *args, **kwargs): + self.errorcode = 0 + self.message = '' + + m = Mock(side_effect=MockException) + with patch.multiple( + 'ansible.modules.network.netscaler.netscaler_ssl_certkey', + key_exists=m, + nitro_exception=MockException + ): + self.module = netscaler_ssl_certkey + result = self.failed() + self.assertTrue( + result['msg'].startswith('nitro exception'), + msg='Nitro exception not caught on operation absent' + )