diff --git a/README.md b/README.md index c9884f8327..593ebf175f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The following table shows the supported versions. | 2.2.2.3 | 3.3.1 | 2.3.3 | | 2.2.3.3 | 6.4.0 | 2.4.11 | | 2.3.3.0 | 6.6.4 | 2.5.5 | -| 2.3.5.3 | 6.7.4 | 2.6.0 | +| 2.3.5.3 | 6.7.5 | 2.6.0 | If your Ansible collection is older please consider updating it first. diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 488a79f5fa..8f16937e35 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -703,4 +703,13 @@ releases: changes: release_summary: Updated sanity test. minor_changes: - - Updated test/sanity and remove unnecessary \ No newline at end of file + - Updated test/sanity and remove unnecessary + 6.7.5: + release_date: "2023-09-25" + changes: + release_summary: Updated different function names. + bugfixes: + - Updated from get_permissions_ap_i to get_permissions_api + - Updated from get_roles_ap_i to get_roles_api + - Updated from get_users_ap_i to get_users_api + - Updated from get_external_authentication_servers_ap_i to get_external_authentication_servers_api \ No newline at end of file diff --git a/galaxy.yml b/galaxy.yml index 016a7da845..07683c4ad4 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: cisco name: dnac -version: 6.7.4 +version: 6.7.5 readme: README.md authors: - Rafael Campos diff --git a/playbooks/user_info.yml b/playbooks/user_info.yml new file mode 100644 index 0000000000..da08c51c9b --- /dev/null +++ b/playbooks/user_info.yml @@ -0,0 +1,18 @@ +- hosts: dnac_servers + vars_files: + - credentials.yml + gather_facts: false + connection: local + tasks: + - name: Get all User + cisco.dnac.user_info: + dnac_host: "{{dnac_host}}" + dnac_username: "{{dnac_username}}" + dnac_password: "{{dnac_password}}" + dnac_verify: "{{dnac_verify}}" + dnac_port: "{{dnac_port}}" + dnac_version: "{{dnac_version}}" + dnac_debug: "{{dnac_debug}}" + #headers: "{{my_headers | from_json}}" + invokeSource: string + register: result \ No newline at end of file diff --git a/plugins/action/role_permissions_info.py b/plugins/action/role_permissions_info.py index 39728ba461..a1b758c744 100644 --- a/plugins/action/role_permissions_info.py +++ b/plugins/action/role_permissions_info.py @@ -78,8 +78,8 @@ def run(self, tmp=None, task_vars=None): dnac = DNACSDK(params=self._task.args) response = dnac.exec( - family="userand_roles", - function='get_permissions_ap_i', + family="user_and_roles", + function='get_permissions_api', params=self.get_object(self._task.args), ) self._result.update(dict(dnac_response=response)) diff --git a/plugins/action/roles_info.py b/plugins/action/roles_info.py index 380c9c6875..4283de11b0 100644 --- a/plugins/action/roles_info.py +++ b/plugins/action/roles_info.py @@ -78,8 +78,8 @@ def run(self, tmp=None, task_vars=None): dnac = DNACSDK(params=self._task.args) response = dnac.exec( - family="userand_roles", - function='get_roles_ap_i', + family="user_and_roles", + function='get_roles_api', params=self.get_object(self._task.args), ) self._result.update(dict(dnac_response=response)) diff --git a/plugins/action/user.py b/plugins/action/user.py index b9c8d5c4e3..1896e57ffb 100644 --- a/plugins/action/user.py +++ b/plugins/action/user.py @@ -91,8 +91,8 @@ def get_object_by_name(self, name): # NOTE: Does not have a get by name method, using get all try: items = self.dnac.exec( - family="userand_roles", - function="get_users_ap_i", + family="user_and_roles", + function="get_users_api", params=self.get_all_params(name=name), ) if isinstance(items, dict): @@ -148,8 +148,8 @@ def requires_update(self, current_obj): def create(self): result = self.dnac.exec( - family="userand_roles", - function="add_user_ap_i", + family="user_and_roles", + function="add_user_api", params=self.create_params(), op_modifies=True, ) @@ -160,8 +160,8 @@ def update(self): name = self.new_object.get("name") result = None result = self.dnac.exec( - family="userand_roles", - function="update_user_ap_i", + family="user_and_roles", + function="update_user_api", params=self.update_all_params(), op_modifies=True, ) diff --git a/plugins/action/user_info.py b/plugins/action/user_info.py index eb9ccc38e8..7a5906fcce 100644 --- a/plugins/action/user_info.py +++ b/plugins/action/user_info.py @@ -80,8 +80,8 @@ def run(self, tmp=None, task_vars=None): dnac = DNACSDK(params=self._task.args) response = dnac.exec( - family="userand_roles", - function='get_users_ap_i', + family="user_and_roles", + function='get_users_api', params=self.get_object(self._task.args), ) self._result.update(dict(dnac_response=response)) diff --git a/plugins/action/users_external_servers_info.py b/plugins/action/users_external_servers_info.py index 1436338cc6..b29ce6bb24 100644 --- a/plugins/action/users_external_servers_info.py +++ b/plugins/action/users_external_servers_info.py @@ -80,8 +80,8 @@ def run(self, tmp=None, task_vars=None): dnac = DNACSDK(params=self._task.args) response = dnac.exec( - family="userand_roles", - function='get_external_authentication_servers_ap_i', + family="user_and_roles", + function='get_external_authentication_servers_api', params=self.get_object(self._task.args), ) self._result.update(dict(dnac_response=response)) diff --git a/plugins/module_utils/dnac.py b/plugins/module_utils/dnac.py index e9d61527de..46f157e879 100644 --- a/plugins/module_utils/dnac.py +++ b/plugins/module_utils/dnac.py @@ -21,13 +21,89 @@ else: LOGGING_IN_STANDARD = True import os.path +import copy import datetime import inspect -def log(msg): +class DnacBase: + """Class contains members which can be reused for all intent modules""" + def __init__(self, module): + self.module = module + self.params = module.params + self.config = copy.deepcopy(module.params.get("config")) + self.have_create = {} + self.want_create = {} + self.validated_config = [] + self.msg = "" + self.status = "success" + dnac_params = self.get_dnac_params(self.params) + self.dnac = DNACSDK(params=dnac_params) + self.dnac_apply = {'exec': self.dnac._exec} + self.get_diff_state_apply = {'merged': self.get_diff_merged, + 'deleted': self.get_diff_deleted} + self.dnac_log = dnac_params.get("dnac_log") + self.log(str(dnac_params)) + self.supported_states = ["merged", "deleted"] + self.result = {"changed": False, "diff": [], "response": [], "warnings": []} + + def log(self, message, frameIncrement=0): + """Log messages into dnac.log file""" + + if self.dnac_log: + message = "Module: " + self.__class__.__name__ + ", " + message + log(message, (1 + frameIncrement)) + + def check_return_status(self): + """API to check the return status value and exit/fail the module""" + + self.log("status: {0}, msg:{1}".format(self.status, self.msg), frameIncrement=1) + if "failed" in self.status: + self.module.fail_json(msg=self.msg, response=[]) + elif "exited" in self.status: + self.module.exit_json(**self.result) + elif "invalid" in self.status: + self.module.fail_json(msg=self.msg, response=[]) + + def get_dnac_params(self, params): + """Store the DNAC parameters from the playbook""" + + dnac_params = {"dnac_host": params.get("dnac_host"), + "dnac_port": params.get("dnac_port"), + "dnac_username": params.get("dnac_username"), + "dnac_password": params.get("dnac_password"), + "dnac_verify": params.get("dnac_verify"), + "dnac_debug": params.get("dnac_debug"), + "dnac_log": params.get("dnac_log") + } + return dnac_params + + def get_task_details(self, task_id): + """Check if the task performed is sucessfull or not""" + + result = None + response = self.dnac_apply['exec']( + family="task", + function="get_task_by_id", + params={"task_id": task_id}, + ) + + self.log(str(response)) + if isinstance(response, dict): + result = response.get("response") + + return result + + def reset_values(self): + """Reset all neccessary attributes to default values""" + + self.have_create.clear() + self.want_create.clear() + + +def log(msg, frameIncrement=0): with open('dnac.log', 'a') as of: - callerframerecord = inspect.stack()[1] + callerframerecord = inspect.stack()[1 + frameIncrement] frame = callerframerecord[0] info = inspect.getframeinfo(frame) d = datetime.datetime.now().replace(microsecond=0).isoformat() diff --git a/plugins/modules/role_permissions_info.py b/plugins/modules/role_permissions_info.py index 53d7a9b3ad..3f520a6972 100644 --- a/plugins/modules/role_permissions_info.py +++ b/plugins/modules/role_permissions_info.py @@ -28,7 +28,7 @@ link: https://developer.cisco.com/docs/dna-center/#!get-permissions-api notes: - SDK Method used are - userand_roles.UserandRoles.get_permissions_ap_i, + user_and_roles.UserandRoles.get_permissions_ap_i, - Paths used are get /dna/system/api/v1/role/permissions, diff --git a/plugins/modules/roles_info.py b/plugins/modules/roles_info.py index 279f9ed107..3e719be1a5 100644 --- a/plugins/modules/roles_info.py +++ b/plugins/modules/roles_info.py @@ -28,7 +28,7 @@ link: https://developer.cisco.com/docs/dna-center/#!get-roles-api notes: - SDK Method used are - userand_roles.UserandRoles.get_roles_ap_i, + user_and_roles.UserandRoles.get_roles_ap_i, - Paths used are get /dna/system/api/v1/roles, diff --git a/plugins/modules/template_intent.py b/plugins/modules/template_intent.py index 8a42f9271b..d713eae46b 100644 --- a/plugins/modules/template_intent.py +++ b/plugins/modules/template_intent.py @@ -1,13 +1,12 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - # Copyright (c) 2022, Cisco Systems # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) - +"""Ansible module to perform operations on project and templates in DNAC.""" from __future__ import absolute_import, division, print_function __metaclass__ = type -__author__ = ("Madhan Sankaranarayanan, Rishita Chowdhary") +__author__ = ['Madhan Sankaranarayanan, Rishita Chowdhary'] DOCUMENTATION = r""" --- @@ -52,7 +51,7 @@ description: Description of template. type: str deviceTypes: - description: Configuration Template Create's deviceTypes. + description: deviceTypes on which templates would be applied. type: list elements: dict suboptions: @@ -78,10 +77,14 @@ description: Name of template. type: str projectName: - description: Project name. + description: Name of the project under which templates are managed. + type: str + required: true + projectDescription: + description: Description of the project created. type: str rollbackTemplateParams: - description: Configuration Template Create's rollbackTemplateParams. + description: Params required for template rollback. type: list elements: dict suboptions: @@ -277,7 +280,7 @@ description: Description of template. type: str deviceTypes: - description: Configuration Template Create's deviceTypes. + description: Configuration Template Create's deviceTypes. This field is mandatory to create a new template. suboptions: productFamily: description: Device family. @@ -306,7 +309,7 @@ description: Latest versioned template time. type: int templateName: - description: Name of template. + description: Name of template. This field is mandatory to create a new template. type: str parentTemplateId: description: Parent templateID. @@ -317,6 +320,9 @@ projectName: description: Project name. type: str + projectDescription: + description: Project Description. + type: str rollbackTemplateContent: description: Rollback template content. type: str @@ -405,7 +411,7 @@ type: list elements: dict softwareType: - description: Applicable device software type. + description: Applicable device software type. This field is mandatory to create a new template. type: str softwareVariant: description: Applicable device software variant. @@ -582,6 +588,7 @@ parentTemplateId: string projectId: string projectName: string + projectDescription: string rollbackTemplateContent: string softwareType: string softwareVariant: string @@ -602,7 +609,7 @@ """ RETURN = r""" -#Case_1: Successful creation/updation/deletion of template +# Case_1: Successful creation/updation/deletion of template/project response_1: description: A dictionary with versioning details of the template as returned by the DNAC Python SDK returned: always @@ -625,7 +632,7 @@ "msg": String } -#Case_2: Error while deleting a template or when given project is not found +# Case_2: Error while deleting a template or when given project is not found response_2: description: A list with the response returned by the Cisco DNAC Python SDK returned: always @@ -636,7 +643,7 @@ "msg": String } -#Case_3: Given template already exists and requires no udpate +# Case_3: Given template already exists and requires no update response_3: description: A dictionary with the exisiting template deatails as returned by the Cisco DNAC Python SDK returned: always @@ -648,220 +655,335 @@ } """ -import copy +from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.dnac.plugins.module_utils.dnac import ( - DNACSDK, + DnacBase, validate_list_of_dicts, - log, get_dict_result, dnac_compare_equality, ) -from ansible.module_utils.basic import AnsibleModule - -class DnacTemplate: +class DnacTemplate(DnacBase): + """Class containing member attributes for template intent module""" def __init__(self, module): - self.module = module - self.params = module.params - self.config = copy.deepcopy(module.params.get("config")) - self.have_create = {} - self.want_create = {} - self.validated = [] - dnac_params = self.get_dnac_params(self.params) - log(str(dnac_params)) - self.dnac = DNACSDK(params=dnac_params) - self.log = dnac_params.get("dnac_log") - - self.result = dict(changed=False, diff=[], response=[], warnings=[]) - - def get_state(self): - return self.params.get("state") + super().__init__(module) + self.have_create_project = {} + self.have_create_template = {} + self.accepted_languages = ["JINJA", "VELOCITY"] def validate_input(self): - temp_spec = dict( - tags=dict(type="list"), - author=dict(type="str"), - composite=dict(type="bool"), - containingTemplates=dict(type="list"), - createTime=dict(type="int"), - customParamsOrder=dict(type="bool"), - description=dict(type="str"), - deviceTypes=dict(type="list", elements='dict'), - failurePolicy=dict(type="str"), - id=dict(type="str"), - language=dict(type="str"), - lastUpdateTime=dict(type="int"), - latestVersionTime=dict(type="int"), - name=dict(type="str"), - parentTemplateId=dict(type="str"), - projectId=dict(type="str"), - projectName=dict(required=True, type="str"), - rollbackTemplateContent=dict(type="str"), - rollbackTemplateParams=dict(type="list"), - softwareType=dict(type="str"), - softwareVariant=dict(type="str"), - softwareVersion=dict(type="str"), - templateContent=dict(type="str"), - templateParams=dict(type="list"), - templateName=dict(required=True, type='str'), - validationErrors=dict(type="dict"), - version=dict(type="str"), - versionDescription=dict(type='str'), + """Validate the fields provided in the playbook""" + + if not self.config: + self.msg = "config not available in playbook for validattion" + self.status = "success" + return self + + temp_spec = {'tags': {'type': 'list'}, + 'author': {'type': 'str'}, + 'composite': {'type': 'bool'}, + 'containingTemplates': {'type': 'list'}, + 'createTime': {'type': 'int'}, + 'customParamsOrder': {'type': 'bool'}, + 'description': {'type': 'str'}, + 'deviceTypes': {'type': 'list', 'elements': 'dict'}, + 'failurePolicy': {'type': 'str'}, + 'id': {'type': 'str'}, + 'language': {'type': 'str'}, + 'lastUpdateTime': {'type': 'int'}, + 'latestVersionTime': {'type': 'int'}, + 'name': {'type': 'str'}, + 'parentTemplateId': {'type': 'str'}, + 'projectId': {'type': 'str'}, + 'projectName': {'required': True, 'type': 'str'}, + 'projectDescription': {'type': 'str'}, + 'rollbackTemplateContent': {'type': 'str'}, + 'rollbackTemplateParams': {'type': 'list'}, + 'softwareType': {'type': 'str'}, + 'softwareVariant': {'type': 'str'}, + 'softwareVersion': {'type': 'str'}, + 'templateContent': {'type': 'str'}, + 'templateParams': {'type': 'list'}, + 'templateName': {'type': 'str'}, + 'validationErrors': {'type': 'dict'}, + 'version': {'type': 'str'}, + 'versionDescription': {'type': 'str'} + } + # Validate template params + valid_temp, invalid_params = validate_list_of_dicts( + self.config, temp_spec ) - - if self.config: - msg = None - # Validate template params - valid_temp, invalid_params = validate_list_of_dicts( - self.config, temp_spec - ) - - if invalid_params: - msg = "Invalid parameters in playbook: {0}".format( - "\n".join(invalid_params) - ) - self.module.fail_json(msg=msg) - - self.validated = valid_temp - - if self.log: - log(str(valid_temp)) - log(str(self.validated)) - - if self.params.get("state") == "merged": - for temp in self.validated: - if not temp.get("language") or not temp.get("deviceTypes") \ - or not temp.get("softwareType"): - msg = "missing required arguments: language or deviceTypes or softwareType" - self.module.fail_json(msg=msg) - if not (temp.get("language").lower() == "velocity" or - temp.get("language").lower() == "jinja"): - msg = "Invalid parameters in playbook: {0} : Invalid choice provided".format( - "".join(temp.get("language"))) - self.module.fail_json(msg=msg) - - def get_dnac_params(self, params): - dnac_params = dict( - dnac_host=params.get("dnac_host"), - dnac_port=params.get("dnac_port"), - dnac_username=params.get("dnac_username"), - dnac_password=params.get("dnac_password"), - dnac_verify=params.get("dnac_verify"), - dnac_debug=params.get("dnac_debug"), - dnac_log=params.get("dnac_log") - ) - return dnac_params + if invalid_params: + self.msg = "Invalid parameters in playbook: {0}".format( + "\n".join(invalid_params)) + self.status = "failed" + return self + + self.validated_config = valid_temp + self.log(str(valid_temp)) + self.msg = "Successfully validated input" + self.status = "success" + return self + + def get_project_params(self, params): + """Store project parameters from the playbook for template processing in DNAC""" + + project_params = {"name": params.get("projectName"), + "description": params.get("projectDescription") + } + return project_params def get_template_params(self, params): - temp_params = dict( - tags=params.get("template_tag"), - author=params.get("author"), - composite=params.get("composite"), - containingTemplates=params.get("containingTemplates"), - createTime=params.get("createTime"), - customParamsOrder=params.get("customParamsOrder"), - description=params.get("template_description"), - deviceTypes=params.get("deviceTypes"), - failurePolicy=params.get("failurePolicy"), - id=params.get("templateId"), - language=params.get("language").upper(), - lastUpdateTime=params.get("lastUpdateTime"), - latestVersionTime=params.get("latestVersionTime"), - name=params.get("templateName"), - parentTemplateId=params.get("parentTemplateId"), - projectId=params.get("projectId"), - projectName=params.get("projectName"), - rollbackTemplateContent=params.get("rollbackTemplateContent"), - rollbackTemplateParams=params.get("rollbackTemplateParams"), - softwareType=params.get("softwareType"), - softwareVariant=params.get("softwareVariant"), - softwareVersion=params.get("softwareVersion"), - templateContent=params.get("templateContent"), - templateParams=params.get("templateParams"), - validationErrors=params.get("validationErrors"), - version=params.get("version"), - project_id=params.get("projectId"), - ) + """Store template parameters from the playbook for template processing in DNAC""" + + temp_params = { + "tags": params.get("template_tag"), + "author": params.get("author"), + "composite": params.get("composite"), + "containingTemplates": params.get("containingTemplates"), + "createTime": params.get("createTime"), + "customParamsOrder": params.get("customParamsOrder"), + "description": params.get("template_description"), + "deviceTypes": params.get("deviceTypes"), + "failurePolicy": params.get("failurePolicy"), + "id": params.get("templateId"), + "language": params.get("language").upper(), + "lastUpdateTime": params.get("lastUpdateTime"), + "latestVersionTime": params.get("latestVersionTime"), + "name": params.get("templateName"), + "parentTemplateId": params.get("parentTemplateId"), + "projectId": params.get("projectId"), + "projectName": params.get("projectName"), + "rollbackTemplateContent": params.get("rollbackTemplateContent"), + "rollbackTemplateParams": params.get("rollbackTemplateParams"), + "softwareType": params.get("softwareType"), + "softwareVariant": params.get("softwareVariant"), + "softwareVersion": params.get("softwareVersion"), + "templateContent": params.get("templateContent"), + "templateParams": params.get("templateParams"), + "validationErrors": params.get("validationErrors"), + "version": params.get("version"), + "project_id": params.get("projectId"), + } return temp_params - def get_template(self): - result = None + def get_template(self, config): + """Get the template needed for updation or creation""" - for temp in self.validated: - items = self.dnac._exec( - family="configuration_templates", - function="get_template_details", - params={"template_id": temp.get("templateId")} - ) - - if items: - result = items - - if self.log: - log(str(items)) + result = None + items = self.dnac_apply['exec']( + family="configuration_templates", + function="get_template_details", + params={"template_id": config.get("templateId")} + ) + if items: + result = items + self.log(str(items)) self.result['response'] = items return result - def get_have(self): - prev_template = None - template_exists = False - have_create = {} - - # Get available templates. Filter templates based on provided projectName - for temp in self.validated: - template_list = self.dnac._exec( - family="configuration_templates", - function='gets_the_templates_available', - params={"project_names": temp.get("projectName")}, - ) - # API execution error returns a dict - if template_list and isinstance(template_list, list): - template_details = get_dict_result(template_list, 'name', temp.get("templateName")) - - if template_details: - temp["templateId"] = template_details.get("templateId") - have_create["templateId"] = template_details.get("templateId") - prev_template = self.get_template() - - if self.log: - log(str(prev_template)) - - template_exists = prev_template is not None and isinstance(prev_template, dict) - else: - self.module.fail_json(msg="Project Not Found", response=[]) + def get_have_project(self, config): + """Get the current project related information from DNAC""" + + have_create_project = {} + given_project_name = config.get("projectName") + template_available = None + + # Check if project exists. + project_details = self.get_project_details(given_project_name) + # DNAC returns project details even if the substring matches. + # Hence check the projectName retrieved from DNAC. + if not (project_details and isinstance(project_details, list)): + self.log("Project: {0} not found, need to create new project in DNAC".format(given_project_name)) + return None + + fetched_project_name = project_details[0].get('name') + if fetched_project_name != given_project_name: + self.log("Project {0} provided is not exact match in DNAC DB".format(given_project_name)) + return None + + template_available = project_details[0].get('templates') + have_create_project["project_found"] = True + have_create_project["id"] = project_details[0].get("id") + have_create_project["isDeletable"] = project_details[0].get("isDeletable") + + self.have_create_project = have_create_project + return template_available + + def get_have_template(self, config, template_available): + """Get the current template related information from DNAC""" + + project_name = config.get("projectName") + template_name = config.get("templateName") + template = None + have_create_template = {} + + have_create_template["isCommitPending"] = False + have_create_template["template_found"] = False + + template_details = get_dict_result(template_available, + "name", + template_name) + # Check if specified template in playbook is available + if not template_details: + self.log("Template {0} not found in project {1}".format(template_name, project_name)) + self.msg = "Template : {0} missing, new template to be created".format(template_name) + self.status = "success" + return self + + config["templateId"] = template_details.get("id") + have_create_template["id"] = template_details.get("id") + # Get available templates which are committed under the project + template_list = self.dnac_apply['exec']( + family="configuration_templates", + function="gets_the_templates_available", + params={"project_names": config.get("projectName")}, + ) + have_create_template["isCommitPending"] = True + # This check will fail if specified template is there not committed in dnac + if template_list and isinstance(template_list, list): + template_info = get_dict_result(template_list, + "name", + template_name) + if template_info: + template = self.get_template(config) + have_create_template["template"] = template + have_create_template["isCommitPending"] = False + have_create_template["template_found"] = template is not None \ + and isinstance(template, dict) + self.log("Template {0} is found and template " + "details are :{1}".format(template_name, str(template))) + + # There are committed templates in the project but the + # one specified in the playbook may not be committed + self.log("Commit pending for template name {0}" + " is {1}".format(template_name, have_create_template.get('isCommitPending'))) + + self.have_create_template = have_create_template + self.msg = "Successfully collected all template parameters from dnac for comparison" + self.status = "success" + return self + + def get_have(self, config): + """Get the current project and template details from DNAC""" + + template_available = self.get_have_project(config) + if template_available: + self.get_have_template(config, template_available) + + self.msg = "Successfully collected all project and template \ + parameters from dnac for comparison" + self.status = "success" + return self + + def get_project_details(self, project_name): + """Get the details of specific project name provided""" + + items = self.dnac_apply['exec']( + family="configuration_templates", + function='get_projects', + op_modifies=True, + params={"name": project_name}, + ) + return items - have_create['template'] = prev_template - have_create['template_found'] = template_exists - self.have_create = have_create + def get_want(self, config): + """Get all the template and project related information from playbook + that is needed to be created in DNAC""" - def get_want(self): want_create = {} + template_params = self.get_template_params(config) + project_params = self.get_project_params(config) + version_comments = config.get("versionDescription") - for temp in self.validated: - template_params = self.get_template_params(temp) - version_comments = temp.get("versionDescription") - - if self.params.get("state") == "merged" and \ - not self.have_create.get("template_found"): - # ProjectId is required for creating a new template. - # Store it with other template parameters. - items = self.dnac._exec( - family="configuration_templates", - function='get_projects', - params={"name": temp.get("projectName")}, - ) - template_params["projectId"] = items[0].get("id") - template_params["project_id"] = items[0].get("id") + if self.params.get("state") == "merged": + self.update_mandatory_parameters(template_params) want_create["template_params"] = template_params + want_create["project_params"] = project_params want_create["comments"] = version_comments self.want_create = want_create + self.msg = "Successfully collected all parameters from playbook " + \ + "for comparison" + self.status = "success" + return self + + def create_project_or_template(self, is_create_project=False): + """Call DNAC API to create project or template based on the input provided""" + + creation_id = None + created = False + template_params = self.want_create.get("template_params") + project_params = self.want_create.get("project_params") + + if is_create_project: + params_key = project_params + name = "project: {0}".format(project_params.get('name')) + validation_string = "Successfully created project" + creation_value = "create_project" + else: + params_key = template_params + name = "template: {0}".format(template_params.get('name')) + validation_string = "Successfully created template" + creation_value = "create_template" + + response = self.dnac_apply['exec']( + family="configuration_templates", + function=creation_value, + op_modifies=True, + params=params_key, + ) + if not isinstance(response, dict): + self.log("Response not in dictionary format.") + return creation_id, created + + task_id = response.get("response").get("taskId") + if not task_id: + self.log("Task id {0} not found".format(task_id)) + return creation_id, created + + while not created: + task_details = self.get_task_details(task_id) + if not task_details: + self.log("Failed to get task details for taskid: {0}".format(task_id)) + return creation_id, created + + self.log("task_details: {0}".format(task_details)) + if task_details.get("isError"): + self.log("isError set to true for taskid: {0}".format(task_id)) + return creation_id, created + + if validation_string not in task_details.get("progress"): + self.log("progress set to {0} " + "for taskid: {1}".format(task_details.get('progress'), task_id)) + continue + + creation_id = task_details.get("data") + if not creation_id: + self.log("data is not found for taskid: {0}".format(task_id)) + continue + + created = True + if is_create_project: + # ProjectId is required for creating a new template. + # Store it with other template parameters. + template_params["projectId"] = creation_id + template_params["project_id"] = creation_id + + self.log("New {0} created with id {1}".format(name, creation_id)) + return creation_id, created def requires_update(self): - current_obj = self.have_create.get("template") + """Check if the template config given requires update.""" + + if self.have_create_template.get("isCommitPending"): + self.log("Template is in saved state and needs to be updated and committed") + return True + + current_obj = self.have_create_template.get("template") requested_obj = self.want_create.get("template_params") obj_params = [ ("tags", "tags", ""), @@ -896,163 +1018,240 @@ def requires_update(self): requested_obj.get(ansible_param)) for (dnac_param, ansible_param, default) in obj_params) - def get_task_details(self, id): - result = None - response = self.dnac._exec( - family="task", - function='get_task_by_id', - params={"task_id": id}, - ) - - if self.log: - log(str(response)) - - if isinstance(response, dict): - result = response.get("response") - - return result + def update_mandatory_parameters(self, template_params): + """Update parameters which are mandatory for creating a template""" + + # Mandate fields required for creating a new template. + # Store it with other template parameters. + template_params["projectId"] = self.have_create_project.get("id") + template_params["project_id"] = self.have_create_project.get("id") + # Update language,deviceTypes and softwareType if not provided for existing template. + if not template_params.get("language"): + template_params["language"] = self.have_create_template.get('template') \ + .get('language') + if not template_params.get("deviceTypes"): + template_params["deviceTypes"] = self.have_create_template.get('template') \ + .get('deviceTypes') + if not template_params.get("softwareType"): + template_params["softwareType"] = self.have_create_template.get('template') \ + .get('softwareType') + + def validate_input_merge(self, template_exists): + """Validate input after getting all the parameters from DNAC. + "If mandate like deviceTypes, softwareType and language " + "already present in DNAC for a template." + "It is not required to be provided in playbook, " + "but if it is new creation error will be thrown to provide these fields.""" + + template_params = self.want_create.get("template_params") + language = template_params.get("language").upper() + if language: + if language not in self.accepted_languages: + self.msg = "Invalid value language {0} ." \ + "Accepted language values are {1}" \ + .format(self.accepted_languages, language) + self.status = "failed" + return self + else: + template_params["language"] = "JINJA" + + if not template_exists: + if not template_params.get("deviceTypes") \ + or not template_params.get("softwareType"): + self.msg = "DeviceTypes and SoftwareType are required arguments to create Templates" + self.status = "failed" + return self + + self.msg = "Input validated for merging" + self.status = "success" + return self + + def get_diff_merged(self, config): + """Update/Create templates and projects in DNAC with fields provided in DNAC""" + + is_project_found = self.have_create_project.get("project_found") + if not is_project_found: + project_id, project_created = self.create_project_or_template(is_create_project=True) + if project_created: + self.log("project created with projectId : {0}".format(project_id)) + else: + self.status = "failed" + self.msg = "Project creation failed" + return self - def get_diff_merge(self): + is_template_found = self.have_create_template.get("template_found") + template_params = self.want_create.get("template_params") template_id = None - template_ceated = False template_updated = False - template_exists = self.have_create.get("template_found") - - if template_exists: + self.validate_input_merge(is_template_found).check_return_status() + if is_template_found: if self.requires_update(): - response = self.dnac._exec( + response = self.dnac_apply['exec']( family="configuration_templates", function="update_template", - params=self.want_create.get("template_params"), + params=template_params, op_modifies=True, ) template_updated = True - template_id = self.have_create.get("templateId") - - if self.log: - log("Updating Existing Template") + template_id = self.have_create_template.get("id") + self.log("Updating Existing Template") else: # Template does not need update - self.result['response'] = self.have_create.get("template") + self.result['response'] = self.have_create_template.get("template") self.result['msg'] = "Template does not need update" - self.module.exit_json(**self.result) + self.status = "exited" + return self else: - response = self.dnac._exec( - family="configuration_templates", - function='create_template', - op_modifies=True, - params=self.want_create.get("template_params"), - ) + if template_params.get("name"): + template_id, template_updated = self.create_project_or_template() + else: + self.msg = "missing required arguments: TemplateName" + self.status = "failed" + return self - if self.log: - log("Template created. Get template_id for versioning") - if isinstance(response, dict): - create_error = False - task_details = {} - task_id = response.get("response").get("taskId") - - if task_id: - while (True): - task_details = self.get_task_details(task_id) - if task_details and task_details.get("isError"): - create_error = True - break - - if task_details and ("Successfully created template" in task_details.get("progress")): - break - if not create_error: - template_id = task_details.get("data") - if template_id: - template_created = True - - if template_updated or template_created: + if template_updated: # Template needs to be versioned - version_params = dict( - comments=self.want_create.get("comments"), - templateId=template_id - ) - response = self.dnac._exec( + version_params = { + "comments": self.want_create.get("comments"), + "templateId": template_id + } + response = self.dnac_apply['exec']( family="configuration_templates", - function='version_template', + function="version_template", op_modifies=True, params=version_params ) task_details = {} task_id = response.get("response").get("taskId") - - if task_id: - task_details = self.get_task_details(task_id) - self.result['changed'] = True - self.result['msg'] = task_details.get('progress') - self.result['diff'] = self.validated - if self.log: - log(str(task_details)) + if not task_id: + self.msg = "Task id: {0} not found".format(task_id) + self.status = "failed" + return self + task_details = self.get_task_details(task_id) + self.result['changed'] = True + self.result['msg'] = task_details.get('progress') + self.result['diff'] = config + self.log(str(task_details)) self.result['response'] = task_details if task_details else response if not self.result.get('msg'): - self.result['msg'] = "Error while versioning the template" + self.msg = "Error while versioning the template" + self.status = "failed" + return self - def get_diff_delete(self): - template_exists = self.have_create.get("template_found") + self.msg = "Successfully completed merged state execution" + self.status = "success" + return self - if template_exists: - response = self.dnac._exec( - family="configuration_templates", - function="deletes_the_template", - params={"template_id": self.have_create.get("templateId")}, - ) - task_details = {} - task_id = response.get("response").get("taskId") - - if task_id: - task_details = self.get_task_details(task_id) - self.result['changed'] = True - self.result['msg'] = task_details.get('progress') - self.result['diff'] = self.validated - - if self.log: - log(str(task_details)) + def delete_project_or_template(self, config, is_delete_project=False): + """Call DNAC API to delete project or template with provided inputs""" + if is_delete_project: + params_key = {"project_id": self.have_create_project.get("id")} + deletion_value = "deletes_the_project" + name = "project: {0}".format(config.get('projectName')) + else: + template_params = self.want_create.get("template_params") + params_key = {"template_id": self.have_create_template.get("id")} + deletion_value = "deletes_the_template" + name = "templateName: {0}".format(template_params.get('templateName')) + + response = self.dnac_apply['exec']( + family="configuration_templates", + function=deletion_value, + params=params_key, + ) + task_id = response.get("response").get("taskId") + if task_id: + task_details = self.get_task_details(task_id) + self.result['changed'] = True + self.result['msg'] = task_details.get('progress') + self.result['diff'] = config + + self.log(str(task_details)) self.result['response'] = task_details if task_details else response - if not self.result['msg']: - self.result['msg'] = "Error while deleting template" + self.result['msg'] = "Error while deleting {name} : " + self.status = "failed" + return self + + self.msg = "Successfully deleted {0} ".format(name) + self.status = "success" + return self + + def get_diff_deleted(self, config): + """Delete projects or templates in DNAC with fields provided in playbook.""" + + is_project_found = self.have_create_project.get("project_found") + projectName = config.get("projectName") + + if not is_project_found: + self.msg = "Project {0} is not found".format(projectName) + self.status = "failed" + return self + + is_template_found = self.have_create_template.get("template_found") + template_params = self.want_create.get("template_params") + template_name = config.get("templateName") + if template_params.get("name"): + if is_template_found: + self.delete_project_or_template(config) + else: + self.msg = "Invalid template {0} under project".format(template_name) + self.status = "failed" + return self else: - self.module.fail_json(msg="Template not found", response=[]) + self.log("Template Name is empty, deleting the project and its associated templates") + is_project_deletable = self.have_create_project.get("isDeletable") + if is_project_deletable: + self.delete_project_or_template(config, is_delete_project=True) + else: + self.msg = "Project is not deletable" + self.status = "failed" + return self + + self.msg = "Successfully completed delete state execution" + self.status = "success" + return self + + def reset_values(self): + """Reset all neccessary attributes to default values""" + + self.have_create_project.clear() + self.have_create_template.clear() + self.want_create.clear() def main(): - """ main entry point for module execution - """ - - element_spec = dict( - dnac_host=dict(required=True, type='str'), - dnac_port=dict(type='str', default='443'), - dnac_username=dict(type='str', default='admin', aliases=["user"]), - dnac_password=dict(type='str', no_log=True), - dnac_verify=dict(type='bool', default='True'), - dnac_version=dict(type="str", default="2.2.3.3"), - dnac_debug=dict(type='bool', default=False), - dnac_log=dict(type='bool', default=False), - validate_response_schema=dict(type="bool", default=True), - config=dict(required=True, type='list', elements='dict'), - state=dict( - default='merged', - choices=['merged', 'deleted']), - ) + """ main entry point for module execution""" + + element_spec = {'dnac_host': {'required': True, 'type': 'str'}, + 'dnac_port': {'type': 'str', 'default': '443'}, + 'dnac_username': {'type': 'str', 'default': 'admin', 'aliases': ['user']}, + 'dnac_password': {'type': 'str', 'no_log': True}, + 'dnac_verify': {'type': 'bool', 'default': 'True'}, + 'dnac_version': {'type': 'str', 'default': '2.2.3.3'}, + 'dnac_debug': {'type': 'bool', 'default': False}, + 'dnac_log': {'type': 'bool', 'default': False}, + 'validate_response_schema': {'type': 'bool', 'default': True}, + 'config': {'required': True, 'type': 'list', 'elements': 'dict'}, + 'state': {'default': 'merged', 'choices': ['merged', 'deleted']} + } module = AnsibleModule(argument_spec=element_spec, supports_check_mode=False) dnac_template = DnacTemplate(module) - dnac_template.validate_input() - state = dnac_template.get_state() - dnac_template.get_have() - dnac_template.get_want() - - if state == "merged": - dnac_template.get_diff_merge() - - elif state == "deleted": - dnac_template.get_diff_delete() + dnac_template.validate_input().check_return_status() + state = dnac_template.params.get("state") + if state not in dnac_template.supported_states: + dnac_template.status = "invalid" + dnac_template.msg = "State {0} is invalid".format(state) + dnac_template.check_return_status() + + for config in dnac_template.validated_config: + dnac_template.reset_values() + dnac_template.get_have(config).check_return_status() + dnac_template.get_want(config).check_return_status() + dnac_template.get_diff_state_apply[state](config).check_return_status() module.exit_json(**dnac_template.result) diff --git a/plugins/modules/user.py b/plugins/modules/user.py index 5bfdc9ced7..21769bb834 100644 --- a/plugins/modules/user.py +++ b/plugins/modules/user.py @@ -51,8 +51,8 @@ link: https://developer.cisco.com/docs/dna-center/#!update-user-api notes: - SDK Method used are - userand_roles.UserandRoles.add_user_ap_i, - userand_roles.UserandRoles.update_user_ap_i, + user_and_roles.UserandRoles.add_user_ap_i, + user_and_roles.UserandRoles.update_user_ap_i, - Paths used are post /dna/system/api/v1/user, diff --git a/plugins/modules/user_info.py b/plugins/modules/user_info.py index bb25c6659c..f00e1d66e4 100644 --- a/plugins/modules/user_info.py +++ b/plugins/modules/user_info.py @@ -32,7 +32,7 @@ link: https://developer.cisco.com/docs/dna-center/#!get-users-api notes: - SDK Method used are - userand_roles.UserandRoles.get_users_ap_i, + user_and_roles.UserandRoles.get_users_api, - Paths used are get /dna/system/api/v1/user, diff --git a/plugins/modules/users_external_servers_info.py b/plugins/modules/users_external_servers_info.py index 2360060adb..536f958c8d 100644 --- a/plugins/modules/users_external_servers_info.py +++ b/plugins/modules/users_external_servers_info.py @@ -32,7 +32,7 @@ link: https://developer.cisco.com/docs/dna-center/#!get-external-authentication-servers-api notes: - SDK Method used are - userand_roles.UserandRoles.get_external_authentication_servers_ap_i, + user_and_roles.UserandRoles.get_external_authentication_servers_ap_i, - Paths used are get /dna/system/api/v1/users/external-servers,