diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:03:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:03:42 +0000 |
commit | 66cec45960ce1d9c794e9399de15c138acb18aed (patch) | |
tree | 59cd19d69e9d56b7989b080da7c20ef1a3fe2a5a /ansible_collections/cisco/nso/plugins/modules | |
parent | Initial commit. (diff) | |
download | ansible-66cec45960ce1d9c794e9399de15c138acb18aed.tar.xz ansible-66cec45960ce1d9c794e9399de15c138acb18aed.zip |
Adding upstream version 7.3.0+dfsg.upstream/7.3.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/nso/plugins/modules')
6 files changed, 1024 insertions, 0 deletions
diff --git a/ansible_collections/cisco/nso/plugins/modules/__init__.py b/ansible_collections/cisco/nso/plugins/modules/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/ansible_collections/cisco/nso/plugins/modules/__init__.py diff --git a/ansible_collections/cisco/nso/plugins/modules/nso_action.py b/ansible_collections/cisco/nso/plugins/modules/nso_action.py new file mode 100644 index 00000000..b5db2084 --- /dev/null +++ b/ansible_collections/cisco/nso/plugins/modules/nso_action.py @@ -0,0 +1,211 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 <http://www.gnu.org/licenses/>. +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_action +extends_documentation_fragment: +- cisco.nso.nso + +short_description: Executes Cisco NSO actions and verifies output. +description: + - This module provides support for executing Cisco NSO actions and then + verifying that the output is as expected. +requirements: + - Cisco NSO version 3.4 or higher. +author: "Claes Nästén (@cnasten)" +options: + path: + description: Path to NSO action. + required: true + type: str + input: + description: > + NSO action parameters. + type: dict + output_required: + description: > + Required output parameters. + type: dict + output_invalid: + description: > + List of result parameter names that will cause the task to fail if they + are present. + type: dict + validate_strict: + description: > + If set to true, the task will fail if any output parameters not in + output_required is present in the output. + type: bool + default: False +''' + +EXAMPLES = ''' +- name: Sync NSO device + cisco.nso.nso_action: + url: https://10.10.20.49/jsonrpc + username: developer + password: C1sco12345 + path: /ncs:devices/device{dist-rtr01}/sync-from + input: {} + +- name: Check device sync + cisco.nso.nso_action: + url: https://10.10.20.49/jsonrpc + username: developer + password: C1sco12345 + path: /ncs:devices/check-sync + input: {} + +- name: Load Native Config + cisco.nso.nso_action: + url: "https://10.10.20.49/jsonrpc" + username: developer + password: C1sco12345 + path: /ncs:devices/ncs:device{dist-rtr01}/load-native-config + input: { file: "/home/developer/test.cfg" , verbose: true, mode: "merge"} + register: result +''' + +RETURN = ''' +output: + description: Action output + returned: success + type: dict + sample: + result: true +''' + +from ansible_collections.cisco.nso.plugins.module_utils.nso import connect, verify_version, nso_argument_spec +from ansible_collections.cisco.nso.plugins.module_utils.nso import normalize_value +from ansible_collections.cisco.nso.plugins.module_utils.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoAction(object): + + REQUIRED_VERSIONS = [ + (3, 4) + ] + + def __init__(self, check_mode, client, + path, input, + output_required, output_invalid, validate_strict): + self._check_mode = check_mode + self._client = client + self._path = path + self._input = input + self._output_required = output_required + self._output_invalid = output_invalid + self._validate_strict = validate_strict + + def main(self): + schema = self._client.get_schema(path=self._path) + if schema['data']['kind'] != 'action': + raise ModuleFailException('{0} is not an action'.format(self._path)) + input_schema = [] + for c in schema['data']['children']: + if c.get('is_action_input', False): + if c['kind'] == 'choice': + for case in c['cases']: + input_schema.append(case) + else: + input_schema.append(c) + + for key, value in self._input.items(): + child = next((c for c in input_schema if c['name'] == key), None) + if child is None: + raise ModuleFailException("unsupported input parameter '{0}'".format(key)) + + # implement type validation in the future + + if self._check_mode: + return {} + else: + return self._run_and_verify() + + def _run_and_verify(self): + output = self._client.run_action(None, self._path, self._input) + for key, value in self._output_required.items(): + if key not in output: + raise ModuleFailException('{0} not in result'.format(key)) + + n_value = normalize_value(value, output[key], key) + if value != n_value: + msg = '{0} value mismatch. expected {1} got {2}'.format( + key, value, n_value) + raise ModuleFailException(msg) + + for key in self._output_invalid.keys(): + if key in output: + raise ModuleFailException('{0} not allowed in result'.format(key)) + + if self._validate_strict: + for name in output.keys(): + if name not in self._output_required: + raise ModuleFailException('{0} not allowed in result'.format(name)) + + return output + + +def main(): + argument_spec = dict( + path=dict(required=True), + input=dict(required=False, type='dict', default={}), + output_required=dict(required=False, type='dict', default={}), + output_invalid=dict(required=False, type='dict', default={}), + validate_strict=dict(required=False, type='bool', default=False) + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_action = NsoAction( + module.check_mode, client, + p['path'], + p['input'], + p['output_required'], + p['output_invalid'], + p['validate_strict']) + try: + verify_version(client, NsoAction.REQUIRED_VERSIONS) + + output = nso_action.main() + client.logout() + module.exit_json(changed=True, output=output) + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/cisco/nso/plugins/modules/nso_config.py b/ansible_collections/cisco/nso/plugins/modules/nso_config.py new file mode 100644 index 00000000..01ee40ec --- /dev/null +++ b/ansible_collections/cisco/nso/plugins/modules/nso_config.py @@ -0,0 +1,338 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 <http://www.gnu.org/licenses/>. +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_config +extends_documentation_fragment: +- cisco.nso.nso + +short_description: Manage Cisco NSO configuration and service synchronization. +description: + - This module provides support for managing configuration in Cisco NSO and + can also ensure services are in sync. +requirements: + - Cisco NSO version 3.4.12 or higher, 4.2.7 or higher, + 4.3.8 or higher, 4.4.3 or higher, 4.5 or higher. +author: "Claes Nästén (@cnasten)" +options: + data: + description: > + NSO data in format as | display json converted to YAML. List entries can + be annotated with a __state entry. Set to in-sync/deep-in-sync for + services to verify service is in sync with the network. Set to absent in + list entries to ensure they are deleted if they exist in NSO. + required: true + type: dict + commit_flags: + description: > + A list containing commit flags. See the API documentation for + supported commit flags. + https://developer.cisco.com/docs/nso/guides/#!life-cycle-operations-how-to-manipulate-existing-services-and-devices/commit-flags-and-device-service-actions + type: list + elements: str +''' + +EXAMPLES = ''' +- name: CREATE DEVICE IN NSO + cisco.nso.nso_config: + url: https://10.10.20.49/jsonrpc + username: developer + password: C1sco12345 + data: + tailf-ncs:devices: + device: + - address: 10.10.20.175 + description: CONFIGURED BY ANSIBLE! + name: dist-rtr01 + authgroup: "labadmin" + device-type: + cli: + ned-id: "cisco-ios-cli-6.44" + port: "22" + state: + admin-state: "unlocked" + +- name: ADD NEW LOOPBACK + cisco.nso.nso_config: + url: https://10.10.20.49/jsonrpc + username: developer + password: C1sco12345 + data: + tailf-ncs:devices: + device: + - name: dist-rtr01 + config: + tailf-ned-cisco-ios:interface: + Loopback: + - name: "1" + description: Created by Ansible! + +- name: CONFIGURE IP ADDRESS ON LOOPBACK + cisco.nso.nso_config: + url: https://10.10.20.49/jsonrpc + username: developer + password: C1sco12345 + data: + tailf-ncs:devices: + device: + - name: dist-rtr01 + config: + tailf-ned-cisco-ios:interface: + Loopback: + - name: "1" + description: Created by Ansible! + ip: + address: + primary: + address: 10.10.10.10 + mask: 255.255.255.255 + +- name: CONFIGURE NTP SERVER ON DEVICE + cisco.nso.nso_config: + url: https://10.10.20.49/jsonrpc + username: developer + password: C1sco12345 + data: + tailf-ncs:devices: + device: + - name: dist-rtr01 + config: + tailf-ned-cisco-ios:ntp: + server: + peer-list: + - name: 2.2.2.2 +''' + +RETURN = ''' +changes: + description: List of changes + returned: always + type: complex + sample: + - path: "/ncs:devices/device{dist-rtr01}/config/ios:interface/Loopback{1}/ip/address/primary/address" + from: null + to: "10.10.10.10" + type: set + contains: + path: + description: Path to value changed + returned: always + type: str + from: + description: Previous value if any, else null + returned: When previous value is present on value change + type: str +diffs: + description: List of sync changes + returned: always + type: complex + contains: + path: + description: keypath to service changed + returned: always + type: str + diff: + description: configuration difference triggered the re-deploy + returned: always + type: str +commit_result: + description: Return values from commit operation + returned: always + type: complex + contains: + commit_queue: + description: Commit queue ID and status, if any + returned: When commit-queue is set in commit_flags + type: dict + sample: + - { + "commit_queue": { + "id": 1611776004976, + "status": "async" + } + } + +''' + +from ansible_collections.cisco.nso.plugins.module_utils.nso import connect, verify_version, nso_argument_spec +from ansible_collections.cisco.nso.plugins.module_utils.nso import State, ValueBuilder +from ansible_collections.cisco.nso.plugins.module_utils.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoConfig(object): + REQUIRED_VERSIONS = [ + (4, 5), + (4, 4, 3), + (4, 3, 8), + (4, 2, 7), + (3, 4, 12) + ] + + def __init__(self, check_mode, client, data, commit_flags): + self._check_mode = check_mode + self._client = client + self._data = data + self._commit_flags = commit_flags + + self._changes = [] + self._diffs = [] + self._commit_result = [] + + def main(self): + # build list of values from configured data + value_builder = ValueBuilder(self._client) + for key, value in self._data.items(): + value_builder.build('', key, value) + + self._data_write(value_builder.values) + + # check sync AFTER configuration is written + sync_values = self._sync_check(value_builder.values) + self._sync_ensure(sync_values) + + return self._changes, self._diffs, self._commit_result + + def _data_write(self, values): + th = self._client.get_trans(mode='read_write') + + for value in values: + if value.state == State.SET: + self._client.set_value(th, value.path, value.value) + elif value.state == State.PRESENT: + self._client.create(th, value.path) + elif value.state == State.ABSENT: + self._client.delete(th, value.path) + + changes = self._client.get_trans_changes(th) + for change in changes: + if change['op'] == 'value_set': + self._changes.append({ + 'path': change['path'], + 'from': change['old'] or None, + 'to': change['value'], + 'type': 'set' + }) + elif change['op'] in ('created', 'deleted'): + self._changes.append({ + 'path': change['path'], + 'type': change['op'][:-1] + }) + + if len(changes) > 0: + # Fix for validate_commit method not working with commit flags prior to 5.4. + # If version < 5.4 then don't send the flags to validate_commit + version = float(self._client._version[0:self._client._version.find('.') + 2:]) + if version >= 5.4: + warnings = self._client.validate_commit(th, self._commit_flags) + else: + warnings = self._client.validate_commit(th) + if len(warnings) > 0: + raise NsoException( + 'failed to validate transaction with warnings: {0}'.format( + ', '.join((str(warning) for warning in warnings))), {}) + if self._check_mode or len(changes) == 0: + self._client.delete_trans(th) + else: + if self._commit_flags: + result = self._client.commit(th, self._commit_flags) + self._commit_result.append(result) + else: + result = self._client.commit(th) + self._commit_result.append(result) + + def _sync_check(self, values): + sync_values = [] + + for value in values: + if value.state in (State.CHECK_SYNC, State.IN_SYNC): + action = 'check-sync' + elif value.state in (State.DEEP_CHECK_SYNC, State.DEEP_IN_SYNC): + action = 'deep-check-sync' + else: + action = None + + if action is not None: + action_path = '{0}/{1}'.format(value.path, action) + action_params = {'outformat': 'cli'} + resp = self._client.run_action(None, action_path, action_params) + if len(resp) > 0: + sync_values.append( + ValueBuilder.Value(value.path, value.state, resp[0]['value'])) + + return sync_values + + def _sync_ensure(self, sync_values): + for value in sync_values: + if value.state in (State.CHECK_SYNC, State.DEEP_CHECK_SYNC): + raise NsoException( + '{0} out of sync, diff {1}'.format(value.path, value.value), {}) + + action_path = '{0}/{1}'.format(value.path, 're-deploy') + if not self._check_mode: + result = self._client.run_action(None, action_path) + if not result: + raise NsoException( + 'failed to re-deploy {0}'.format(value.path), {}) + + self._changes.append({'path': value.path, 'type': 're-deploy'}) + self._diffs.append({'path': value.path, 'diff': value.value}) + + +def main(): + argument_spec = dict( + data=dict(required=True, type='dict'), + commit_flags=dict(required=False, type='list', elements='str') + ) + + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + client = connect(p) + nso_config = NsoConfig(module.check_mode, client, p['data'], p['commit_flags']) + try: + verify_version(client, NsoConfig.REQUIRED_VERSIONS) + + changes, diffs, commit_result = nso_config.main() + client.logout() + + changed = len(changes) > 0 + module.exit_json( + changed=changed, changes=changes, diffs=diffs, commit_result=commit_result) + + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/cisco/nso/plugins/modules/nso_query.py b/ansible_collections/cisco/nso/plugins/modules/nso_query.py new file mode 100644 index 00000000..5f2d26f5 --- /dev/null +++ b/ansible_collections/cisco/nso/plugins/modules/nso_query.py @@ -0,0 +1,129 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 <http://www.gnu.org/licenses/>. +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_query +extends_documentation_fragment: +- cisco.nso.nso + +short_description: Query data from Cisco NSO. +description: + - This module provides support for querying data from Cisco NSO using XPath. +requirements: + - Cisco NSO version 3.4 or higher. +author: "Claes Nästén (@cnasten)" +options: + xpath: + description: XPath selection relative to the root. + required: true + type: str + fields: + description: > + List of fields to select from matching nodes. + required: true + type: list + elements: str +''' + +EXAMPLES = ''' +- name: QUERY DEVICES DISPLAYING NAME AND DESCRIPTION + cisco.nso.nso_query: + url: https://10.10.20.49/jsonrpc + username: developer + password: C1sco12345 + xpath: /ncs:devices/device + fields: + - name + - description + register: nso_query_result + +- name: DISPLAY NSO_QUERY RESULT + debug: + var: nso_query_result +''' + +RETURN = ''' +output: + description: Value of matching nodes + returned: success + type: list +''' + +from ansible_collections.cisco.nso.plugins.module_utils.nso import connect, verify_version, nso_argument_spec +from ansible_collections.cisco.nso.plugins.module_utils.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoQuery(object): + REQUIRED_VERSIONS = [ + (3, 4) + ] + + def __init__(self, check_mode, client, xpath, fields): + self._check_mode = check_mode + self._client = client + self._xpath = xpath + self._fields = fields + + def main(self): + if self._check_mode: + return [] + else: + return self._client.query(self._xpath, self._fields) + + +def main(): + argument_spec = dict( + xpath=dict(required=True, type='str'), + fields=dict(required=True, type='list', elements='str') + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_query = NsoQuery( + module.check_mode, client, + p['xpath'], p['fields']) + try: + verify_version(client, NsoQuery.REQUIRED_VERSIONS) + + output = nso_query.main() + client.logout() + module.exit_json(changed=False, output=output) + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/cisco/nso/plugins/modules/nso_show.py b/ansible_collections/cisco/nso/plugins/modules/nso_show.py new file mode 100644 index 00000000..e9d8963d --- /dev/null +++ b/ansible_collections/cisco/nso/plugins/modules/nso_show.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 <http://www.gnu.org/licenses/>. +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_show +extends_documentation_fragment: +- cisco.nso.nso + +short_description: Displays data from Cisco NSO. +description: + - This module provides support for displaying data from Cisco NSO. +requirements: + - Cisco NSO version 3.4.12 or higher, 4.1.9 or higher, 4.2.6 or higher, + 4.3.7 or higher, 4.4.5 or higher, 4.5 or higher. +author: "Claes Nästén (@cnasten)" +options: + path: + description: Path to NSO data. + required: true + type: str + operational: + description: > + Controls whether or not operational data is included in the result. + type: bool + default: false +''' + +EXAMPLES = ''' +- name: DISPLAY DEVICE INCLUDING OPERATIONAL DATA + cisco.nso.nso_show: + url: https://10.10.20.49/jsonrpc + username: developer + password: C1sco12345 + path: /ncs:devices/device{dist-rtr01} + operational: true + register: result + +- name: Display the result + debug: + var: result + +- name: DISPLAY INTERFACES + cisco.nso.nso_show: + url: "https://10.10.20.49/jsonrpc" + username: developer + password: C1sco12345 + path: /ncs:devices/device{dist-rtr01}/config/interface + operational: true + register: result + +- name: Display the result + debug: + var: result +''' + +RETURN = ''' +output: + description: Configuration + returned: success + type: dict +''' + +from ansible_collections.cisco.nso.plugins.module_utils.nso import connect, verify_version, nso_argument_spec +from ansible_collections.cisco.nso.plugins.module_utils.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoShow(object): + REQUIRED_VERSIONS = [ + (4, 5), + (4, 4, 5), + (4, 3, 7), + (4, 2, 6), + (4, 1, 9), + (3, 4, 12) + ] + + def __init__(self, check_mode, client, path, operational): + self._check_mode = check_mode + self._client = client + self._path = path + self._operational = operational + + def main(self): + if self._check_mode: + return {} + else: + return self._client.show_config(self._path, self._operational) + + +def main(): + argument_spec = dict( + path=dict(required=True, type='str'), + operational=dict(required=False, type='bool', default=False) + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_show = NsoShow( + module.check_mode, client, + p['path'], p['operational']) + try: + verify_version(client, NsoShow.REQUIRED_VERSIONS) + + output = nso_show.main() + client.logout() + module.exit_json(changed=False, output=output) + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() diff --git a/ansible_collections/cisco/nso/plugins/modules/nso_verify.py b/ansible_collections/cisco/nso/plugins/modules/nso_verify.py new file mode 100644 index 00000000..03759706 --- /dev/null +++ b/ansible_collections/cisco/nso/plugins/modules/nso_verify.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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 <http://www.gnu.org/licenses/>. +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: nso_verify +extends_documentation_fragment: +- cisco.nso.nso + +short_description: Verifies Cisco NSO configuration. +description: + - This module provides support for verifying Cisco NSO configuration is in + compliance with specified values. +requirements: + - Cisco NSO version 3.4.12 or higher, 4.2.7 or higher, + 4.3.8 or higher, 4.4.3 or higher, 4.5 or higher. +author: "Claes Nästén (@cnasten)" +options: + data: + description: > + NSO data in format as C(| display json) converted to YAML. List entries can + be annotated with a C(__state) entry. Set to in-sync/deep-in-sync for + services to verify service is in sync with the network. Set to absent in + list entries to ensure they are deleted if they exist in NSO. + required: true + type: dict +''' + +EXAMPLES = ''' +- name: VERIFY INTERFACE IS ADMINISTRATIVELY UP + cisco.nso.nso_verify: + url: http://localhost:8080/jsonrpc + username: username + password: password + data: + tailf-ncs:devices: + device: + - name: dist-sw01 + config: + interface: + Ethernet: + - name: "1/1" + shutdown: false +''' + +RETURN = ''' +violations: + description: List of value violations + returned: failed + type: complex + sample: + - path: /ncs:devices/device{dist-sw01}/config/interface/Ethernet{1/1}/shutdown + expected-value: false + value: true + contains: + path: + description: Path to the value in violation + returned: always + type: str + expected-value: + description: Expected value of path + returned: always + type: str + value: + description: Current value of path + returned: always + type: str +''' + +from ansible_collections.cisco.nso.plugins.module_utils.nso import connect, verify_version, nso_argument_spec +from ansible_collections.cisco.nso.plugins.module_utils.nso import normalize_value +from ansible_collections.cisco.nso.plugins.module_utils.nso import State, ValueBuilder +from ansible_collections.cisco.nso.plugins.module_utils.nso import ModuleFailException, NsoException +from ansible.module_utils.basic import AnsibleModule + + +class NsoVerify(object): + REQUIRED_VERSIONS = [ + (4, 5), + (4, 4, 3), + (4, 3, 8), + (4, 2, 7), + (3, 4, 12) + ] + + def __init__(self, client, data): + self._client = client + self._data = data + + def main(self): + violations = [] + + # build list of values from configured data + value_builder = ValueBuilder(self._client, 'verify') + for key, value in self._data.items(): + value_builder.build('', key, value) + + for expected_value in value_builder.values: + if expected_value.state == State.PRESENT: + violations.append({ + 'path': expected_value.path, + 'expected-value': 'present', + 'value': 'absent' + }) + elif expected_value.state == State.ABSENT: + violations.append({ + 'path': expected_value.path, + 'expected-value': 'absent', + 'value': 'present' + }) + elif expected_value.state == State.SET: + try: + value = self._client.get_value(expected_value.path)['value'] + except NsoException as ex: + if ex.error.get('type', '') == 'data.not_found': + value = None + else: + raise + + # handle different types properly + n_value = normalize_value( + expected_value.value, value, expected_value.path) + if n_value != expected_value.value: + # if the value comparison fails, try mapping identityref + value_type = value_builder.get_type(expected_value.path) + if value_type is not None and 'identityref' in value_type: + n_value, t_value = self.get_prefix_name(value) + + if expected_value.value != n_value: + violations.append({ + 'path': expected_value.path, + 'expected-value': expected_value.value, + 'value': n_value + }) + else: + raise ModuleFailException( + 'value state {0} not supported at {1}'.format( + expected_value.state, expected_value.path)) + + return violations + + +def main(): + argument_spec = dict( + data=dict(required=True, type='dict') + ) + argument_spec.update(nso_argument_spec) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + p = module.params + + client = connect(p) + nso_verify = NsoVerify(client, p['data']) + try: + verify_version(client, NsoVerify.REQUIRED_VERSIONS) + + violations = nso_verify.main() + client.logout() + + num_violations = len(violations) + if num_violations > 0: + msg = '{0} value{1} differ'.format( + num_violations, num_violations > 1 and 's' or '') + module.fail_json(msg=msg, violations=violations) + else: + module.exit_json(changed=False) + + except NsoException as ex: + client.logout() + module.fail_json(msg=ex.message) + except ModuleFailException as ex: + client.logout() + module.fail_json(msg=ex.message) + + +if __name__ == '__main__': + main() |