From 8b57cd9bb0a81110dc0348260c0c4daecec70350 Mon Sep 17 00:00:00 2001 From: David Flores Date: Thu, 19 Sep 2019 10:55:17 +0100 Subject: [PATCH 1/2] Adding test ansible playbook --- Makefile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Makefile b/Makefile index a34c24a..fe962bb 100644 --- a/Makefile +++ b/Makefile @@ -6,3 +6,9 @@ build: publish: build mazer publish releases/davidban77-gns3-${VERSION}.tar.gz + +test-create-lab: + cd test/playbooks; ansible-playbook main.yml -e execute=create + +test-delete-lab: + cd test/playbooks; ansible-playbook main.yml -e execute=delete From 8362b2741c3a5275133bc24febe594087041e279 Mon Sep 17 00:00:00 2001 From: David Flores Date: Sun, 22 Sep 2019 11:52:30 +0100 Subject: [PATCH 2/2] Adding gns3_node_file and gns3_project_file --- CHANGELOG.md | 6 + Makefile | 10 ++ galaxy.yml | 2 +- plugins/modules/gns3_node_file.py | 178 +++++++++++++++++++++++++++ plugins/modules/gns3_project_file.py | 156 +++++++++++++++++++++++ test/playbooks/create_files.yml | 22 ++++ test/playbooks/delete_files.yml | 16 +++ test/playbooks/group_vars/all.yml | 3 + 8 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 plugins/modules/gns3_node_file.py create mode 100644 plugins/modules/gns3_project_file.py create mode 100644 test/playbooks/create_files.yml create mode 100644 test/playbooks/delete_files.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 228399c..c0bbd6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Releases +## 1.3.0 + +- Added `gns3_node_file` and `gns3_project_file` modules. +- Improved the Makefile +- Added alpine node to the tests + ## 1.2.2. - Upgrading to `gns3fy ^0.4.0` diff --git a/Makefile b/Makefile index fe962bb..a7f74c0 100644 --- a/Makefile +++ b/Makefile @@ -10,5 +10,15 @@ publish: build test-create-lab: cd test/playbooks; ansible-playbook main.yml -e execute=create +test-create-files: + cd test/playbooks; ansible-playbook create_files.yml + +test-delete-files: + cd test/playbooks; ansible-playbook delete_files.yml + test-delete-lab: cd test/playbooks; ansible-playbook main.yml -e execute=delete + +test-create-env: test-create-lab test-create-files + +test-delete-env: test-delete-files test-delete-lab diff --git a/galaxy.yml b/galaxy.yml index d55299d..a613ae0 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,6 +1,6 @@ namespace: "davidban77" name: "gns3" -version: "1.2.2" +version: "1.3.0" readme: "README.md" description: "Module to interact with GNS3 server REST API based on gns3fy" authors: diff --git a/plugins/modules/gns3_node_file.py b/plugins/modules/gns3_node_file.py new file mode 100644 index 0000000..079a8a9 --- /dev/null +++ b/plugins/modules/gns3_node_file.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + +DOCUMENTATION = """ +--- +module: gns3_node_file +short_description: Updates/creates a file on a node directory +version_added: '2.8' +description: + - "Updates/creates a file on a node directory of a GNS3 project" +requirements: [ gns3fy ] +author: + - David Flores (@netpanda) +options: + url: + description: + - URL target of the GNS3 server + required: true + type: str + port: + description: + - TCP port to connect to server REST API + type: int + default: 3080 + project_name: + description: + - Project name + type: str + project_id: + description: + - Project ID + type: str + node_name: + description: + - Node name + type: str + node_id: + description: + - Node ID + type: str + data: + description: + - The text to insert. + type: str + dest: + description: + - Node destination path. Like 'etc/network/interfaces' + type: str + required: true + state: + description: + - If the file should present or absent + type: str + choices: ['present', 'absent'] + default: present +""" + +EXAMPLES = """ +# Retrieve the GNS3 server version +- name: Get the server version + gns3_node_file: + url: http://localhost + port: 3080 + project_name: test_lab + node_name: alpine-1 + data: | + auto eth0 + iface eth0 inet dhcp + dest: /etc/network/interfaces +""" +import traceback + +GNS3FY_IMP_ERR = None +try: + import gns3fy + + HAS_GNS3FY = True +except ImportError: + GNS3FY_IMP_ERR = traceback.format_exc() + HAS_GNS3FY = False + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib + + +def node_write_file(module, node, path, data): + "Writes text data into specified path of the node" + try: + node.write_file(path=path, data=data) + except Exception as err: + module.fail_json(msg=str(err), exception=traceback.format_exc()) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + url=dict(type="str", required=True), + port=dict(type="int", default=3080), + project_name=dict(type="str", default=None), + project_id=dict(type="str", default=None), + node_name=dict(type="str", default=None), + node_id=dict(type="str", default=None), + data=dict(type="str", default=None), + dest=dict(type="str", required=True), + state=dict(type="str", choices=["present", "absent"], default="present"), + ), + required_one_of=[["project_name", "project_id"], ["node_name", "node_id"]], + ) + if not HAS_GNS3FY: + module.fail_json(msg=missing_required_lib("gns3fy"), exception=GNS3FY_IMP_ERR) + result = dict(changed=False) + + server_url = module.params['url'] + server_port = module.params['port'] + project_name = module.params['project_name'] + project_id = module.params['project_id'] + node_name = module.params['node_name'] + node_id = module.params['node_id'] + data = module.params['data'] + dest = module.params['dest'] + state = module.params['state'] + if state == "present" and data is None: + module.fail_json(msg="Parameter needs to be passed: data", **result) + + # Create server session + server = gns3fy.Gns3Connector(url=f"{server_url}:{server_port}") + # Define the project + if project_name is not None: + project = gns3fy.Project(name=project_name, connector=server) + elif project_id is not None: + project = gns3fy.Project(project_id=project_id, connector=server) + if project is None: + module.fail_json(msg="Could not retrieve project. Check name", **result) + + # Retrieve project info + project.get() + + # Define the Node + if node_name is not None: + node = project.get_node(name=node_name) + elif node_id is not None: + node = project.get_node(node_id=node_id) + if node is None: + module.fail_json(msg="Could not retrieve node. Check name", **result) + + # Try to get file data + try: + file_data = node.get_file(path=dest) + except Exception as err: + if "not found" in str(err): + file_data = None + else: + module.fail_json(msg=str(err), exception=traceback.format_exc()) + + if state == "absent": + if file_data is None or file_data == "": + result.update(changed=False) + else: + # Delete node file data + # As of now (GNS3 v2.2.rc5) the DELETE method is not allowed + node_write_file(module, node, dest, "") + result.update(changed=True) + elif state == "present": + if file_data == data: + result.update(changed=False) + else: + node_write_file(module, node, dest, data) + result.update(changed=True) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/gns3_project_file.py b/plugins/modules/gns3_project_file.py new file mode 100644 index 0000000..27fb105 --- /dev/null +++ b/plugins/modules/gns3_project_file.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + +DOCUMENTATION = """ +--- +module: gns3_project_file +short_description: Updates/creates a file on a project directory +version_added: '2.8' +description: + - "Updates/creates a file on a project directory on the GNS3 server" +requirements: [ gns3fy ] +author: + - David Flores (@netpanda) +options: + url: + description: + - URL target of the GNS3 server + required: true + type: str + port: + description: + - TCP port to connect to server REST API + type: int + default: 3080 + project_name: + description: + - Project name + type: str + project_id: + description: + - Project ID + type: str + data: + description: + - The text to insert. + type: str + dest: + description: + - Node destination path. Like 'etc/network/interfaces' + type: str + required: true + state: + description: + - If the file should present or absent + type: str + choices: ['present', 'absent'] + default: present +""" + +EXAMPLES = """ +# Retrieve the GNS3 server version +- name: Get the server version + gns3_project_file: + url: http://localhost + port: 3080 + project_name: test_lab + data: | + Hello this is a README! + dest: README.txt +""" +import traceback + +GNS3FY_IMP_ERR = None +try: + import gns3fy + + HAS_GNS3FY = True +except ImportError: + GNS3FY_IMP_ERR = traceback.format_exc() + HAS_GNS3FY = False + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib + + +def project_write_file(module, project, path, data): + "Writes text data into specified path of the project" + try: + project.write_file(path=path, data=data) + except Exception as err: + module.fail_json(msg=str(err), exception=traceback.format_exc()) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + url=dict(type="str", required=True), + port=dict(type="int", default=3080), + project_name=dict(type="str", default=None), + project_id=dict(type="str", default=None), + data=dict(type="str", default=None), + dest=dict(type="str", required=True), + state=dict(type="str", choices=["present", "absent"], default="present"), + ), + required_one_of=[["project_name", "project_id"]], + ) + if not HAS_GNS3FY: + module.fail_json(msg=missing_required_lib("gns3fy"), exception=GNS3FY_IMP_ERR) + result = dict(changed=False) + + server_url = module.params['url'] + server_port = module.params['port'] + project_name = module.params['project_name'] + project_id = module.params['project_id'] + data = module.params['data'] + dest = module.params['dest'] + state = module.params['state'] + if state == "present" and data is None: + module.fail_json(msg="Parameter needs to be passed: data", **result) + + # Create server session + server = gns3fy.Gns3Connector(url=f"{server_url}:{server_port}") + # Define the project + if project_name is not None: + project = gns3fy.Project(name=project_name, connector=server) + elif project_id is not None: + project = gns3fy.Project(project_id=project_id, connector=server) + if project is None: + module.fail_json(msg="Could not retrieve project. Check name", **result) + + # Retrieve project info + project.get() + + # Try to get file data + try: + file_data = project.get_file(path=dest) + except Exception as err: + if "Not Found" in str(err): + file_data = None + else: + module.fail_json(msg=str(err), exception=traceback.format_exc()) + + if state == "absent": + if file_data is None or file_data == "": + result.update(changed=False) + else: + # Delete project file data + # As of now (GNS3 v2.2.rc5) the DELETE method is not allowed + project_write_file(module, project, dest, "") + result.update(changed=True) + elif state == "present": + if file_data == data: + result.update(changed=False) + else: + project_write_file(module, project, dest, data) + result.update(changed=True) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/test/playbooks/create_files.yml b/test/playbooks/create_files.yml new file mode 100644 index 0000000..f7ff5ce --- /dev/null +++ b/test/playbooks/create_files.yml @@ -0,0 +1,22 @@ +- hosts: localhost + tasks: + - name: Play with a dummy file to node + gns3_node_file: + url: "{{ gns3_url }}" + project_name: "{{ gns3_project_name }}" + node_name: alpine-1 + state: present + dest: /etc/network/dummy_file + data: | + # Some data to insert on the file + auto eth0 + iface eth0 inect dhcp + + - name: Play with a dummy file on project + gns3_project_file: + url: "{{ gns3_url }}" + project_name: "{{ gns3_project_name }}" + state: present + dest: README.txt + data: | + Hello! this is a README diff --git a/test/playbooks/delete_files.yml b/test/playbooks/delete_files.yml new file mode 100644 index 0000000..0b51a33 --- /dev/null +++ b/test/playbooks/delete_files.yml @@ -0,0 +1,16 @@ +- hosts: localhost + tasks: + - name: Play with a dummy file to node + gns3_node_file: + url: "{{ gns3_url }}" + project_name: "{{ gns3_project_name }}" + node_name: alpine-1 + state: absent + dest: /etc/network/dummy_file + + - name: Play with a dummy file on project + gns3_project_file: + url: "{{ gns3_url }}" + project_name: "{{ gns3_project_name }}" + state: absent + dest: README.txt diff --git a/test/playbooks/group_vars/all.yml b/test/playbooks/group_vars/all.yml index 0e97765..7ffbb25 100644 --- a/test/playbooks/group_vars/all.yml +++ b/test/playbooks/group_vars/all.yml @@ -10,9 +10,12 @@ gns3_nodes_spec: template: "IOU-L2-15.1" - name: ios-2 template: "IOU-L3-15.4" + - name: alpine-1 + template: "alpine" gns3_nodes_strategy: one_by_one gns3_links_spec: - ["veos-1", "Ethernet1", "veos-2", "Ethernet1"] - ["veos-1", "Ethernet2", "ios-1", "Ethernet1/0"] - ["veos-2", "Ethernet2", "ios-2", "Ethernet1/0"] - ["ios-1", "Ethernet1/2", "ios-2", "Ethernet1/2"] + - ["ios-1", "Ethernet1/3", "alpine-1", "eth0"]