├── .gitignore ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── Vagrantfile ├── create_vagrant_mikrotik.sh ├── library ├── __init__.py ├── mikrotik.py ├── mt_command.py ├── mt_dhcp_server.py ├── mt_facts.py ├── mt_hotspot.py ├── mt_interface_wireless.py ├── mt_interfaces.py ├── mt_ip.py ├── mt_ip_address.py ├── mt_ip_firewall.py ├── mt_ip_firewall_addresslist.py ├── mt_login_test.py ├── mt_neighbor.py ├── mt_ppp_profile.py ├── mt_ppp_secret.py ├── mt_ppp_server.py ├── mt_radius.py ├── mt_radius_backup.py ├── mt_snmp.py ├── mt_system.py ├── mt_system_scheduler.py ├── mt_tool.py └── mt_user.py ├── pythonlibs ├── mt_api │ ├── __init__.py │ ├── retryloop.py │ └── socket_utils.py └── mt_common.py ├── tasks ├── backup_recover.yml └── main.yml └── tests └── integration ├── ansible.cfg ├── library ├── pythonlibs ├── run_tests.sh ├── tasks ├── hotspot-tests.yml ├── radius-tests.yml ├── test-address-list.yml ├── test-bridge.yml ├── test-command.yml ├── test-dhcp-server.yml ├── test-facts.yml ├── test-firewall-filter.yml ├── test-firewall-nat.yml ├── test-interface-ethernet.yml ├── test-interface-vlan.yml ├── test-interface-wireless.yml ├── test-ip-address.yml ├── test-ip-pool.yml ├── test-neighbor.yml ├── test-ovpn-client.yml ├── test-scheduler.yml ├── test-service.yml ├── test-snmp.yml ├── test-system.yml ├── test-tool.yml └── test-user.yml ├── tests.retry └── tests.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | # Vagrant 92 | .vagrant/ 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.python.org/simple" 3 | verify_ssl = true 4 | 5 | [packages] 6 | ansible = "2.4.2.0" 7 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "1973d9e9e2050096ddc5b35f2633f9d83cecb5d93937c9ee1d2d9948e832fc8c" 5 | }, 6 | "requires": {}, 7 | "sources": [ 8 | { 9 | "url": "https://pypi.python.org/simple", 10 | "verify_ssl": true 11 | } 12 | ] 13 | }, 14 | "default": { 15 | "Jinja2": { 16 | "hash": "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", 17 | "version": "==2.10" 18 | }, 19 | "MarkupSafe": { 20 | "hash": "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665", 21 | "version": "==1.0" 22 | }, 23 | "PyNaCl": { 24 | "hash": "sha256:0bfa0d94d2be6874e40f896e0a67e290749151e7de767c5aefbad1121cad7512", 25 | "version": "==1.2.1" 26 | }, 27 | "PyYAML": { 28 | "hash": "sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab", 29 | "version": "==3.12" 30 | }, 31 | "ansible": { 32 | "hash": "sha256:315f1580b20bbc2c2f1104f8b5e548c6b4cac943b88711639c5e0d4dfc4d7658", 33 | "version": "==2.4.2.0" 34 | }, 35 | "asn1crypto": { 36 | "hash": "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", 37 | "version": "==0.24.0" 38 | }, 39 | "bcrypt": { 40 | "hash": "sha256:0f317e4ffbdd15c3c0f8ab5fbd86aa9aabc7bea18b5cc5951b456fe39e9f738c", 41 | "version": "==3.1.4" 42 | }, 43 | "cffi": { 44 | "hash": "sha256:248198cb714fe09f5c60b6acba3675d52199c6142641536796cdf89dd45e5590", 45 | "version": "==1.11.2" 46 | }, 47 | "cryptography": { 48 | "hash": "sha256:69285f5615507b6625f89ea1048addd1d9218585fb886eb90bdebb1d2b2d26f5", 49 | "version": "==2.1.4" 50 | }, 51 | "enum34": { 52 | "hash": "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", 53 | "version": "==1.1.6" 54 | }, 55 | "idna": { 56 | "hash": "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", 57 | "version": "==2.6" 58 | }, 59 | "ipaddress": { 60 | "hash": "sha256:200d8686011d470b5e4de207d803445deee427455cd0cb7c982b68cf82524f81", 61 | "version": "==1.0.19" 62 | }, 63 | "paramiko": { 64 | "hash": "sha256:8851e728e8b7590989e68e3936c48ee3ca4dad91d29e3d7ff0305b6c5fc582db", 65 | "version": "==2.4.0" 66 | }, 67 | "pyasn1": { 68 | "hash": "sha256:d5cd6ed995dba16fad0c521cfe31cd2d68400b53fcc2bce93326829be73ab6d1", 69 | "version": "==0.4.2" 70 | }, 71 | "pycparser": { 72 | "hash": "sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226", 73 | "version": "==2.18" 74 | }, 75 | "setuptools": { 76 | "hash": "sha256:155c2ec9fdcc00c3973d966b416e1cf3a1e7ce75f4c09fb760b23f94b935926e", 77 | "version": "==38.4.0" 78 | }, 79 | "six": { 80 | "hash": "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", 81 | "version": "==1.11.0" 82 | } 83 | }, 84 | "develop": {} 85 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ansible MikroTik modules 2 | ======================== 3 | 4 | Introduction 5 | ------------ 6 | 7 | This repository provides Ansible modules to manage MikroTik RouterOS-based 8 | devices. 9 | 10 | Requirements 11 | ------------ 12 | 13 | Ansible=2.4.2.0 14 | 15 | At this time there are no external dependencies. However, there are additional 16 | Python modules that are required by the Ansible modules. You may find these in 17 | `pythonlibs`. Before using Ansible you should add these libraries to your 18 | Python path: 19 | `export PYTHONPATH="$PYTHONPATH:$PWD/pythonlibs"` 20 | 21 | Development 22 | ----------- 23 | ----------- 24 | 25 | In order to test this module, you'll need a RouterOS instance to target. If you 26 | have an existing RouterOS-based MikroTik device, you need only make sure the 27 | API service is enabled. 28 | 29 | AWS EC2 30 | ------- 31 | You can use an ec2 CHR image for testing. Keep in mind that as of right now we can only set up two interfaces on most ec2 instances. 32 | https://aws.amazon.com/marketplace/pp/B01E00PU50?qid=1517274040207&sr=0-1&ref_=srh_res_product_title 33 | 34 | Vagrant 35 | ------- 36 | This repository provides a Vagrantfile for setting up the x86 build 37 | of RouterOS for testing. To use it, you must first ensure Vagrant and 38 | VirtualBox are installed. Then, run `./create_vagrant_mikrotik.sh` to download 39 | the official MikroTik Cloud Hosted Router (CHR) image from MikroTik, package 40 | it as a Vagrant .box file, and register the .box with Vagrant. 41 | 42 | Then, you need only run `vagrant up` in the repository root to start the CHR. 43 | 44 | Ansible setup 45 | ------------ 46 | 47 | To use pipenv ensure pipenv is installed: 48 | 49 | `pip install pipenv` 50 | 51 | Then enable virtualenv and install dependencies: 52 | 53 | `pipenv shell` 54 | 55 | `pipenv install` 56 | 57 | Installing 58 | ---------- 59 | 60 | These modules are still in a very early stage of development; stay tuned for 61 | installation instructions later! :) 62 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | vmname = 'mikrotik-6-38-1' 5 | 6 | # Specify minimum Vagrant version and Vagrant API version 7 | Vagrant.require_version ">= 1.6.0" 8 | VAGRANTFILE_API_VERSION = "2" 9 | 10 | 11 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 12 | 13 | #config.vm.box = 'mikrotik-6-38-1' 14 | config.vm.box = vmname 15 | config.vm.hostname = 'mikrotik01' 16 | config.vm.network "private_network", ip: '192.168.60.202' 17 | config.vm.network "private_network", ip: '192.168.60.203' 18 | config.vm.network "private_network", ip: '192.168.60.204' 19 | config.vm.network "private_network", ip: '192.168.60.205' 20 | config.vm.network "private_network", ip: '192.168.60.206' 21 | config.vm.network "private_network", ip: '192.168.60.207' 22 | config.vm.network "private_network", ip: '192.168.60.208' 23 | config.vm.network "private_network", ip: '192.168.60.209' 24 | config.vm.network "private_network", ip: '192.168.60.210' 25 | config.vm.network "private_network", ip: '192.168.60.211' 26 | config.vm.network "private_network", ip: '192.168.60.212' 27 | config.vm.network :forwarded_port, guest: 22, host: 2301, id: 'ssh' 28 | config.vm.network :forwarded_port, guest: 8728, host: 8728, id: 'api' 29 | config.vm.network :forwarded_port, guest: 80, host: 8080, id: 'web' 30 | 31 | config.vm.provider :virtualbox do |vb| 32 | vb.name = vmname + '_test' 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /create_vagrant_mikrotik.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | dl_link='https://download.mikrotik.com/routeros/6.42.9/chr-6.42.9.vdi' 4 | vmname='mikrotik-6-42-9' 5 | 6 | [[ -f ./downloads/$(basename "$dl_link") ]] && { 7 | echo "*** vdi already exists" 8 | } || { 9 | mkdir -p ./downloads/ 10 | wget --directory-prefix=./downloads/ "$dl_link" 11 | } 12 | 13 | 14 | echo "*** create the vm" 15 | VBoxManage createvm \ 16 | --name "$vmname" \ 17 | --ostype 'Linux_64' \ 18 | --register 19 | 20 | VBoxManage storagectl \ 21 | "$vmname" \ 22 | --name "SATA Controller" \ 23 | --add sata 24 | 25 | echo "*** add the hard disk" 26 | VBoxManage storageattach \ 27 | "$vmname" \ 28 | --storagectl "SATA Controller" \ 29 | --port 0 \ 30 | --device 0 \ 31 | --type hdd \ 32 | --medium ./downloads/$(basename "$dl_link") 33 | 34 | vagrant package --base "$vmname" --output ~/"$vmname".box 35 | 36 | vagrant box add "$vmname" ~/"$vmname".box --name "$vmname" 37 | -------------------------------------------------------------------------------- /library/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zahodi/ansible-mikrotik/fb9c4eddd74c6665237a2dd5120b7c14c3cbadff/library/__init__.py -------------------------------------------------------------------------------- /library/mikrotik.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | __author__ = 'Valentin Gurmeza' 5 | __version__ = "0.1.1" 6 | 7 | DOCUMENTATION = ''' 8 | module = mikrotik 9 | ''' 10 | 11 | 12 | class MikrotikModule(): 13 | def __init__(self, module): 14 | self.module = module 15 | # Variables 16 | # Init attributes 17 | # Get Key name 1st from params if not check env variable 18 | self.user_name = self.module.params["user_name"] 19 | self.ip_addr = self.module.params["ip_addr"] 20 | self.password = self.module.params["password"] 21 | # self.name = self.module.params["name"] 22 | # self.time_out = self.module.params["time_out"] 23 | self.fail_on_warning = self.module.params["fail_on_warning"] 24 | 25 | def main(): 26 | module = AnsibleModule( 27 | argument_spec=dict( 28 | password=dict(default=None), 29 | user_name=dict(default=None), 30 | ip_addr=dict(default=None), 31 | # tags=dict(default=None, type="dict"), 32 | # fail_on_warning=dict(default=True, choices=BOOLEANS, type="bool"), 33 | # fire_forget=dict(default=True, choices=BOOLEANS, type="bool"), 34 | # time_out=dict(default=500, typ="int"), 35 | ), 36 | supports_check_mode=True 37 | ) 38 | if not rosapi_found: 39 | module.fail_json(msg="The ansible mikrotik module requires rosapi library. use 'pip install rosapi' ") 40 | 41 | try: 42 | import rosapi 43 | except ImportError: 44 | rosapi_found = False 45 | else: 46 | rosapi_found = True 47 | MikrotikModule(module).main() 48 | 49 | from ansible.module_utils.basic import AnsibleModule 50 | main() 51 | -------------------------------------------------------------------------------- /library/mt_command.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_command 4 | author: 5 | - "Valentin Gurmeza" 6 | version_added: "2.3" 7 | short_description: Issue mikrotik command 8 | requirements: 9 | - mt_api 10 | description: 11 | - Issue a mikrotik command 12 | options: 13 | hostname: 14 | description: 15 | - hotstname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | command: 26 | description: 27 | - command to be sent to the router. The command must be a command path using 28 | - '/' for word separation 29 | required: True 30 | command_arguments: 31 | description: 32 | - parameters to pass with the command. Must be a dictionary 33 | ''' 34 | 35 | EXAMPLES = ''' 36 | - mt_command: 37 | hostname: "{{ inventory_hostname }}" 38 | username: "{{ mt_user }}" 39 | password: "{{ mt_pass }}" 40 | command: /system/backup/save 41 | command_arguments: 42 | name: ansible_test 43 | password: 123 44 | ''' 45 | 46 | from ansible.module_utils import mt_api 47 | from ansible.module_utils.mt_common import clean_params 48 | from ansible.module_utils.basic import AnsibleModule 49 | 50 | 51 | def main(): 52 | 53 | module = AnsibleModule( 54 | argument_spec=dict( 55 | hostname=dict(required=True), 56 | username=dict(required=True), 57 | password=dict(required=True, no_log=True), 58 | command=dict(required=True, type='str'), 59 | command_arguments=dict(required=False, type='dict'), 60 | ) 61 | ) 62 | 63 | hostname = module.params['hostname'] 64 | username = module.params['username'] 65 | password = module.params['password'] 66 | changed = False 67 | changed_message = [] 68 | 69 | mk = mt_api.Mikrotik(hostname, username, password) 70 | try: 71 | mk.login() 72 | except: 73 | module.fail_json( 74 | msg="Could not log into Mikrotik device." + 75 | " Check the username and password.", 76 | ) 77 | 78 | api_path = module.params['command'] 79 | 80 | if module.params['command_arguments'] != None: 81 | response = mk.api_command(base_path=api_path, params=module.params['command_arguments']) 82 | else: 83 | response = mk.api_command(base_path=api_path) 84 | 85 | if response[-1][0] == '!done': 86 | changed = True 87 | changed_message.append(response) 88 | changed_message.append(api_path) 89 | if module.params['command_arguments'] != None: 90 | changed_message.append(module.params['command_arguments']) 91 | 92 | if changed: 93 | module.exit_json( 94 | failed=False, 95 | changed=True, 96 | msg=changed_message 97 | ) 98 | else: 99 | module.exit_json( 100 | failed=False, 101 | changed=False, 102 | msg="Command failed" 103 | ) 104 | if __name__ == '__main__': 105 | main() 106 | -------------------------------------------------------------------------------- /library/mt_dhcp_server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_dhcp_server.py 4 | author: 5 | - "Valentin Gurmeza" 6 | version_added: "2.4" 7 | short_description: Manage mikrotik dhcp-server endpoints 8 | requirements: 9 | - mt_api 10 | description: 11 | - Mikrotik dhcp-server generic module 12 | options: 13 | hostname: 14 | description: 15 | - hotstname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | parameter: 26 | description: 27 | - sub endpoint for mikrotik tool 28 | required: True 29 | options: 30 | - netwatch 31 | - e-mail 32 | settings: 33 | description: 34 | - All Mikrotik compatible parameters for this particular endpoint. 35 | Any yes/no values must be enclosed in double quotes 36 | state: 37 | description: 38 | - absent or present 39 | ''' 40 | 41 | EXAMPLES = ''' 42 | - mt_dhcp_server: 43 | hostname: "{{ inventory_hostname }}" 44 | username: "{{ mt_user }}" 45 | password: "{{ mt_pass }}" 46 | parameter: network 47 | settings: 48 | address: 192.168.1.0/24 49 | dns: 192.168.1.20 50 | ''' 51 | 52 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 53 | from ansible.module_utils.basic import AnsibleModule 54 | 55 | 56 | 57 | def main(): 58 | module = AnsibleModule( 59 | argument_spec = dict( 60 | hostname = dict(required=True), 61 | username = dict(required=True), 62 | password = dict(required=True, no_log=True), 63 | settings = dict(required=False, type='dict'), 64 | parameter = dict( 65 | required = True, 66 | choices = ['network', 'option', 'dhcp-server'], 67 | type = 'str' 68 | ), 69 | state = dict( 70 | required = False, 71 | choices = ['present', 'absent'], 72 | type = 'str' 73 | ), 74 | ), 75 | supports_check_mode=True 76 | ) 77 | 78 | idempotent_parameter = None 79 | params = module.params 80 | 81 | if params['parameter'] == 'network': 82 | idempotent_parameter = 'address' 83 | params['parameter'] = "dhcp-server/network" 84 | 85 | if params['parameter'] == 'option': 86 | idempotent_parameter = 'name' 87 | params['parameter'] = "dhcp-server/option" 88 | 89 | if params['parameter'] == 'dhcp-server': 90 | idempotent_parameter = 'name' 91 | 92 | mt_obj = MikrotikIdempotent( 93 | hostname = params['hostname'], 94 | username = params['username'], 95 | password = params['password'], 96 | state = params['state'], 97 | desired_params = params['settings'], 98 | idempotent_param = idempotent_parameter, 99 | api_path = '/ip/' + str(params['parameter']), 100 | check_mode = module.check_mode, 101 | 102 | ) 103 | 104 | mt_obj.sync_state() 105 | 106 | if mt_obj.failed: 107 | module.fail_json( 108 | msg = mt_obj.failed_msg 109 | ) 110 | elif mt_obj.changed: 111 | module.exit_json( 112 | failed=False, 113 | changed=True, 114 | msg=mt_obj.changed_msg, 115 | diff={ "prepared": { 116 | "old": mt_obj.old_params, 117 | "new": mt_obj.new_params, 118 | }}, 119 | ) 120 | else: 121 | module.exit_json( 122 | failed=False, 123 | changed=False, 124 | #msg='', 125 | msg=params['settings'], 126 | ) 127 | 128 | if __name__ == '__main__': 129 | main() 130 | -------------------------------------------------------------------------------- /library/mt_facts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | ANSIBLE_METADATA = {'metadata_version': '1.0', 5 | 'status': ['preview'], 6 | 'supported_by': 'community'} 7 | 8 | DOCUMENTATION = ''' 9 | --- 10 | module: mt_facts 11 | short_description: Gather facts for mikrotik devices. 12 | description: 13 | - This module fetches data from the Mikrotik API 14 | author: "kollyma" 15 | options: 16 | filter: 17 | description: 18 | - Filter for a specific fact. 19 | choices: 20 | - interface_ethernet 21 | - system_ntp_client 22 | - system_routerboard 23 | - ip_route 24 | - ip_dns 25 | - ip_address 26 | 27 | requirements: [ 'yaml' ] 28 | ''' 29 | 30 | RETURN = ''' 31 | --- 32 | interface_ethernet: 33 | description: Return list of Mikrotik Interfaces 34 | returned: success 35 | type: list 36 | sample: interface_ethernet [ { 37 | "mac_address": "4C:5E:0C:F4:BF:44", 38 | "master_port": "none", 39 | "mtu": "1500", 40 | "name": "ether1", 41 | "default_name": "ether1", 42 | "disabled": "false", 43 | ... 44 | } ] 45 | system_routerboard: 46 | description: Return Mikrotik System Information 47 | returned: success 48 | type: dict 49 | sample: "system_routerboard": { 50 | "current_firmware": "3.19", 51 | "factory_firmware": "3.19", 52 | "firmware_type": "ar9344", 53 | "model": "951G-2HnD", 54 | "routerboard": "true", 55 | "serial_number": "557E04B12525", 56 | "upgrade_firmware": "3.41" 57 | } 58 | system_ntp_client: 59 | description: Return NTP Client Information 60 | returned: success 61 | type: dict 62 | sample: "system_ntp_client": { 63 | "active_server": "5.148.175.134", 64 | "enabled": "true", 65 | "last_adjustment": "1ms538us", 66 | "last_update_before": "6m19s560ms", 67 | "last_update_from": "5.148.175.134", 68 | "mode": "unicast", 69 | "poll_interval": "15m", 70 | "primary_ntp": "213.251.53.234", 71 | "secondary_ntp": "5.148.175.134", 72 | "server_dns_names": "" 73 | }, 74 | ip_route: 75 | description: Return Mikrotik IP Routes 76 | returned: success 77 | type: dict 78 | sample: "ip_route": { 79 | "active": "true", 80 | "distance": "1", 81 | "dst_address": "0.0.0.0/0", 82 | "dynamic": "true", 83 | "gateway": "8.8.8.8", 84 | "static": "true", 85 | "vrf_interface": "ether1" 86 | } 87 | ip_address: 88 | description: Return Mikrotik IP addresses 89 | returned: success 90 | type: list 91 | sample: "ip_address": [ 92 | { 93 | ".id": "*1", 94 | "actual-interface": "bridge", 95 | "address": "192.168.88.1/24", 96 | "comment": "defconf", 97 | "disabled": "false", 98 | "dynamic": "false", 99 | "interface": "bridge", 100 | "invalid": "false", 101 | "network": "192.168.88.0" 102 | }, ] 103 | ''' 104 | 105 | 106 | import re 107 | from ansible.module_utils import mt_api 108 | from ansible.module_utils.basic import AnsibleModule 109 | 110 | 111 | class MikrotikFacts(object): 112 | 113 | def __init__(self, hostname, username, password): 114 | 115 | self.hostname = hostname 116 | self.username = username 117 | self.password = password 118 | self.login_success = False 119 | self.current_params = {} 120 | self.mk = None 121 | self.failed_msg = None 122 | self.login() 123 | 124 | def run(self): 125 | param = dict() 126 | filter = module.params.get('filter') 127 | self.current_params = self.mk.api_print(base_path='/' + re.sub('_', '/', filter)) 128 | 129 | if len(self.current_params) > 2: 130 | param[filter] = [] 131 | for current_param in self.current_params[:-1]: 132 | param[filter].append(current_param[1]) 133 | else: 134 | param[filter] = dict() 135 | for key, value in self.current_params[0][1].items(): 136 | key = re.sub('-', '_', key) 137 | param[filter][key] = value 138 | 139 | return param 140 | 141 | def login(self): 142 | self.mk = mt_api.Mikrotik( 143 | self.hostname, 144 | self.username, 145 | self.password, 146 | ) 147 | try: 148 | self.mk.login() 149 | self.login_success = True 150 | except: 151 | self.failed_msg = "Could not log into Mikrotik device, check the username and password." 152 | 153 | 154 | def main(): 155 | global module 156 | module = AnsibleModule( 157 | argument_spec = dict( 158 | filter=dict(default='system_routerboard', choices=[ 159 | 'interface_ethernet', 160 | 'system_ntp_client', 161 | 'system_routerboard', 162 | 'ip_route', 163 | 'ip_dns', 164 | 'ip_address', 165 | ]), 166 | hostname=dict(required=True), 167 | username=dict(required=True), 168 | password=dict(required=True, no_log=True), 169 | ), 170 | supports_check_mode=True 171 | ) 172 | 173 | params = module.params 174 | device = MikrotikFacts(params['hostname'], params['username'], params['password']) 175 | mt_facts = device.run() 176 | mt_facts_result = dict(changed=False, ansible_facts=mt_facts) 177 | module.exit_json(**mt_facts_result) 178 | 179 | if __name__ == '__main__': 180 | main() 181 | -------------------------------------------------------------------------------- /library/mt_hotspot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_snmp 4 | author: 5 | - "Valentin Gurmeza" 6 | version_added: "2.4" 7 | short_description: Manage mikrotik hotspot endpoints 8 | requirements: 9 | - mt_api 10 | description: 11 | - Generic mikrotik hotspot module. 12 | options: 13 | hostname: 14 | description: 15 | - hotstname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | parameter: 26 | description: 27 | - sub endpoint for mikrotik hotspot 28 | required: True 29 | options: 30 | - netwatch 31 | - e-mail 32 | - hotspot 33 | settings: 34 | description: 35 | - All Mikrotik compatible parameters for this particular endpoint. 36 | Any yes/no values must be enclosed in double quotes 37 | state: 38 | description: 39 | - absent or present 40 | ''' 41 | 42 | EXAMPLES = ''' 43 | - mt_hotspot: 44 | hostname: "{{ inventory_hostname }}" 45 | username: "{{ mt_user }}" 46 | password: "{{ mt_pass }}" 47 | parameter: profile 48 | settings: 49 | name: Hotspot_Crew 50 | use-radius: yes 51 | ''' 52 | 53 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 54 | from ansible.module_utils.basic import AnsibleModule 55 | 56 | 57 | 58 | def main(): 59 | module = AnsibleModule( 60 | argument_spec = dict( 61 | hostname = dict(required=True), 62 | username = dict(required=True), 63 | password = dict(required=True, no_log=True), 64 | settings = dict(required=False, type='dict'), 65 | parameter = dict( 66 | required = True, 67 | choices = ['hotspot', 'profile', 'walled-garden'], 68 | type = 'str' 69 | ), 70 | state = dict( 71 | required = False, 72 | choices = ['present', 'absent'], 73 | type = 'str' 74 | ), 75 | ), 76 | supports_check_mode=True 77 | ) 78 | 79 | idempotent_parameter = None 80 | params = module.params 81 | idempotent_parameter = 'name' 82 | 83 | if params['parameter'] == 'profile': 84 | params['parameter'] = "hotspot/profile" 85 | 86 | if params['parameter'] == 'walled-garden': 87 | idempotent_parameter = 'comment' 88 | params['parameter'] = "hotspot/walled-garden" 89 | 90 | mt_obj = MikrotikIdempotent( 91 | hostname = params['hostname'], 92 | username = params['username'], 93 | password = params['password'], 94 | state = params['state'], 95 | desired_params = params['settings'], 96 | idempotent_param = idempotent_parameter, 97 | api_path = '/ip/' + str(params['parameter']), 98 | check_mode = module.check_mode, 99 | 100 | ) 101 | 102 | mt_obj.sync_state() 103 | 104 | if mt_obj.failed: 105 | module.fail_json( 106 | msg = mt_obj.failed_msg 107 | ) 108 | elif mt_obj.changed: 109 | module.exit_json( 110 | failed=False, 111 | changed=True, 112 | msg=mt_obj.changed_msg, 113 | diff={ "prepared": { 114 | "old": mt_obj.old_params, 115 | "new": mt_obj.new_params, 116 | }}, 117 | ) 118 | else: 119 | module.exit_json( 120 | failed=False, 121 | changed=False, 122 | #msg='', 123 | msg=params['settings'], 124 | ) 125 | 126 | if __name__ == '__main__': 127 | main() 128 | -------------------------------------------------------------------------------- /library/mt_interface_wireless.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_interface_wireless 4 | author: 5 | - "Valentin Gurmeza" 6 | version_added: "2.4" 7 | short_description: Manage mikrotik interface_wireless endpoints 8 | requirements: 9 | - mt_api 10 | description: 11 | - Generic mikrotik interface wireless module. 12 | options: 13 | hostname: 14 | description: 15 | - hotstname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | parameter: 26 | description: 27 | - sub endpoint for mikrotik interface wireless 28 | required: True 29 | options: 30 | - security-profiles 31 | settings: 32 | description: 33 | - All Mikrotik compatible parameters for this particular endpoint. 34 | Any yes/no values must be enclosed in double quotes 35 | state: 36 | description: 37 | - absent or present 38 | ''' 39 | 40 | EXAMPLES = ''' 41 | - mt_interface_wireless: 42 | hostname: "{{ inventory_hostname }}" 43 | username: "{{ mt_user }}" 44 | password: "{{ mt_pass }}" 45 | parameter: security-profiles 46 | state: present 47 | settings: 48 | name: test1 49 | supplicant-identity: test 50 | 51 | ''' 52 | 53 | from ansible.module_utils.basic import AnsibleModule 54 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 55 | 56 | 57 | def main(): 58 | module = AnsibleModule( 59 | argument_spec = dict( 60 | hostname = dict(required=True), 61 | username = dict(required=True), 62 | password = dict(required=True, no_log=True), 63 | settings = dict(required=False, type='dict'), 64 | parameter = dict( 65 | required = True, 66 | choices = ['security-profiles'], 67 | type = 'str' 68 | ), 69 | state = dict( 70 | required = False, 71 | choices = ['present', 'absent'], 72 | type = 'str' 73 | ), 74 | ), 75 | supports_check_mode=True 76 | ) 77 | 78 | idempotent_parameter = None 79 | params = module.params 80 | 81 | idempotent_parameter = 'name' 82 | 83 | mt_obj = MikrotikIdempotent( 84 | hostname = params['hostname'], 85 | username = params['username'], 86 | password = params['password'], 87 | state = params['state'], 88 | desired_params = params['settings'], 89 | idempotent_param = idempotent_parameter, 90 | api_path = '/interface/wireless/' + str(params['parameter']), 91 | check_mode = module.check_mode 92 | ) 93 | 94 | mt_obj.sync_state() 95 | 96 | if mt_obj.failed: 97 | module.fail_json( 98 | msg = mt_obj.failed_msg 99 | ) 100 | elif mt_obj.changed: 101 | module.exit_json( 102 | failed=False, 103 | changed=True, 104 | msg=mt_obj.changed_msg, 105 | diff={ "prepared": { 106 | "old": mt_obj.old_params, 107 | "new": mt_obj.new_params, 108 | }}, 109 | ) 110 | else: 111 | module.exit_json( 112 | failed=False, 113 | changed=False, 114 | msg=params['settings'], 115 | ) 116 | 117 | if __name__ == '__main__': 118 | main() 119 | -------------------------------------------------------------------------------- /library/mt_interfaces.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_interface.py 4 | author: 5 | - "Shaun Smiley" 6 | - "Valentin Gurmeza" 7 | version_added: "2.4" 8 | short_description: Manage mikrotik interfaces 9 | requirements: 10 | - mt_api 11 | description: 12 | - manage interfaces and settings 13 | options: 14 | hostname: 15 | description: 16 | - hotstname of mikrotik router 17 | required: True 18 | username: 19 | description: 20 | - username used to connect to mikrotik router 21 | required: True 22 | password: 23 | description: 24 | - password used for authentication to mikrotik router 25 | required: True 26 | parameter: 27 | description: 28 | - sub endpoint for mikrotik tool 29 | required: True 30 | options: 31 | - ovpn-client 32 | - ethernet 33 | - vlan 34 | - bridge 35 | - bridge port 36 | - bridge settings 37 | settings: 38 | description: 39 | - All Mikrotik compatible parameters for this particular endpoint. 40 | Any yes/no values must be enclosed in double quotes 41 | required: True 42 | state: 43 | description: 44 | - absent or present 45 | required: Flase 46 | ''' 47 | 48 | EXAMPLES = ''' 49 | - mt_interfaces: 50 | hostname: "{{ inventory_hostname }}" 51 | username: "{{ mt_user }}" 52 | password: "{{ mt_pass }}" 53 | parameter: "ethernet" 54 | state: present 55 | settings: 56 | name: ether2 57 | comment: Ansible controlled ether2 58 | mtu: 1501 59 | ''' 60 | 61 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 62 | from ansible.module_utils.basic import AnsibleModule 63 | 64 | 65 | def main(): 66 | module = AnsibleModule( 67 | argument_spec=dict( 68 | hostname=dict(required=True), 69 | username=dict(required=True), 70 | password=dict(required=True, no_log=True), 71 | settings=dict(required=True, type='dict'), 72 | parameter=dict( 73 | required=True, 74 | choices=[ 75 | 'ethernet', 76 | 'vlan', 77 | 'ovpn-client', 78 | 'bridge', 79 | 'bridge port', 80 | 'bridge settings' 81 | ], 82 | type='str' 83 | ), 84 | state=dict( 85 | required = False, 86 | choices = ['present', 'absent'], 87 | type = 'str' 88 | ) 89 | ), 90 | supports_check_mode=True 91 | ) 92 | 93 | params = module.params 94 | if params['parameter'] == 'bridge port': 95 | params['parameter'] = 'bridge/port' 96 | idempotent_parameter = "interface" 97 | elif params['parameter'] == 'bridge settings': 98 | params['parameter'] = 'bridge/settings' 99 | idempotent_parameter = None 100 | else: 101 | idempotent_parameter = 'name' 102 | 103 | mt_obj = MikrotikIdempotent( 104 | hostname = params['hostname'], 105 | username = params['username'], 106 | password = params['password'], 107 | state = params['state'], 108 | desired_params = params['settings'], 109 | idempotent_param = idempotent_parameter, 110 | api_path = '/interface/' + str(params['parameter']), 111 | check_mode = module.check_mode 112 | ) 113 | 114 | # exit if login failed 115 | if not mt_obj.login_success: 116 | module.fail_json( 117 | msg = mt_obj.failed_msg 118 | ) 119 | 120 | # add, remove or edit things 121 | mt_obj.sync_state() 122 | 123 | if mt_obj.failed: 124 | module.fail_json( 125 | msg = mt_obj.failed_msg 126 | ) 127 | elif mt_obj.changed: 128 | module.exit_json( 129 | failed=False, 130 | changed=True, 131 | msg=mt_obj.changed_msg, 132 | diff={ "prepared": { 133 | "old": mt_obj.old_params, 134 | "new": mt_obj.new_params, 135 | }}, 136 | ) 137 | else: 138 | module.exit_json( 139 | failed=False, 140 | changed=False, 141 | #msg='', 142 | msg=params['settings'], 143 | ) 144 | 145 | if __name__ == '__main__': 146 | main() 147 | -------------------------------------------------------------------------------- /library/mt_ip.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_ip 4 | author: 5 | - "Valentin Gurmeza" 6 | - "Shaun Smiley" 7 | version_added: "2.3" 8 | short_description: Manage mikrotik ip endpoints 9 | requirements: 10 | - mt_api 11 | description: 12 | - enable, disable, or modify a ip endpoint settings 13 | options: 14 | hostname: 15 | description: 16 | - hotstname of mikrotik router 17 | required: True 18 | username: 19 | description: 20 | - username used to connect to mikrotik router 21 | required: True 22 | password: 23 | description: 24 | - password used for authentication to mikrotik router 25 | required: True 26 | parameter: 27 | description: 28 | - sub endpoint for mikrotik snmp 29 | required: True 30 | options: 31 | - address-list 32 | - netwatch 33 | - e-mail 34 | settings: 35 | description: 36 | - All Mikrotik compatible parameters for this particular endpoint. 37 | Any yes/no values must be enclosed in double quotes 38 | state: 39 | description: 40 | - absent or present 41 | ''' 42 | 43 | EXAMPLES = ''' 44 | - mt_service: 45 | hostname: "{{ inventory_hostname }}" 46 | username: "{{ mt_user }}" 47 | password: "{{ mt_pass }}" 48 | parameter: service 49 | settings: 50 | disabled: no 51 | name: ftp 52 | address: 192.168.52.3 53 | ''' 54 | 55 | from ansible.module_utils.basic import AnsibleModule 56 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 57 | 58 | 59 | def main(): 60 | module = AnsibleModule( 61 | argument_spec = dict( 62 | hostname = dict(required=True), 63 | username = dict(required=True), 64 | password = dict(required=True, no_log=True), 65 | settings = dict(required=False, type='dict'), 66 | parameter = dict( 67 | required = True, 68 | choices = ['service', 'pool', 'firewall/address-list'], 69 | type = 'str' 70 | ), 71 | state = dict( 72 | required = False, 73 | choices = ['present', 'absent'], 74 | type = 'str' 75 | ), 76 | ), 77 | supports_check_mode=True 78 | ) 79 | 80 | params = module.params 81 | if params['parameter'] == 'firewall/address-list': 82 | idempotent_parameter = 'address' 83 | else: 84 | idempotent_parameter = 'name' 85 | 86 | mt_obj = MikrotikIdempotent( 87 | hostname = params['hostname'], 88 | username = params['username'], 89 | password = params['password'], 90 | state = params['state'], 91 | desired_params = params['settings'], 92 | idempotent_param = idempotent_parameter, 93 | api_path = '/ip/' + str(params['parameter']), 94 | check_mode = module.check_mode 95 | ) 96 | 97 | mt_obj.sync_state() 98 | 99 | if mt_obj.failed: 100 | module.fail_json( 101 | msg = mt_obj.failed_msg 102 | ) 103 | elif mt_obj.changed: 104 | module.exit_json( 105 | failed=False, 106 | changed=True, 107 | msg=mt_obj.changed_msg, 108 | diff={ "prepared": { 109 | "old": mt_obj.old_params, 110 | "new": mt_obj.new_params, 111 | }}, 112 | ) 113 | else: 114 | module.exit_json( 115 | failed=False, 116 | changed=False, 117 | msg=params['settings'], 118 | ) 119 | if __name__ == '__main__': 120 | main() 121 | -------------------------------------------------------------------------------- /library/mt_ip_address.py: -------------------------------------------------------------------------------- 1 | DOCUMENTATION = ''' 2 | module: mt_ip_address 3 | author: 4 | - "Valentin Gurmeza" 5 | - "Shaun Smiley" 6 | - "Antoni Matamalas" 7 | version_added: "2.3" 8 | short_description: Manage mikrotik /ip/addresses 9 | requirements: 10 | - mt_api 11 | description: 12 | - Manage addresses on interfaces 13 | options: 14 | hostname: 15 | description: 16 | - hotstname of mikrotik router 17 | required: True 18 | username: 19 | description: 20 | - username used to connect to mikrotik router 21 | required: True 22 | password: 23 | description: 24 | - password used for authentication to mikrotik router 25 | required: True 26 | idempotent: 27 | description: 28 | - parameter that will define the behavior for the ip address status. 29 | - If "interface" is used, only one IP will be allowed per interface. 30 | The "state" parameter will define if the IP is added, edited or 31 | removed. No settings options are required to removed the IP from an 32 | interface 33 | - If "address" is used, and interface will be able to have multiple IPs, 34 | but address will only be added or removed. In order to change an IP, it 35 | will have to be first removed and then added to the interface in two 36 | tasks. 37 | required: False 38 | default: address 39 | settings: 40 | description: 41 | - All Mikrotik compatible parameters for this particular endpoint. 42 | Any yes/no values must be enclosed in double quotes 43 | required: True 44 | state: 45 | description: 46 | - Depending on the idempotent option, it will define the status of the IP 47 | on an interface 48 | required: False 49 | default: present 50 | ''' 51 | 52 | EXAMPLES = ''' 53 | # Add IP to an interface with a comment. If the interface has already an IP it 54 | # will add as a sencond IP 55 | - mt_ip_address: 56 | hostname: "{{ inventory_hostname }}" 57 | username: "{{ mt_user }}" 58 | password: "{{ mt_pass }}" 59 | idempotent: "address" 60 | state: "present" 61 | settings: 62 | interface: "ether2" 63 | address: "192.168.88.2/24" 64 | network: "192.168.88.0/24" 65 | comment: "link 3" 66 | 67 | # Assign IP to the interface. If the interface has any previous IP, it will be 68 | # replaced by this one. 69 | - mt_ip_address: 70 | hostname: "{{ inventory_hostname }}" 71 | username: "{{ mt_user }}" 72 | password: "{{ mt_pass }}" 73 | idempotent: "interface" 74 | state: "present" 75 | settings: 76 | interface: "ether2" 77 | address: "192.168.88.2/24" 78 | network: "192.168.88.0/24" 79 | comment: "link 3" 80 | 81 | # Remove any IP from an interface 82 | - mt_ip_address: 83 | hostname: "{{ inventory_hostname }}" 84 | username: "{{ mt_user }}" 85 | password: "{{ mt_pass }}" 86 | idempotent: "interface" 87 | state: "absent" 88 | settings: 89 | interface: "ether2" 90 | ''' 91 | 92 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 93 | from ansible.module_utils.basic import AnsibleModule 94 | 95 | 96 | def main(): 97 | 98 | module = AnsibleModule( 99 | argument_spec=dict( 100 | hostname = dict(required=True), 101 | username = dict(required=True), 102 | password = dict(required=True, no_log=True), 103 | settings = dict(required=True, type='dict'), 104 | idempotent = dict( 105 | required = False, 106 | default = 'address', 107 | choices = ['address', 'interface'], 108 | type = 'str' 109 | ), 110 | state = dict( 111 | required = False, 112 | default = "present", 113 | choices = ['present', 'absent'], 114 | type = 'str' 115 | ), 116 | ), 117 | supports_check_mode=True 118 | ) 119 | 120 | params = module.params 121 | mt_obj = MikrotikIdempotent( 122 | hostname = params['hostname'], 123 | username = params['username'], 124 | password = params['password'], 125 | state = params['state'], 126 | desired_params = params['settings'], 127 | idempotent_param = params['idempotent'], 128 | api_path = '/ip/address', 129 | check_mode = module.check_mode 130 | ) 131 | 132 | # exit if login failed 133 | if not mt_obj.login_success: 134 | module.fail_json( 135 | msg = mt_obj.failed_msg 136 | ) 137 | 138 | # add, remove or edit things 139 | mt_obj.sync_state() 140 | 141 | if mt_obj.failed: 142 | module.fail_json( 143 | msg = mt_obj.failed_msg 144 | ) 145 | elif mt_obj.changed: 146 | module.exit_json( 147 | failed=False, 148 | changed=True, 149 | msg=mt_obj.changed_msg, 150 | diff={ "prepared": { 151 | "old": mt_obj.old_params, 152 | "new": mt_obj.new_params, 153 | }}, 154 | ) 155 | else: 156 | module.exit_json( 157 | failed=False, 158 | changed=False, 159 | #msg='', 160 | msg=params['settings'], 161 | ) 162 | 163 | if __name__ == '__main__': 164 | main() 165 | 166 | -------------------------------------------------------------------------------- /library/mt_ip_firewall.py: -------------------------------------------------------------------------------- 1 | DOCUMENTATION = ''' 2 | module: mt_ip_firewall 3 | author: 4 | - "Valentin Gurmeza" 5 | - "Shaun Smiley" 6 | version_added: "2.3" 7 | short_description: Manage mikrotik /ip/firewall/ 8 | requirements: 9 | - mt_api 10 | description: 11 | - Generic mikrotik firewall module 12 | options: 13 | hostname: 14 | description: 15 | - hotstname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | rule: 26 | description: 27 | - a list containing dictionary parameters. 28 | action, chain, comment, and place-before keys are required 29 | parameter: 30 | description: 31 | - sub endpoint for mikrotik firewall 32 | required: True 33 | options: 34 | - filter 35 | - nat 36 | - mangle 37 | settings: 38 | description: 39 | - All Mikrotik compatible parameters for this particular endpoint. 40 | Any yes/no values must be enclosed in double quotes 41 | - if a firewall list containing dictionary parameters, 42 | action, chain, comment, and place-before keys are required 43 | state: 44 | description: 45 | - absent or present 46 | force: 47 | description: 48 | - True/False value to force remove the rule regardless of the position in the rule list. 49 | ''' 50 | 51 | EXAMPLES = ''' 52 | - mt_ip_firewall: 53 | hostname: "{{ inventory_hostname }}" 54 | username: "{{ mt_user }}" 55 | password: "{{ mt_pass }}" 56 | state: present 57 | parameter: filter 58 | rule: 59 | action: accept 60 | chain: forward 61 | comment: controlled by ansible 62 | place-before: "2" 63 | ''' 64 | 65 | from ansible.module_utils.basic import AnsibleModule 66 | from ansible.module_utils import mt_api 67 | import re 68 | from copy import copy 69 | 70 | 71 | def main(): 72 | 73 | module = AnsibleModule( 74 | argument_spec=dict( 75 | hostname = dict(required=True), 76 | username = dict(required=True), 77 | password = dict(required=True, no_log=True), 78 | rule = dict(required=False, type='dict'), 79 | parameter = dict(required=True, type='str'), 80 | state = dict( 81 | required = False, 82 | default = "present", 83 | choices = ['present', 'absent'], 84 | type = 'str' 85 | ), 86 | ), 87 | supports_check_mode=True 88 | ) 89 | 90 | hostname = module.params['hostname'] 91 | username = module.params['username'] 92 | password = module.params['password'] 93 | rule = module.params['rule'] 94 | state = module.params['state'] 95 | api_path = '/ip/firewall/' + module.params['parameter'] 96 | check_mode = module.check_mode 97 | # ############################################## 98 | # Check if "place-before" is an integer 99 | # ############################################# 100 | try: 101 | desired_order = int(rule['place-before']) 102 | except: 103 | module.exit_json( 104 | failed=True, 105 | changed=False, 106 | msg="place-before is not set or is not set to an integer", 107 | ) 108 | changed = False 109 | msg = "" 110 | 111 | mk = mt_api.Mikrotik(hostname, username, password) 112 | try: 113 | mk.login() 114 | except: 115 | module.fail_json( 116 | msg="Could not log into Mikrotik device." + 117 | " Check the username and password.", 118 | ) 119 | 120 | filter_response = mk.api_print(api_path) 121 | current_rule = None 122 | current_id = None 123 | existing_order = None 124 | last_item = len(filter_response) - 2 125 | changed_msg = [] 126 | 127 | # Always set the comment to the order_number 128 | if 'comment' in rule: 129 | rule['comment'] = str(desired_order) + " " + str(rule['comment']) 130 | else: 131 | rule['comment'] = str(desired_order) 132 | 133 | if desired_order <= last_item: 134 | placed_at_the_end = False 135 | else: 136 | placed_at_the_end = True 137 | # remove the place-before if we are placing 138 | # the rule at the bottom of the chain 139 | rule.pop('place-before', None) 140 | 141 | # Check rule is not present 142 | # find existing rule 143 | # current_rule is what's on mikrotik right now 144 | for index, current_param in enumerate(filter_response): 145 | if 'comment' in current_param[1]: 146 | if re.search(r"^" + str(desired_order) + "\s+", current_param[1]['comment']): 147 | current_id = current_param[1]['.id'] 148 | existing_order = index 149 | current_rule = current_param[1] 150 | # remove the place-before since we'll be editing not moving it 151 | rule.pop('place-before', None) 152 | 153 | # ensure the rule if state is present 154 | if state == "present": 155 | # if we don't have an existing rule to match 156 | # the desired we create a new one 157 | if not current_rule: 158 | if not check_mode: 159 | mk.api_add(api_path, rule) 160 | changed = True, 161 | # if current_rule is true we need to ensure the changes 162 | else: 163 | out_params = {} 164 | old_params = {} 165 | for desired_param in rule: 166 | rule[desired_param] = str(rule[desired_param]) 167 | if desired_param in current_rule: 168 | if current_rule[desired_param] != rule[desired_param]: 169 | out_params[desired_param] = rule[desired_param] 170 | old_params[desired_param] = current_rule[desired_param] 171 | else: 172 | out_params[desired_param] = rule[desired_param] 173 | if desired_param in current_rule: 174 | old_params[desired_param] = current_rule[desired_param] 175 | 176 | # When out_params has been set it means we found our diff 177 | # and will set it on the mikrotik 178 | if out_params: 179 | if current_id is not None: 180 | out_params['.id'] = current_id 181 | 182 | if not check_mode: 183 | mk.api_edit( 184 | base_path = api_path, 185 | params = out_params 186 | ) 187 | 188 | # we don't need to show the .id in the changed message 189 | if '.id' in out_params: 190 | del out_params['.id'] 191 | changed = True 192 | 193 | changed_msg.append({ 194 | "new_params": out_params, 195 | "old_params": old_params, 196 | }) 197 | 198 | # ensure the rule is in right position 199 | if current_id: 200 | if int(existing_order) != int(desired_order): 201 | api_path += '/move' 202 | params = False 203 | if placed_at_the_end: 204 | if existing_order > last_item: 205 | params = { 206 | '.id': current_id, 207 | } 208 | else: 209 | params = { 210 | '.id': current_id, 211 | 'destination': desired_order 212 | } 213 | if params: 214 | if not check_mode: 215 | mk.api_command(api_path, params) 216 | changed_msg.append({ 217 | "moved": existing_order, 218 | "to": old_params, 219 | }) 220 | changed = True 221 | 222 | ##################################### 223 | # Remove the rule 224 | ##################################### 225 | elif state == "absent": 226 | if current_rule: 227 | if not check_mode: 228 | mk.api_remove(api_path, current_id) 229 | changed = True 230 | changed_msg.append("removed rule: " + str(desired_order)) 231 | else: 232 | failed = True 233 | 234 | if changed: 235 | module.exit_json( 236 | failed=False, 237 | changed=True, 238 | msg=changed_msg 239 | ) 240 | elif not changed: 241 | module.exit_json( 242 | failed=False, 243 | changed=False, 244 | ) 245 | else: 246 | module.fail_json() 247 | 248 | ########################################## 249 | # To DO: 250 | # Clean up duplicate items 251 | ########################################### 252 | 253 | if __name__ == '__main__': 254 | main() 255 | -------------------------------------------------------------------------------- /library/mt_ip_firewall_addresslist.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_ip_firewall_filter 4 | author: 5 | - "Valentin Gurmeza" 6 | - "Shaun Smiley" 7 | version_added: "2.3" 8 | short_description: Manage mikrotik /ip/firewall/filter 9 | requirements: 10 | - mt_api 11 | description: 12 | - FILL ME OUT 13 | options: 14 | hostname: 15 | description: 16 | - 17 | username: 18 | description: 19 | - 20 | password: 21 | description: 22 | - 23 | list-name: 24 | description: 25 | - name of the address-list 26 | state: 27 | description: 28 | - present or absent 29 | address_list: 30 | description: 31 | - A list of single IP addresses or range of IPs to add to address-list. 32 | Can also be a set to a hostname which will create a dynamic entry 33 | in the list with the proper IP address for the record (as of 6.38.1) 34 | ''' 35 | 36 | EXAMPLES = ''' 37 | - mt_ip_firewall_addresslist: 38 | hostname: "{{ inventory_hostname }}" 39 | username: "{{ mt_user }}" 40 | password: "{{ mt_pass }}" 41 | state:   "present" 42 | name:   "block_all" 43 | dynamic: false 44 | address_list: 45 | - 192.168.10.1 46 | - yahoo.com 47 | - 19.134.52.23/23 48 | ''' 49 | 50 | from ansible.module_utils import mt_api 51 | from ansible.module_utils.basic import AnsibleModule 52 | 53 | 54 | def main(): 55 | 56 | module = AnsibleModule( 57 | argument_spec=dict( 58 | hostname = dict(required=True), 59 | username = dict(required=True), 60 | password = dict(required=True, no_log=True), 61 | list_name = dict(required=True, type='str'), 62 | address_list = dict(required=False, type='list'), 63 | state = dict( 64 | required = False, 65 | default = "present", 66 | choices = ['present', 'absent', 'force'], 67 | type = 'str' 68 | ), 69 | ), 70 | supports_check_mode=True 71 | ) 72 | 73 | hostname = module.params['hostname'] 74 | username = module.params['username'] 75 | password = module.params['password'] 76 | ansible_list_name = module.params['list_name'] 77 | ansible_address_list = module.params['address_list'] 78 | state = module.params['state'] 79 | check_mode = module.check_mode 80 | changed = False 81 | msg = "" 82 | 83 | address_list_path = '/ip/firewall/address-list' 84 | mk = mt_api.Mikrotik(hostname, username, password) 85 | try: 86 | mk.login() 87 | except: 88 | module.fail_json( 89 | msg="Could not log into Mikrotik device." + 90 | " Check the username and password.", 91 | ) 92 | 93 | response = mk.api_print(address_list_path) 94 | mikrotik_address_list = [] 95 | mikrotik_address_id = {} 96 | list_name = ansible_list_name 97 | for item in response: 98 | if 'list' in item[1].keys(): 99 | address = item[1]['address'] 100 | if item[1]['list'] == list_name: 101 | temp_dict = {} 102 | temp_dict['address'] = item[1]['address'] 103 | if 'comment' in item[1].keys(): 104 | temp_dict['comment'] = item[1]['comment'] 105 | mikrotik_address_list.append(dict(temp_dict)) 106 | mikrotik_address_id[address] = item[1]['.id'] 107 | 108 | if state == "present": 109 | if ansible_address_list == mikrotik_address_list: 110 | module.exit_json( 111 | changed = False, 112 | failed = False, 113 | msg = "list up to date", 114 | ) 115 | common_list = [] 116 | for item in ansible_address_list: 117 | for item2 in mikrotik_address_list: 118 | if item['address'] in item2['address']: 119 | common_list.append(item['address']) 120 | if item['comment'] in item2['comment']: 121 | ################## 122 | # update comment 123 | ################# 124 | pass 125 | 126 | ################################# 127 | # build add_list 128 | # add item missing from mikrotik 129 | ################################# 130 | add_list = [] 131 | for item in ansible_address_list: 132 | if item['address'] not in common_list: 133 | temp_dict = {} 134 | temp_dict['address'] = item['address'] 135 | temp_dict['comment'] = item['comment'] 136 | add_list.append(dict(temp_dict)) 137 | 138 | for i in add_list: 139 | #address = i['address'] 140 | #comment = i['comment'] 141 | add_dictionary = { 142 | "address": i['address'], 143 | "list": list_name, 144 | "comment": i['comment'] 145 | } 146 | if not check_mode: 147 | mk.api_add(address_list_path, add_dictionary) 148 | changed = True 149 | 150 | ##################### 151 | # build remove list 152 | ###################### 153 | remove_list = [] 154 | for item in mikrotik_address_list: 155 | if item['address'] not in common_list: 156 | remove_list.append(item['address']) 157 | ####################################### 158 | # Remove every item in the address_list 159 | ####################################### 160 | for i in remove_list: 161 | remove_id = mikrotik_address_id[i] 162 | if not check_mode: 163 | mk.api_remove(address_list_path, remove_id) 164 | if not changed: 165 | changed = True 166 | else: 167 | ####################################### 168 | # Remove every item 169 | ####################################### 170 | for remove_id in mikrotik_address_id.values(): 171 | if not check_mode: 172 | mk.api_remove(address_list_path, remove_id) 173 | if not changed: 174 | changed = True 175 | 176 | if changed: 177 | module.exit_json( 178 | changed = True, 179 | failed = False, 180 | msg = ansible_list_name + "has been modified", 181 | ) 182 | else: 183 | module.exit_json( 184 | changed = False, 185 | failed = False, 186 | msg = ansible_list_name + " is up to date", 187 | ) 188 | 189 | 190 | if __name__ == '__main__': 191 | main() 192 | -------------------------------------------------------------------------------- /library/mt_login_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | import socket 3 | from ansible.module_utils import mt_api 4 | 5 | from ansible.module_utils.basic import AnsibleModule 6 | 7 | 8 | def main(): 9 | 10 | module = AnsibleModule( 11 | argument_spec=dict( 12 | hostname=dict(required=True), 13 | username=dict(required=True), 14 | password=dict(required=True, no_log=True), 15 | ) 16 | ) 17 | 18 | hostname = module.params['hostname'] 19 | username = module.params['username'] 20 | password = module.params['password'] 21 | changed = False 22 | msg = "" 23 | 24 | mk = mt_api.Mikrotik(hostname,username,password) 25 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 26 | result = sock.connect_ex((hostname, 8728)) 27 | if result == 0: 28 | try: 29 | mk.login() 30 | except: 31 | module.fail_json( 32 | msg="Could not log into Mikrotik device. Check the username and password." 33 | ) 34 | else: 35 | module.fail_json( 36 | msg="Could not access RouterOS api." + " Verify API service is enabled and not blocked by firewall." 37 | ) 38 | 39 | 40 | # response = apiros.talk([b'/ip/address/add', b'=address=192.168.15.2/24', b'=interface=ether7']) 41 | module.exit_json( 42 | changed=False, 43 | failed=False, 44 | ) 45 | 46 | if __name__ == '__main__': 47 | main() 48 | -------------------------------------------------------------------------------- /library/mt_neighbor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_snmp 4 | author: 5 | - "Valentin Gurmeza" 6 | version_added: "2.4" 7 | short_description: Manage mikrotik neighbor endpoints 8 | requirements: 9 | - mt_api 10 | description: 11 | - Generic mikrotik neighbor module. 12 | options: 13 | hostname: 14 | description: 15 | - hotstname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | parameter: 26 | description: 27 | - sub endpoint for mikrotik neighbor 28 | required: True 29 | options: 30 | - netwatch 31 | - e-mail 32 | settings: 33 | description: 34 | - All Mikrotik compatible parameters for this particular endpoint. 35 | Any yes/no values must be enclosed in double quotes 36 | state: 37 | description: 38 | - absent or present 39 | ''' 40 | 41 | EXAMPLES = ''' 42 | - mt_hotspot: 43 | hostname: "{{ inventory_hostname }}" 44 | username: "{{ mt_user }}" 45 | password: "{{ mt_pass }}" 46 | parameter: discovery 47 | settings: 48 | name: ether7 49 | discover: "yes" 50 | ''' 51 | 52 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 53 | from ansible.module_utils.basic import AnsibleModule 54 | 55 | 56 | 57 | def main(): 58 | module = AnsibleModule( 59 | argument_spec = dict( 60 | hostname = dict(required=True), 61 | username = dict(required=True), 62 | password = dict(required=True, no_log=True), 63 | settings = dict(required=False, type='dict'), 64 | parameter = dict( 65 | required = True, 66 | choices = ['discovery'], 67 | type = 'str' 68 | ), 69 | state = dict( 70 | required = False, 71 | choices = ['present', 'absent'], 72 | type = 'str' 73 | ), 74 | ), 75 | supports_check_mode=True 76 | ) 77 | 78 | idempotent_parameter = None 79 | params = module.params 80 | idempotent_parameter = 'name' 81 | 82 | 83 | mt_obj = MikrotikIdempotent( 84 | hostname = params['hostname'], 85 | username = params['username'], 86 | password = params['password'], 87 | state = params['state'], 88 | desired_params = params['settings'], 89 | idempotent_param = idempotent_parameter, 90 | api_path = '/ip/neighbor/' + str(params['parameter']), 91 | check_mode = module.check_mode 92 | ) 93 | 94 | mt_obj.sync_state() 95 | 96 | if mt_obj.failed: 97 | module.fail_json( 98 | msg = mt_obj.failed_msg 99 | ) 100 | elif mt_obj.changed: 101 | module.exit_json( 102 | failed=False, 103 | changed=True, 104 | msg=mt_obj.changed_msg, 105 | diff={ "prepared": { 106 | "old": mt_obj.old_params, 107 | "new": mt_obj.new_params, 108 | }}, 109 | ) 110 | else: 111 | module.exit_json( 112 | failed=False, 113 | changed=False, 114 | #msg='', 115 | msg=params['settings'], 116 | ) 117 | 118 | if __name__ == '__main__': 119 | main() 120 | -------------------------------------------------------------------------------- /library/mt_ppp_profile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_ppp_profile 4 | author: 5 | - "Colin Zwiebel" 6 | version_added: "2.4.1" 7 | short_description: Manage mikrotik ppp profiles 8 | requirements: 9 | - mt_api 10 | description: 11 | - Generic mikrotik ppp profile management module. 12 | options: 13 | hostname: 14 | description: 15 | - hostname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | settings: 26 | description: 27 | - All Mikrotik compatible parameters for the ppp-profile endpoint. 28 | Any yes/no values must be enclosed in double quotes 29 | state: 30 | description: 31 | - absent or present 32 | ''' 33 | 34 | EXAMPLES = ''' 35 | - mt_ppp_profile: 36 | hostname: "{{ inventory_hostname }}" 37 | username: "{{ mt_user }}" 38 | password: "{{ mt_pass }}" 39 | state: present 40 | settings: 41 | name: example-profile 42 | local-address: 1.2.3.4 43 | change-tcp-mss: "y" 44 | use-compression: "y" 45 | use-encryption: required 46 | ''' 47 | 48 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 49 | from ansible.module_utils.basic import AnsibleModule 50 | 51 | 52 | def main(): 53 | module = AnsibleModule( 54 | argument_spec = dict( 55 | hostname = dict(required=True), 56 | username = dict(required=True), 57 | password = dict(required=True, no_log=True), 58 | settings = dict(required=False, type='dict'), 59 | state = dict( 60 | required = False, 61 | choices = ['present', 'absent'], 62 | type = 'str' 63 | ), 64 | ), 65 | supports_check_mode=True 66 | ) 67 | 68 | params = module.params 69 | mt_obj = MikrotikIdempotent( 70 | hostname = params['hostname'], 71 | username = params['username'], 72 | password = params['password'], 73 | state = params['state'], 74 | desired_params = params['settings'], 75 | idempotent_param = 'name', 76 | api_path = '/ppp/profile', 77 | check_mode = module.check_mode 78 | ) 79 | 80 | mt_obj.sync_state() 81 | 82 | if mt_obj.failed: 83 | module.fail_json( 84 | msg = mt_obj.failed_msg 85 | ) 86 | elif mt_obj.changed: 87 | module.exit_json( 88 | failed=False, 89 | changed=True, 90 | msg=mt_obj.changed_msg, 91 | diff={ "prepared": { 92 | "old": mt_obj.old_params, 93 | "new": mt_obj.new_params, 94 | }}, 95 | ) 96 | else: 97 | module.exit_json( 98 | failed=False, 99 | changed=False, 100 | #msg='', 101 | msg=params['settings'], 102 | ) 103 | 104 | if __name__ == '__main__': 105 | main() 106 | -------------------------------------------------------------------------------- /library/mt_ppp_secret.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_ppp_secret 4 | author: 5 | - "Colin Zwiebel" 6 | version_added: "2.4.1" 7 | short_description: Manage mikrotik ppp secrets (vpn users) 8 | requirements: 9 | - mt_api 10 | description: 11 | - Generic mikrotik ppp secret module. 12 | options: 13 | hostname: 14 | description: 15 | - hostname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | settings: 26 | description: 27 | - All Mikrotik compatible parameters for the ppp secrets endpoint. 28 | Any yes/no values must be enclosed in double quotes 29 | state: 30 | description: 31 | - absent or present 32 | ''' 33 | 34 | EXAMPLES = ''' 35 | - mt_ppp_secret: 36 | hostname: "{{ inventory_hostname }}" 37 | username: "{{ mt_user }}" 38 | password: "{{ mt_pass }}" 39 | state: present 40 | settings: 41 | name: user2 42 | password: pass2 43 | service: ovpn 44 | remote-address: 1.2.3.4 45 | ''' 46 | 47 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 48 | from ansible.module_utils.basic import AnsibleModule 49 | 50 | 51 | 52 | def main(): 53 | module = AnsibleModule( 54 | argument_spec = dict( 55 | hostname = dict(required=True), 56 | username = dict(required=True), 57 | password = dict(required=True, no_log=True), 58 | settings = dict(required=False, type='dict'), 59 | state = dict( 60 | required = False, 61 | choices = ['present', 'absent'], 62 | type = 'str' 63 | ), 64 | ), 65 | supports_check_mode=True 66 | ) 67 | 68 | params = module.params 69 | mt_obj = MikrotikIdempotent( 70 | hostname = params['hostname'], 71 | username = params['username'], 72 | password = params['password'], 73 | state = params['state'], 74 | desired_params = params['settings'], 75 | idempotent_param = 'name', 76 | api_path = '/ppp/secret', 77 | check_mode = module.check_mode 78 | ) 79 | 80 | mt_obj.sync_state() 81 | 82 | if mt_obj.failed: 83 | module.fail_json( 84 | msg = mt_obj.failed_msg 85 | ) 86 | elif mt_obj.changed: 87 | module.exit_json( 88 | failed=False, 89 | changed=True, 90 | msg=mt_obj.changed_msg, 91 | diff={ "prepared": { 92 | "old": mt_obj.old_params, 93 | "new": mt_obj.new_params, 94 | }}, 95 | ) 96 | else: 97 | module.exit_json( 98 | failed=False, 99 | changed=False, 100 | msg=params['settings'], 101 | ) 102 | 103 | if __name__ == '__main__': 104 | main() 105 | -------------------------------------------------------------------------------- /library/mt_ppp_server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_ppp_server 4 | author: 5 | - "Colin Zwiebel" 6 | version_added: "2.3.1" 7 | short_description: Manage mikrotik ppp servers 8 | requirements: 9 | - mt_api 10 | description: 11 | - Manage ppp servers and their settings. 12 | options: 13 | hostname: 14 | description: 15 | - hostname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | server_type: 26 | description: 27 | - VPN server type to manage 28 | required: True 29 | options: 30 | - l2tp 31 | - ovpn 32 | - pptp 33 | - sstp 34 | settings: 35 | description: 36 | - All Mikrotik compatible parameters for this type of vpn server. 37 | Any yes/no values must be enclosed in double quotes 38 | ''' 39 | 40 | EXAMPLES = ''' 41 | - mt_ppp_server: 42 | hostname: "{{ inventory_hostname }}" 43 | username: "{{ mt_user }}" 44 | password: "{{ mt_pass }}" 45 | server_type: pptp 46 | settings: 47 | disabled: no 48 | max-mtu: 1420 49 | authentication: mschap2 50 | ''' 51 | 52 | from ansible.module_utils.basic import AnsibleModule 53 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 54 | 55 | 56 | def main(): 57 | module = AnsibleModule( 58 | argument_spec = dict( 59 | hostname = dict(required=True), 60 | username = dict(required=True), 61 | password = dict(required=True, no_log=True), 62 | settings = dict(required=False, type='dict'), 63 | server_type = dict( 64 | required = True, 65 | choices = ['l2tp', 'ovpn', 'pptp', 'sstp'], 66 | type = 'str' 67 | ), 68 | ), 69 | supports_check_mode=True 70 | ) 71 | 72 | params = module.params 73 | mt_obj = MikrotikIdempotent( 74 | hostname = params['hostname'], 75 | username = params['username'], 76 | password = params['password'], 77 | state = None, 78 | desired_params = params['settings'], 79 | idempotent_param = None, 80 | api_path = '/interface/{}-server/server'.format(params['server_type']), 81 | check_mode = module.check_mode 82 | ) 83 | 84 | mt_obj.sync_state() 85 | 86 | if mt_obj.failed: 87 | module.fail_json( 88 | msg = mt_obj.failed_msg 89 | ) 90 | elif mt_obj.changed: 91 | module.exit_json( 92 | failed=False, 93 | changed=True, 94 | msg=mt_obj.changed_msg, 95 | diff={ "prepared": { 96 | "old": mt_obj.old_params, 97 | "new": mt_obj.new_params, 98 | }}, 99 | ) 100 | else: 101 | module.exit_json( 102 | failed=False, 103 | changed=False, 104 | msg=params['settings'], 105 | ) 106 | if __name__ == '__main__': 107 | main() 108 | -------------------------------------------------------------------------------- /library/mt_radius.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_radius 4 | author: 5 | - "Valentin Gurmeza" 6 | - "Shaun Smiley" 7 | version_added: "2.3" 8 | short_description: Manage mikrotik radius client 9 | requirements: 10 | - mt_api 11 | description: 12 | - Add or remove a radius client 13 | options: 14 | hostname: 15 | description: 16 | - hotstname of mikrotik router 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | password: 21 | description: 22 | - password used for authentication to mikrotik router 23 | state: 24 | description: 25 | - client present or absent 26 | required: False 27 | choices: 28 | - present 29 | - absent 30 | ''' 31 | 32 | EXAMPLES = ''' 33 | # Add a new radius entry 34 | - mt_radius: 35 | hostname: "{{ inventory_hostname }}" 36 | username: "{{ mt_user }}" 37 | password: "{{ mt_pass }}" 38 | state: present 39 | parameter: radius 40 | settings: 41 | address: 192.168.230.1 42 | comment: ansible_test 43 | timeout: '2s500ms' 44 | secret: 'password' 45 | service: 46 | - login 47 | - hotspot 48 | - wireless 49 | ''' 50 | 51 | from ansible.module_utils.basic import AnsibleModule 52 | from ansible.module_utils.mt_common import MikrotikIdempotent 53 | 54 | 55 | def main(): 56 | module = AnsibleModule( 57 | argument_spec = dict( 58 | hostname = dict(required=True), 59 | username = dict(required=True), 60 | password = dict(required=True, no_log=True), 61 | settings = dict(required=False, type='dict'), 62 | parameter = dict( 63 | required = True, 64 | choices = ['radius', 'incoming'], 65 | type = 'str' 66 | ), 67 | state = dict( 68 | required = False, 69 | choices = ['present', 'absent'], 70 | type = 'str' 71 | ), 72 | ), 73 | supports_check_mode=True 74 | ) 75 | 76 | idempotent_parameter = None 77 | params = module.params 78 | 79 | if params['parameter'] == 'radius': 80 | idempotent_parameter = 'comment' 81 | params['parameter'] = "/radius" 82 | 83 | if params['parameter'] == 'incoming': 84 | params['parameter'] = "/radius/incoming" 85 | 86 | 87 | mt_obj = MikrotikIdempotent( 88 | hostname = params['hostname'], 89 | username = params['username'], 90 | password = params['password'], 91 | state = params['state'], 92 | desired_params = params['settings'], 93 | idempotent_param = idempotent_parameter, 94 | api_path = str(params['parameter']), 95 | check_mode = module.check_mode, 96 | 97 | ) 98 | 99 | mt_obj.sync_state() 100 | 101 | if mt_obj.failed: 102 | module.fail_json( 103 | msg = mt_obj.failed_msg 104 | ) 105 | elif mt_obj.changed: 106 | module.exit_json( 107 | failed=False, 108 | changed=True, 109 | msg=mt_obj.changed_msg, 110 | diff={ "prepared": { 111 | "old": mt_obj.old_params, 112 | "new": mt_obj.new_params, 113 | }}, 114 | ) 115 | else: 116 | module.exit_json( 117 | failed=False, 118 | changed=False, 119 | #msg='', 120 | msg=params['settings'], 121 | ) 122 | 123 | if __name__ == '__main__': 124 | main() 125 | -------------------------------------------------------------------------------- /library/mt_radius_backup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_radius 4 | author: 5 | - "Valentin Gurmeza" 6 | - "Shaun Smiley" 7 | version_added: "2.3" 8 | short_description: Manage mikrotik radius client 9 | requirements: 10 | - mt_api 11 | description: 12 | - Add or remove a radius client 13 | options: 14 | hostname: 15 | description: 16 | - hotstname of mikrotik router 17 | required: True 18 | username: 19 | description: 20 | - username used to connect to mikrotik router 21 | required: True 22 | password: 23 | description: 24 | - password used for authentication to mikrotik router 25 | required: True 26 | state: 27 | description: 28 | - client present or absent 29 | required: True 30 | choices: 31 | - present 32 | - absent 33 | comment: 34 | description: 35 | - This module only ensures entries that match the comment field. 36 | Thus, you should make unique comments for every entry. 37 | required: True # only if state is present 38 | address: 39 | description: 40 | - IPv4 or IPv6 address of RADIUS server 41 | required: False 42 | secret: 43 | description: 44 | - Shared secret used to access the RADIUS server 45 | required: False 46 | default: null 47 | timeout: 48 | description: 49 | - Timeout after which the request should be resend 50 | required: False 51 | default: null 52 | service: 53 | description: 54 | - Router services that will use this RADIUS server: 55 | choices: 56 | - 'hotspot' # HotSpot authentication service 57 | - 'login' # router's local user authentication 58 | - 'ppp # Point-to-Point clients authentication 59 | - 'wireless # wireless client authentication (client's MAC address is sent as User-Name) 60 | - 'dhcp # DHCP protocol client authentication (client's MAC address is sent as User-Name)IPv4 or IPv6 address of RADIUS server 61 | required: False 62 | default: null 63 | incoming: 64 | accept: 65 | choices: ['true', 'false' ] 66 | port: "3799" 67 | description: 68 | - Whether to accept the unsolicited messages. 69 | Also include the port number to listen for the requests on. 70 | Accept and port values must be strings 71 | required: False 72 | default: null 73 | ''' 74 | 75 | EXAMPLES = ''' 76 | # Add a new radius entry 77 | - mt_radius: 78 | hostname: "{{ inventory_hostname }}" 79 | username: "{{ mt_user }}" 80 | password: "{{ mt_pass }}" 81 | state: present 82 | address: 192.168.230.1 83 | comment: ansible_test 84 | secret: 'password' 85 | service: 86 | - login 87 | - hotspot 88 | - wireless 89 | timeout: '2s500ms' 90 | ''' 91 | 92 | from ansible.module_utils import mt_api 93 | from ansible.module_utils.basic import AnsibleModule 94 | 95 | 96 | def main(): 97 | module = AnsibleModule( 98 | argument_spec=dict( 99 | hostname= dict(required=True), 100 | username= dict(required=True), 101 | password= dict(required=True, no_log=True), 102 | address = dict(required=False, type='str'), 103 | comment = dict(required=True, type='str'), 104 | secret = dict(required=False, type='str'), 105 | service = dict(required=False, type='list'), 106 | timeout = dict(required=False, type='str'), 107 | incoming= dict(required=False, type='dict'), 108 | state = dict( 109 | required = True, 110 | choices = ['present', 'absent'], 111 | type = 'str' 112 | ), 113 | ), 114 | supports_check_mode=True 115 | ) 116 | 117 | hostname = module.params['hostname'] 118 | username = module.params['username'] 119 | password = module.params['password'] 120 | state = module.params['state'] 121 | check_mode = module.check_mode 122 | changed = False 123 | msg = "" 124 | 125 | radius_path = '/radius' 126 | mk = mt_api.Mikrotik(hostname, username, password) 127 | try: 128 | mk.login() 129 | except: 130 | module.fail_json( 131 | msg="Could not log into Mikrotik device." + 132 | " Check the username and password.", 133 | ) 134 | 135 | response = mk.api_print(radius_path) 136 | radius_params = module.params 137 | 138 | ######################################################## 139 | # Check if we need to edit the incoming radius settings 140 | ######################################################## 141 | if radius_params['incoming'] is not None: 142 | incoming_path = '/radius/incoming' 143 | incoming_response = mk.api_print(incoming_path) 144 | incoming = radius_params['incoming'] 145 | if incoming_response[0][1]['accept'] == incoming['accept']: 146 | if incoming_response[0][1]['port'] == incoming['port']: 147 | # nothing to do 148 | pass 149 | else: 150 | # edit port 151 | if not check_mode: 152 | mk.api_edit(base_path=incoming_path, params=incoming) 153 | else: 154 | # edit the accept and the port 155 | if not check_mode: 156 | mk.api_edit(base_path=incoming_path, params=incoming) 157 | ####################################### 158 | # Since we are grabbing all the parameters passed by the module 159 | # We need to remove the one that won't be used 160 | # as mikrotik parameters 161 | remove_params = ['hostname', 'username', 'password', 'state', 'incoming'] 162 | for i in remove_params: 163 | radius_params.pop(i) 164 | ####################################### 165 | # remove keys with empty values 166 | # convert service list to stings 167 | ###################################### 168 | for key in radius_params.keys(): 169 | if radius_params[key] is None: 170 | radius_params.pop(key) 171 | 172 | 173 | ################################################# 174 | # Convert service list to comma separated string 175 | ################################################# 176 | list_to_string = "" 177 | if 'service' in radius_params: 178 | list_to_string = ','.join(map(str, radius_params['service'])) 179 | radius_params['service'] = list_to_string 180 | 181 | ################################################ 182 | # mikrotik_radius is the dictionary with the parameters 183 | # we get from mikrotik 184 | ################################# 185 | # We grab the first radius item to 186 | # match the comment 187 | ################################# 188 | mikrotik_radius = {} 189 | for i in response: 190 | if 'comment' in i[1]: 191 | if i[1]['comment'] == radius_params['comment']: 192 | mikrotik_radius = i[1] 193 | break 194 | 195 | ########################################################## 196 | # Define radius_id to be used by remove and edit function 197 | ########################################################## 198 | if '.id' in mikrotik_radius: 199 | radius_id = mikrotik_radius['.id'] 200 | else: 201 | radius_id = False 202 | 203 | ###################################################### 204 | # If the state is present and we can't find matching 205 | # radius comment we add a new item with all the parameters 206 | # from Ansible 207 | ####################################################### 208 | if state == "present": 209 | if mikrotik_radius == {}: 210 | if not check_mode: 211 | mk.api_add(base_path=radius_path, params=radius_params) 212 | module.exit_json( 213 | failed=False, 214 | changed=True, 215 | msg="Added radius item", 216 | ) 217 | ################################################### 218 | # If an item exists we check if all the parameters 219 | # match what we have in ansible 220 | ###################################### 221 | else: 222 | radius_diff_keys = {} 223 | for key in radius_params: 224 | if radius_params[key] != mikrotik_radius[key]: 225 | radius_diff_keys[key] = radius_params[key] 226 | if radius_diff_keys != {}: 227 | radius_diff_keys['numbers'] = radius_id 228 | if not check_mode: 229 | mk.api_edit(base_path=radius_path, params=radius_diff_keys) 230 | module.exit_json( 231 | failed=False, 232 | changed=True, 233 | msg="Changed radius item: " + radius_params['comment'] 234 | ) 235 | else: 236 | #################### 237 | # Already up date 238 | module.exit_json( 239 | failed=False, 240 | changed=False, 241 | ) 242 | elif state == "absent": 243 | if radius_id: 244 | if not check_mode: 245 | mk.api_remove(base_path=radius_path, remove_id=radius_id) 246 | module.exit_json( 247 | failed=False, 248 | changed=True, 249 | msg=radius_params['comment'] + " removed" 250 | ) 251 | ##################################################### 252 | # if radius_id is not set there is nothing to remove 253 | ##################################################### 254 | else: 255 | module.exit_json( 256 | failed=False, 257 | changed=False, 258 | ) 259 | else: 260 | module.exit_json( 261 | failed=True, 262 | changed=False, 263 | ) 264 | if __name__ == '__main__': 265 | main() 266 | -------------------------------------------------------------------------------- /library/mt_snmp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_snmp 4 | author: 5 | - "Valentin Gurmeza" 6 | version_added: "2.4" 7 | short_description: Manage mikrotik snmp endpoints 8 | requirements: 9 | - mt_api 10 | description: 11 | - Generic mikrotik snmp module. 12 | options: 13 | hostname: 14 | description: 15 | - hotstname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | parameter: 26 | description: 27 | - sub endpoint for mikrotik snmp 28 | required: True 29 | options: 30 | - community 31 | - snmp 32 | settings: 33 | description: 34 | - All Mikrotik compatible parameters for this particular endpoint. 35 | Any yes/no values must be enclosed in double quotes 36 | state: 37 | description: 38 | - absent or present 39 | ''' 40 | 41 | EXAMPLES = ''' 42 | - mt_snmp: 43 | hostname: "{{ inventory_hostname }}" 44 | username: "{{ mt_user }}" 45 | password: "{{ mt_pass }}" 46 | parameter: community 47 | settings: 48 | addresses: "192.168.1.0/24" 49 | name: ansible_managed 50 | ''' 51 | 52 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 53 | from ansible.module_utils.basic import AnsibleModule 54 | 55 | 56 | 57 | def main(): 58 | module = AnsibleModule( 59 | argument_spec = dict( 60 | hostname = dict(required=True), 61 | username = dict(required=True), 62 | password = dict(required=True, no_log=True), 63 | settings = dict(required=False, type='dict'), 64 | parameter = dict( 65 | required = True, 66 | choices = ['community', 'snmp'], 67 | type = 'str' 68 | ), 69 | state = dict( 70 | required = False, 71 | choices = ['present', 'absent'], 72 | type = 'str' 73 | ), 74 | ), 75 | supports_check_mode=True 76 | ) 77 | 78 | idempotent_parameter = None 79 | params = module.params 80 | 81 | 82 | if params['parameter'] == 'community': 83 | idempotent_parameter = 'name' 84 | params['parameter'] = "snmp/community" 85 | 86 | mt_obj = MikrotikIdempotent( 87 | hostname = params['hostname'], 88 | username = params['username'], 89 | password = params['password'], 90 | state = params['state'], 91 | desired_params = params['settings'], 92 | idempotent_param = idempotent_parameter, 93 | api_path = '/' + str(params['parameter']), 94 | check_mode = module.check_mode 95 | ) 96 | 97 | mt_obj.sync_state() 98 | 99 | if mt_obj.failed: 100 | module.fail_json( 101 | msg = mt_obj.failed_msg 102 | ) 103 | elif mt_obj.changed: 104 | module.exit_json( 105 | failed=False, 106 | changed=True, 107 | msg=mt_obj.changed_msg, 108 | diff={ "prepared": { 109 | "old": mt_obj.old_params, 110 | "new": mt_obj.new_params, 111 | }}, 112 | ) 113 | else: 114 | module.exit_json( 115 | failed=False, 116 | changed=False, 117 | msg=params['settings'], 118 | ) 119 | 120 | if __name__ == '__main__': 121 | main() 122 | -------------------------------------------------------------------------------- /library/mt_system.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_system.py 4 | author: 5 | - "Valentin Gurmeza" 6 | version_added: "2.4" 7 | short_description: Manage mikrotik system endpoints 8 | requirements: 9 | - mt_api 10 | description: 11 | - manage mikrotik system parameters 12 | options: 13 | hostname: 14 | description: 15 | - hotstname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | parameter: 26 | description: 27 | - sub enpoint for mikrotik system 28 | required: True 29 | options: 30 | - ntp_client 31 | - clock 32 | - logging 33 | - routerboard 34 | - identity 35 | settings: 36 | description: 37 | - All Mikrotik compatible parameters for this particular endpoint. 38 | Any yes/no values must be enclosed in double quotes 39 | state: 40 | description: 41 | - absent or present 42 | ''' 43 | 44 | EXAMPLES = ''' 45 | - mt_system: 46 | hostname: "{{ inventory_hostname }}" 47 | username: "{{ mt_user }}" 48 | password: "{{ mt_pass }}" 49 | parameter: identity 50 | settings: 51 | name: test_ansible 52 | ''' 53 | 54 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 55 | from ansible.module_utils.basic import AnsibleModule 56 | 57 | 58 | 59 | def main(): 60 | module = AnsibleModule( 61 | argument_spec = dict( 62 | hostname = dict(required=True), 63 | username = dict(required=True), 64 | password = dict(required=True, no_log=True), 65 | settings = dict(required=False, type='dict'), 66 | parameter = dict( 67 | required = True, 68 | choices = ['ntp_client', 'clock', 'identity', 'logging', 'routerboard_settings'], 69 | type = 'str' 70 | ), 71 | state = dict( 72 | required = False, 73 | choices = ['present', 'absent'], 74 | type = 'str' 75 | ), 76 | ), 77 | supports_check_mode=True 78 | ) 79 | 80 | params = module.params 81 | 82 | if params['parameter'] == 'routerboard_settings': 83 | params['parameter'] = 'routerboard/settings' 84 | 85 | if params['parameter'] == 'ntp_client': 86 | params['parameter'] = 'ntp/client' 87 | 88 | clean_params(params['settings']) 89 | mt_obj = MikrotikIdempotent( 90 | hostname = params['hostname'], 91 | username = params['username'], 92 | password = params['password'], 93 | state = params['state'], 94 | desired_params = params['settings'], 95 | idempotent_param= None, 96 | api_path = '/system/' + params['parameter'], 97 | check_mode = module.check_mode 98 | ) 99 | 100 | mt_obj.sync_state() 101 | 102 | if mt_obj.failed: 103 | module.fail_json( 104 | msg = mt_obj.failed_msg 105 | ) 106 | elif mt_obj.changed: 107 | module.exit_json( 108 | failed=False, 109 | changed=True, 110 | msg=mt_obj.changed_msg, 111 | diff={ "prepared": { 112 | "old": mt_obj.old_params, 113 | "new": mt_obj.new_params, 114 | }}, 115 | ) 116 | else: 117 | module.exit_json( 118 | failed=False, 119 | changed=False, 120 | #msg='', 121 | msg=params['settings'], 122 | ) 123 | 124 | if __name__ == '__main__': 125 | main() 126 | -------------------------------------------------------------------------------- /library/mt_system_scheduler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_system_scheduler 4 | author: 5 | - "Valentin Gurmeza" 6 | version_added: "2.3" 7 | short_description: Manage mikrotik system scheduler 8 | requirements: 9 | - mt_api 10 | description: 11 | - add, remove, or modify a system scheduler task 12 | options: 13 | hostname: 14 | description: 15 | - hotstname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | parameter: 26 | description: 27 | - sub endpoint for mikrotik system 28 | required: True 29 | options: 30 | - scheduler 31 | settings: 32 | description: 33 | - All Mikrotik compatible parameters for this particular endpoint. 34 | Any yes/no values must be enclosed in double quotes 35 | required: True 36 | state: 37 | description: 38 | - absent or present 39 | required: True 40 | ''' 41 | 42 | EXAMPLES = ''' 43 | - mt_system_scheduler: 44 | hostname: "{{ inventory_hostname }}" 45 | username: "{{ mt_user }}" 46 | password: "{{ mt_pass }}" 47 | state: present 48 | parameter: scheduler 49 | name: test_by_ansible 50 | comment: ansible_test 51 | on_event: put "hello" 52 | ''' 53 | 54 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 55 | from ansible.module_utils.basic import AnsibleModule 56 | 57 | 58 | def main(): 59 | module = AnsibleModule( 60 | argument_spec=dict( 61 | hostname=dict(required=True), 62 | username=dict(required=True), 63 | password=dict(required=True, no_log=True), 64 | settings=dict(required=True, type='dict'), 65 | parameter = dict( 66 | required = True, 67 | choices = ['scheduler'], 68 | type = 'str' 69 | ), 70 | state = dict( 71 | required = True, 72 | choices = ['present', 'absent'], 73 | type = 'str' 74 | ) 75 | ), 76 | supports_check_mode=True 77 | ) 78 | 79 | params = module.params 80 | idempotent_parameter = 'name' 81 | 82 | mt_obj = MikrotikIdempotent( 83 | hostname = params['hostname'], 84 | username = params['username'], 85 | password = params['password'], 86 | state = params['state'], 87 | desired_params = params['settings'], 88 | idempotent_param = idempotent_parameter, 89 | api_path = '/system/' + str(params['parameter']), 90 | check_mode = module.check_mode 91 | ) 92 | 93 | # exit if login failed 94 | if not mt_obj.login_success: 95 | module.fail_json( 96 | msg = mt_obj.failed_msg 97 | ) 98 | 99 | # add, remove or edit things 100 | mt_obj.sync_state() 101 | 102 | if mt_obj.failed: 103 | module.fail_json( 104 | msg = mt_obj.failed_msg 105 | ) 106 | elif mt_obj.changed: 107 | module.exit_json( 108 | failed=False, 109 | changed=True, 110 | msg=mt_obj.changed_msg, 111 | diff={ "prepared": { 112 | "old": mt_obj.old_params, 113 | "new": mt_obj.new_params, 114 | }}, 115 | ) 116 | else: 117 | module.exit_json( 118 | failed=False, 119 | changed=False, 120 | 121 | msg=params['settings'], 122 | ) 123 | 124 | if __name__ == '__main__': 125 | main() 126 | -------------------------------------------------------------------------------- /library/mt_tool.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_tool 4 | author: 5 | - "Valentin Gurmeza" 6 | version_added: "2.4" 7 | short_description: Manage mikrotik tool endpoints 8 | requirements: 9 | - mt_api 10 | description: 11 | - Generic mikrotik tool module. 12 | options: 13 | hostname: 14 | description: 15 | - hotstname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | parameter: 26 | description: 27 | - sub endpoint for mikrotik tool 28 | required: True 29 | options: 30 | - netwatch 31 | - e-mail 32 | settings: 33 | description: 34 | - All Mikrotik compatible parameters for this particular endpoint. 35 | Any yes/no values must be enclosed in double quotes 36 | state: 37 | description: 38 | - absent or present 39 | ''' 40 | 41 | EXAMPLES = ''' 42 | - mt_tool: 43 | hostname: "{{ inventory_hostname }}" 44 | username: "{{ mt_user }}" 45 | password: "{{ mt_pass }}" 46 | parameter: e-mail 47 | settings: 48 | address: 192.168.1.1 49 | from: foo@bar.com 50 | ''' 51 | 52 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 53 | from ansible.module_utils.basic import AnsibleModule 54 | 55 | 56 | def main(): 57 | module = AnsibleModule( 58 | argument_spec = dict( 59 | hostname = dict(required=True), 60 | username = dict(required=True), 61 | password = dict(required=True, no_log=True), 62 | settings = dict(required=False, type='dict'), 63 | parameter = dict( 64 | required = True, 65 | choices = ['e-mail', 'netwatch'], 66 | type = 'str' 67 | ), 68 | state = dict( 69 | required = False, 70 | choices = ['present', 'absent'], 71 | type = 'str' 72 | ), 73 | ), 74 | supports_check_mode=True 75 | ) 76 | 77 | idempotent_parameter = None 78 | params = module.params 79 | 80 | if params['parameter'] == 'netwatch': 81 | idempotent_parameter = 'host' 82 | 83 | # clean_params(params['settings']) 84 | 85 | mt_obj = MikrotikIdempotent( 86 | hostname = params['hostname'], 87 | username = params['username'], 88 | password = params['password'], 89 | state = params['state'], 90 | desired_params = params['settings'], 91 | idempotent_param = idempotent_parameter, 92 | api_path = '/tool/' + str(params['parameter']), 93 | check_mode = module.check_mode 94 | 95 | ) 96 | 97 | mt_obj.sync_state() 98 | 99 | if mt_obj.failed: 100 | module.fail_json( 101 | msg=mt_obj.failed_msg 102 | ) 103 | elif mt_obj.changed: 104 | module.exit_json( 105 | failed=False, 106 | changed=True, 107 | msg=mt_obj.changed_msg, 108 | diff={"prepared": { 109 | "old": mt_obj.old_params, 110 | "new": mt_obj.new_params, 111 | }}, 112 | ) 113 | else: 114 | module.exit_json( 115 | failed=False, 116 | changed=False, 117 | msg=params['settings'], 118 | ) 119 | 120 | if __name__ == '__main__': 121 | main() 122 | -------------------------------------------------------------------------------- /library/mt_user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | DOCUMENTATION = ''' 3 | module: mt_user 4 | author: 5 | - "Valentin Gurmeza" 6 | version_added: "2.4" 7 | short_description: Manage mikrotik user endpoints 8 | requirements: 9 | - mt_api 10 | description: 11 | - Generic mikrotik user module. 12 | options: 13 | hostname: 14 | description: 15 | - hotstname of mikrotik router 16 | required: True 17 | username: 18 | description: 19 | - username used to connect to mikrotik router 20 | required: True 21 | password: 22 | description: 23 | - password used for authentication to mikrotik router 24 | required: True 25 | parameter: 26 | description: 27 | - sub endpoint for mikrotik user 28 | required: True 29 | options: 30 | - user 31 | - active 32 | - aaa 33 | settings: 34 | description: 35 | - All Mikrotik compatible parameters for this particular endpoint. 36 | Any yes/no values must be enclosed in double quotes 37 | state: 38 | description: 39 | - absent or present 40 | ''' 41 | 42 | EXAMPLES = ''' 43 | - mt_user: 44 | hostname: "{{ inventory_hostname }}" 45 | username: "{{ mt_user }}" 46 | password: "{{ mt_pass }}" 47 | parameter: user 48 | settings: 49 | name: test1 50 | group: read 51 | ''' 52 | 53 | from ansible.module_utils.mt_common import clean_params, MikrotikIdempotent 54 | from ansible.module_utils.basic import AnsibleModule 55 | 56 | 57 | 58 | def main(): 59 | module = AnsibleModule( 60 | argument_spec = dict( 61 | hostname = dict(required=True), 62 | username = dict(required=True), 63 | password = dict(required=True, no_log=True), 64 | settings = dict(required=False, type='dict'), 65 | parameter = dict( 66 | required = True, 67 | choices = ['user', 'active', 'aaa'], 68 | type = 'str' 69 | ), 70 | state = dict( 71 | required = False, 72 | choices = ['present', 'absent'], 73 | type = 'str' 74 | ), 75 | ), 76 | supports_check_mode=True 77 | ) 78 | 79 | idempotent_parameter = None 80 | params = module.params 81 | idempotent_parameter = 'name' 82 | 83 | if params['parameter'] == 'group': 84 | params['parameter'] = 'user/group' 85 | mt_obj = MikrotikIdempotent( 86 | hostname = params['hostname'], 87 | username = params['username'], 88 | password = params['password'], 89 | state = params['state'], 90 | desired_params = params['settings'], 91 | idempotent_param = idempotent_parameter, 92 | api_path = '/' + str(params['parameter']), 93 | check_mode = module.check_mode 94 | ) 95 | 96 | mt_obj.sync_state() 97 | 98 | if mt_obj.failed: 99 | module.fail_json( 100 | msg = mt_obj.failed_msg 101 | ) 102 | elif mt_obj.changed: 103 | module.exit_json( 104 | failed=False, 105 | changed=True, 106 | msg=mt_obj.changed_msg, 107 | diff={ "prepared": { 108 | "old": mt_obj.old_params, 109 | "new": mt_obj.new_params, 110 | }}, 111 | ) 112 | else: 113 | module.exit_json( 114 | failed=False, 115 | changed=False, 116 | #msg='', 117 | msg=params['settings'], 118 | ) 119 | 120 | if __name__ == '__main__': 121 | main() 122 | -------------------------------------------------------------------------------- /pythonlibs/mt_api/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import binascii 4 | import hashlib 5 | import logging 6 | import socket 7 | import ssl 8 | import sys 9 | 10 | from ansible.module_utils.mt_api.retryloop import RetryError 11 | from ansible.module_utils.mt_api.retryloop import retryloop 12 | from ansible.module_utils.mt_api.socket_utils import set_keepalive 13 | 14 | PY2 = sys.version_info[0] < 3 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | class RosAPIError(Exception): 19 | def __init__(self, value): 20 | self.value = value 21 | 22 | def __str__(self): 23 | if isinstance(self.value, dict) and self.value.get('message'): 24 | return self.value['message'] 25 | elif isinstance(self.value, list): 26 | elements = ( 27 | '%s: %s' % 28 | (element.__class__, str(element)) for element in self.value 29 | ) 30 | return '[%s]' % (', '.join(element for element in elements)) 31 | else: 32 | return str(self.value) 33 | 34 | 35 | class RosAPIConnectionError(RosAPIError): 36 | pass 37 | 38 | 39 | class RosAPIFatalError(RosAPIError): 40 | pass 41 | 42 | 43 | class RosApiLengthUtils(object): 44 | def __init__(self, api): 45 | self.api = api 46 | 47 | def write_lenght(self, length): 48 | self.api.write_bytes(self.length_to_bytes(length)) 49 | 50 | def length_to_bytes(self, length): 51 | if length < 0x80: 52 | return self.to_bytes(length) 53 | elif length < 0x4000: 54 | length |= 0x8000 55 | return self.to_bytes(length, 2) 56 | elif length < 0x200000: 57 | length |= 0xC00000 58 | return self.to_bytes(length, 3) 59 | elif length < 0x10000000: 60 | length |= 0xE0000000 61 | return self.to_bytes(length, 4) 62 | else: 63 | return self.to_bytes(0xF0) + self.to_bytes(length, 4) 64 | 65 | def read_length(self): 66 | b = self.api.read_bytes(1) 67 | i = self.from_bytes(b) 68 | if (i & 0x80) == 0x00: 69 | return i 70 | elif (i & 0xC0) == 0x80: 71 | return self._unpack(1, i & ~0xC0) 72 | elif (i & 0xE0) == 0xC0: 73 | return self._unpack(2, i & ~0xE0) 74 | elif (i & 0xF0) == 0xE0: 75 | return self._unpack(3, i & ~0xF0) 76 | elif (i & 0xF8) == 0xF0: 77 | return self.from_bytes(self.api.read_bytes(1)) 78 | else: 79 | raise RosAPIFatalError('Unknown value: %x' % i) 80 | 81 | def _unpack(self, times, i): 82 | temp1 = self.to_bytes(i) 83 | temp2 = self.api.read_bytes(times) 84 | try: 85 | temp3 = temp2.decode('utf-8') 86 | except: 87 | try: 88 | temp3 = temp2.decode('windows-1252') 89 | except Exception: 90 | print("Cannot decode response properly:", temp2) 91 | print(Exception) 92 | exit(1) 93 | 94 | res = temp1 + temp3 95 | return self.from_bytes(res) 96 | 97 | if PY2: 98 | def from_bytes(self, data): 99 | data_values = [ord(char) for char in data] 100 | value = 0 101 | for byte_value in data_values: 102 | value <<= 8 103 | value += byte_value 104 | return value 105 | 106 | def to_bytes(self, i, size=1): 107 | data = [] 108 | for _ in xrange(size): 109 | data.append(chr(i & 0xff)) 110 | i >>= 8 111 | return b''.join(reversed(data)) 112 | else: 113 | def from_bytes(self, data): 114 | return int.from_bytes(data, 'big') 115 | 116 | def to_bytes(self, i, size=1): 117 | return i.to_bytes(size, 'big') 118 | 119 | 120 | class RosAPI(object): 121 | """Routeros api""" 122 | 123 | def __init__(self, socket): 124 | self.socket = socket 125 | self.length_utils = RosApiLengthUtils(self) 126 | 127 | def login(self, username, pwd): 128 | for _, attrs in self.talk([b'/login']): 129 | token = binascii.unhexlify(attrs[b'ret']) 130 | hasher = hashlib.md5() 131 | hasher.update(b'\x00') 132 | hasher.update(pwd) 133 | hasher.update(token) 134 | self.talk([b'/login', b'=name=' + username, 135 | b'=response=00' + hasher.hexdigest().encode('ascii')]) 136 | 137 | def talk(self, words): 138 | if self.write_sentence(words) == 0: 139 | return 140 | output = [] 141 | while True: 142 | input_sentence = self.read_sentence() 143 | if not len(input_sentence): 144 | continue 145 | attrs = {} 146 | reply = input_sentence.pop(0) 147 | for line in input_sentence: 148 | try: 149 | second_eq_pos = line.index(b'=', 1) 150 | except IndexError: 151 | attrs[line[1:]] = b'' 152 | else: 153 | attrs[line[1:second_eq_pos]] = line[second_eq_pos + 1:] 154 | output.append((reply, attrs)) 155 | if reply == b'!done': 156 | if output[0][0] == b'!trap': 157 | raise RosAPIError(output[0][1]) 158 | if output[0][0] == b'!fatal': 159 | self.socket.close() 160 | raise RosAPIFatalError(output[0][1]) 161 | return output 162 | 163 | def write_sentence(self, words): 164 | words_written = 0 165 | for word in words: 166 | self.write_word(word) 167 | words_written += 1 168 | self.write_word(b'') 169 | return words_written 170 | 171 | def read_sentence(self): 172 | sentence = [] 173 | while True: 174 | word = self.read_word() 175 | if not len(word): 176 | return sentence 177 | sentence.append(word) 178 | 179 | def write_word(self, word): 180 | logger.debug('>>> %s' % word) 181 | self.length_utils.write_lenght(len(word)) 182 | self.write_bytes(word) 183 | 184 | def read_word(self): 185 | word = self.read_bytes(self.length_utils.read_length()) 186 | logger.debug('<<< %s' % word) 187 | return word 188 | 189 | def write_bytes(self, data): 190 | sent_overal = 0 191 | while sent_overal < len(data): 192 | try: 193 | sent = self.socket.send(data[sent_overal:]) 194 | except socket.error as e: 195 | raise RosAPIConnectionError(str(e)) 196 | if sent == 0: 197 | raise RosAPIConnectionError('Connection closed by remote end.') 198 | sent_overal += sent 199 | 200 | def read_bytes(self, length): 201 | received_overal = b'' 202 | while len(received_overal) < length: 203 | try: 204 | received = self.socket.recv( 205 | length - len(received_overal)) 206 | except socket.error as e: 207 | raise RosAPIConnectionError(str(e)) 208 | if len(received) == 0: 209 | raise RosAPIConnectionError('Connection closed by remote end.') 210 | received_overal += received 211 | return received_overal 212 | 213 | 214 | 215 | 216 | class BaseRouterboardResource(object): 217 | def __init__(self, api, namespace): 218 | self.api = api 219 | self.namespace = namespace 220 | 221 | def call(self, command, set_kwargs, query_kwargs=None): 222 | query_kwargs = query_kwargs or {} 223 | query_arguments = self._prepare_arguments(True, **query_kwargs) 224 | set_arguments = self._prepare_arguments(False, **set_kwargs) 225 | query = ([('%s/%s' % (self.namespace, command)).encode('ascii')] + 226 | query_arguments + set_arguments) 227 | response = self.api.api_client.talk(query) 228 | 229 | output = [] 230 | for response_type, attributes in response: 231 | if response_type == b'!re': 232 | output.append(self._remove_first_char_from_keys(attributes)) 233 | 234 | return output 235 | 236 | @staticmethod 237 | def _prepare_arguments(is_query, **kwargs): 238 | command_arguments = [] 239 | for key, value in kwargs.items(): 240 | if key in ['id', 'proplist']: 241 | key = '.%s' % key 242 | key = key.replace('_', '-') 243 | selector_char = '?' if is_query else '=' 244 | command_arguments.append( 245 | ('%s%s=' % (selector_char, key)).encode('ascii') + value) 246 | 247 | return command_arguments 248 | 249 | @staticmethod 250 | def _remove_first_char_from_keys(dictionary): 251 | elements = [] 252 | for key, value in dictionary.items(): 253 | key = key.decode('ascii') 254 | if key in ['.id', '.proplist']: 255 | key = key[1:] 256 | elements.append((key, value)) 257 | return dict(elements) 258 | 259 | def get(self, **kwargs): 260 | return self.call('print', {}, kwargs) 261 | 262 | def detailed_get(self, **kwargs): 263 | return self.call('print', {'detail': b''}, kwargs) 264 | 265 | def set(self, **kwargs): 266 | return self.call('set', kwargs) 267 | 268 | def add(self, **kwargs): 269 | return self.call('add', kwargs) 270 | 271 | def remove(self, **kwargs): 272 | return self.call('remove', kwargs) 273 | 274 | 275 | class RouterboardResource(BaseRouterboardResource): 276 | def detailed_get(self, **kwargs): 277 | return self.call('print', {'detail': ''}, kwargs) 278 | 279 | def call(self, command, set_kwargs, query_kwargs=None): 280 | query_kwargs = query_kwargs or {} 281 | result = super(RouterboardResource, self).call( 282 | command, self._encode_kwargs(set_kwargs), 283 | self._encode_kwargs(query_kwargs)) 284 | for item in result: 285 | for k in item: 286 | item[k] = item[k].decode('ascii') 287 | return result 288 | 289 | def _encode_kwargs(self, kwargs): 290 | return dict((k, v.encode('ascii')) for k, v in kwargs.items()) 291 | 292 | 293 | class RouterboardAPI(object): 294 | def __init__(self, host, username='api', password='', port=8728, ssl=False): 295 | self.host = host 296 | self.username = username 297 | self.password = password 298 | self.socket = None 299 | self.port = port 300 | self.ssl = ssl 301 | self.reconnect() 302 | 303 | def __enter__(self): 304 | return self 305 | 306 | def __exit__(self, _, __, ___): 307 | self.close_connection() 308 | 309 | def reconnect(self): 310 | if self.socket: 311 | self.close_connection() 312 | try: 313 | for retry in retryloop(10, delay=0.1, timeout=30): 314 | try: 315 | self.connect() 316 | self.login() 317 | except socket.error: 318 | retry() 319 | except (socket.error, RetryError) as e: 320 | raise RosAPIConnectionError(str(e)) 321 | 322 | def connect(self): 323 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 324 | sock.settimeout(15.0) 325 | sock.connect((self.host, self.port)) 326 | set_keepalive(sock, after_idle_sec=10) 327 | if self.ssl: 328 | try: 329 | self.socket = ssl.wrap_socket(sock) 330 | except ssl.SSLError as e: 331 | raise RosAPIConnectionError(str(e)) 332 | else: 333 | self.socket = sock 334 | self.api_client = RosAPI(self.socket) 335 | 336 | def login(self): 337 | self.api_client.login(self.username.encode('ascii'), 338 | self.password.encode('ascii')) 339 | 340 | def get_resource(self, namespace): 341 | return RouterboardResource(self, namespace) 342 | 343 | def get_base_resource(self, namespace): 344 | return BaseRouterboardResource(self, namespace) 345 | 346 | def close_connection(self): 347 | self.socket.close() 348 | 349 | 350 | class Mikrotik(object): 351 | 352 | def __init__(self, hostname, username, password): 353 | self.hostname = hostname 354 | self.username = username 355 | self.password = password 356 | 357 | def login(self): 358 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 359 | s.connect((self.hostname, 8728)) 360 | mt = RosAPI(s) 361 | mt.login(self.username, self.password) 362 | return mt 363 | 364 | def talk(self, talk_command): 365 | r = self.login() 366 | response = r.talk(talk_command) 367 | return(response) 368 | 369 | def api_print(self, base_path, params=None): 370 | command = [base_path + '/print'] 371 | if params is not None: 372 | for key, value in params.iteritems(): 373 | item = b'=' + key + '=' + str(value) 374 | command.append(item) 375 | 376 | return self.talk(command) 377 | 378 | def api_add(self, base_path, params): 379 | command = [base_path + '/add'] 380 | for key, value in params.iteritems(): 381 | item = b'=' + key + '=' + str(value) 382 | command.append(item) 383 | 384 | return self.talk(command) 385 | 386 | def api_edit(self, base_path, params): 387 | command = [base_path + '/set'] 388 | for key, value in params.iteritems(): 389 | item = b'=' + key + '=' + str(value) 390 | command.append(item) 391 | 392 | return self.talk(command) 393 | 394 | def api_remove(self, base_path, remove_id): 395 | command = [ 396 | base_path + '/remove', 397 | b'=.id=' + remove_id 398 | ] 399 | 400 | return self.talk(command) 401 | 402 | def api_command(self, base_path, params=None): 403 | command = [base_path] 404 | if params is not None: 405 | for key, value in params.iteritems(): 406 | item = b'=' + key + '=' + str(value) 407 | command.append(item) 408 | 409 | return self.talk(command) 410 | -------------------------------------------------------------------------------- /pythonlibs/mt_api/retryloop.py: -------------------------------------------------------------------------------- 1 | # retry loop from http://code.activestate.com/recipes/578163-retry-loop/ 2 | import time 3 | import sys 4 | 5 | 6 | class RetryError(Exception): 7 | pass 8 | 9 | 10 | def retryloop(attempts, timeout=None, delay=0, backoff=1): 11 | starttime = time.time() 12 | success = set() 13 | for i in range(attempts): 14 | success.add(True) 15 | yield success.clear 16 | if success: 17 | return 18 | duration = time.time() - starttime 19 | if timeout is not None and duration > timeout: 20 | break 21 | if delay: 22 | time.sleep(delay) 23 | delay *= backoff 24 | 25 | e = sys.exc_info()[1] 26 | 27 | # No pending exception? Make one 28 | if e is None: 29 | try: 30 | raise RetryError 31 | except RetryError as exc: 32 | e = exc 33 | 34 | # Decorate exception with retry information: 35 | e.args = e.args + ("on attempt {0} of {1} after {2:.3f} seconds".format( 36 | i + 1, attempts, duration), ) 37 | 38 | raise e 39 | -------------------------------------------------------------------------------- /pythonlibs/mt_api/socket_utils.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | 4 | # http://stackoverflow.com/a/14855726 5 | def set_keepalive(sock, after_idle_sec=1, interval_sec=3, max_fails=5): 6 | """Set TCP keepalive on an open socket. 7 | 8 | It activates after 1 second (after_idle_sec) of idleness, 9 | then sends a keepalive ping once every 3 seconds (interval_sec), 10 | and closes the connection after 5 failed ping (max_fails), or 15 seconds 11 | """ 12 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) 13 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, after_idle_sec) 14 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval_sec) 15 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, max_fails) 16 | -------------------------------------------------------------------------------- /pythonlibs/mt_common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from ansible.module_utils import mt_api 3 | import re 4 | import sys 5 | import socket 6 | 7 | 8 | def list_to_string(list): 9 | list_string = "" 10 | list_string = ','.join(map(str, list)) 11 | return list_string 12 | 13 | 14 | def clean_params(params): 15 | ''' 16 | remove keys with empty values 17 | modify keys with '_' to match mikrotik parameters 18 | convert yes/no to true/false 19 | ''' 20 | if isinstance(params, dict): 21 | for key in list(params): 22 | if params[key] is None: 23 | del params[key] 24 | continue 25 | 26 | new_key = re.sub('_', '-', key) 27 | if new_key != key: 28 | params[new_key] = str(params[key]) 29 | del params[key] 30 | continue 31 | 32 | if params[key] == "yes": 33 | params[key] = "true" 34 | if params[key] == "no": 35 | params[key] = "false" 36 | else: 37 | print("Must be a dictionary") 38 | 39 | 40 | class MikrotikIdempotent(): 41 | ''' 42 | MikrotikIdempotent Class 43 | - A helper class for Ansible modules to abstract common functions. 44 | 45 | Example Usage: 46 | mt_obj = MikrotikIdempotent( 47 | hostname = params['hostname'], 48 | username = params['username'], 49 | password = params['password'], 50 | state = None, 51 | desired_params = params['settings'], 52 | idempotent_param= 'name', 53 | api_path = '/interface/ethernet', 54 | ) 55 | 56 | mt_obj.sync_state() 57 | ''' 58 | 59 | def __init__( 60 | self, hostname, username, password, desired_params, api_path, 61 | state, idempotent_param, check_mode=False): 62 | 63 | self.hostname = hostname 64 | self.username = username 65 | self.password = password 66 | self.state = state 67 | self.desired_params = desired_params 68 | self.idempotent_param = idempotent_param 69 | self.current_params = {} 70 | self.api_path = api_path 71 | self.check_mode = check_mode 72 | 73 | self.login_success = False 74 | self.changed = False 75 | self.changed_msg = [] 76 | self.failed = False 77 | self.failed_msg = [] 78 | 79 | self.login() 80 | 81 | def login(self): 82 | self.mk = mt_api.Mikrotik( 83 | self.hostname, 84 | self.username, 85 | self.password, 86 | ) 87 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 88 | result = sock.connect_ex((self.hostname, 8728)) 89 | if result == 0: 90 | try: 91 | self.mk.login() 92 | self.login_success = True 93 | except Exception as e: 94 | self.failed_msg = "Could not log into Mikrotik device." + " Check the username and password. Exception {} - {}".format(type(e), e), 95 | else: 96 | self.failed_msg = "Could not access RouterOS api." + " Verify API service is enabled and not blocked by firewall.", 97 | 98 | 99 | def get_current_params(self): 100 | clean_params(self.desired_params) 101 | self.param_id = None 102 | self.current_param = None 103 | self.current_params = self.mk.api_print(base_path=self.api_path) 104 | 105 | # When state and idempotent_param is None we are working 106 | # on editable params only and we are grabbing the only item from response 107 | if self.state is None and self.idempotent_param is None: 108 | self.current_param = self.current_params[0][1] 109 | 110 | # Else we iterate over every item in the list until we find the matching 111 | # params 112 | # We also set the param_id here to reference later for editing or removal 113 | else: 114 | for current_param in self.current_params: 115 | if self.idempotent_param in current_param[1]: 116 | if self.desired_params[self.idempotent_param] == current_param[1][self.idempotent_param]: 117 | self.current_param = current_param[1] 118 | self.param_id = current_param[1]['.id'] 119 | # current_param now is a dict, something like: 120 | # { 121 | # ".id": "*1", 122 | # "full-duplex": "true", 123 | # "mac-address": "08:00:27:6F:4C:22", 124 | # "mtu": "1500", 125 | # "name": "ether1", 126 | # ... 127 | # } 128 | 129 | def add(self): 130 | # When current_param is empty we need to call api_add method to add 131 | # all the parameters in the desired_params 132 | if self.current_param is None: 133 | # check if we have a list within the dictionary 134 | # convert the list to string to pass to mikrotik 135 | for i in self.desired_params: 136 | if isinstance(self.desired_params[i], list): 137 | self.desired_params[i] = list_to_string(self.desired_params[i]) 138 | self.new_params = self.desired_params 139 | self.old_params = "" 140 | if not self.check_mode: 141 | self.mk.api_add( 142 | base_path = self.api_path, 143 | params = self.desired_params, 144 | ) 145 | self.changed = True 146 | 147 | # Else we need to determing what the difference between the currently 148 | # and the desired 149 | else: 150 | self.edit() 151 | 152 | def rem(self): 153 | # if param_id is set this means there is currently a matching item 154 | # which we will remove 155 | if self.param_id: 156 | self.new_params = "item removed" 157 | self.old_params = self.desired_params 158 | if not self.check_mode: 159 | self.mk.api_remove( 160 | base_path=self.api_path, 161 | remove_id=self.param_id, 162 | ) 163 | self.changed = True 164 | 165 | def edit(self): 166 | # out_params is used to pass to api_edit() to make changes 167 | # to a mikrotik device 168 | out_params = {} 169 | # old_params used storing old values that are going to be changed 170 | # to aid in the diff output 171 | old_params = {} # used to store values of params we change 172 | 173 | # iterate over items in desired params and 174 | # match against items in current_param 175 | # to figure out the difference 176 | for desired_param in self.desired_params: 177 | # check if a desired item is already set in mikrotik 178 | if desired_param in self.current_param: 179 | # check if we have a list within the dictionary 180 | # convert mikrotik string to list to get a diff 181 | if isinstance(self.desired_params[desired_param], list): 182 | if desired_param in self.current_param: 183 | current_param_list = self.current_param[desired_param].split(',') 184 | if set(self.desired_params[desired_param]) != set(current_param_list): 185 | out_params[desired_param] = list_to_string(self.desired_params[desired_param]) 186 | old_params[desired_param] = str(self.current_param[desired_param]) 187 | else: 188 | out_params[desired_param] = list_to_string(self.desired_params[desired_param]) 189 | # value is not a list, move on and identify difference 190 | else: 191 | if self.current_param[desired_param] != str(self.desired_params[desired_param]): 192 | out_params[desired_param] = str(self.desired_params[desired_param]) 193 | old_params[desired_param] = str(self.current_param[desired_param]) 194 | # since we didn't get a matching key from mikrotik settings 195 | # we'll it the out_params to whatever is desired_param 196 | else: 197 | if isinstance(desired_param, list): 198 | out_params[desired_param] = list_to_string(self.desired_params[desired_param]) 199 | else: 200 | out_params[desired_param] = str(self.desired_params[desired_param]) 201 | 202 | # When out_params has been set it means we found our diff 203 | # and will set it on the mikrotik 204 | if out_params: 205 | if self.param_id is not None: 206 | out_params['.id'] = self.current_param['.id'] 207 | 208 | if not self.check_mode: 209 | self.mk.api_edit( 210 | base_path = self.api_path, 211 | params = out_params, 212 | ) 213 | 214 | # we don't need to show the .id in the changed message 215 | if '.id' in out_params: 216 | del out_params['.id'] 217 | 218 | self.changed_msg.append({ 219 | "new_params": out_params, 220 | "old_params": old_params, 221 | }) 222 | 223 | self.new_params = out_params 224 | self.old_params = old_params 225 | self.changed = True 226 | 227 | def sync_state(self): 228 | self.get_current_params() 229 | 230 | # When state and idempotent_param are not set we are working 231 | # on editable parameters only that we can't add or remove 232 | if self.state is None and self.idempotent_param is None: 233 | self.edit() 234 | elif self.state == "absent": 235 | self.rem() 236 | elif self.state == "present" or self.idempotent_param: 237 | self.add() 238 | -------------------------------------------------------------------------------- /tasks/backup_recover.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ################################################################ 3 | # TEMPORARY PLAYBOOK TO STORE THE ROLLBACK PROCEDURE 4 | ####################################################################### 5 | 6 | ################################################################### 7 | # create a backup and add a scheduler to rollback if we lose connection 8 | # to the a mikrotik device during the ansible run. 9 | # Place this in the begging of your playbook 10 | ##################################################################### 11 | - name: run command module to create a backup 12 | mt_command: 13 | hostname: "{{ mt_hostname }}" 14 | username: "{{ mt_user }}" 15 | password: "{{ mt_pass }}" 16 | command: /system/backup/save 17 | command_arguments: 18 | name: pre_deploy 19 | password: 123 20 | 21 | - name: add rollback scheduler task 22 | mt_system_scheduler: 23 | hostname: "{{ mt_hostname }}" 24 | username: "{{ mt_user }}" 25 | password: "{{ mt_pass }}" 26 | state: present 27 | name: rollback 28 | on_event: /system backup load name=pre_deploy.backup password=123 29 | interval: 30m 30 | policy: 31 | - password 32 | - reboot 33 | - write 34 | - sensitive 35 | - test 36 | - read 37 | - policy 38 | 39 | ################################################################### 40 | # Place this in the end of your mikrotik playbook this will remove the 41 | # rollback scheduler task if the playbook runs successfully 42 | ##################################################################### 43 | - name: remove rollback scheduler task if run succesfull 44 | hosts: govsat 45 | gather_facts: no 46 | connection: local 47 | tasks: 48 | - mt_system_scheduler: 49 | hostname: "{{ mt_hostname }}" 50 | username: "{{ mt_user }}" 51 | password: "{{ mt_pass }}" 52 | state: absent 53 | name: rollback 54 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: set ip 3 | mikrotik: 4 | interface: "ether6-master" 5 | ip_addr: "192.168.50.1" 6 | -------------------------------------------------------------------------------- /tests/integration/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | module_utils = ./pythonlibs/ 3 | -------------------------------------------------------------------------------- /tests/integration/library: -------------------------------------------------------------------------------- 1 | ../../library/ -------------------------------------------------------------------------------- /tests/integration/pythonlibs: -------------------------------------------------------------------------------- 1 | ../../pythonlibs/ -------------------------------------------------------------------------------- /tests/integration/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | pushd . 5 | 6 | cd "$ABSOLUTE_PATH" 7 | ansible-playbook tests.yml --diff -i 127.0.0.1, $@ 8 | 9 | popd >/dev/null 10 | -------------------------------------------------------------------------------- /tests/integration/tasks/hotspot-tests.yml: -------------------------------------------------------------------------------- 1 | - name: add a hotspot profile 2 | mt_hotspot: 3 | hostname: "{{ mt_hostname }}" 4 | username: "{{ mt_user }}" 5 | password: "{{ mt_pass }}" 6 | state: present 7 | parameter: profile 8 | settings: 9 | dns-name: internet.com 10 | login-by: http-pap 11 | name: Hotspot1 12 | radius-interim-update: 3m 13 | use-radius: "yes" 14 | 15 | - name: NEVER_CHANGES add a hotspot profile, check idempotency 16 | mt_hotspot: 17 | hostname: "{{ mt_hostname }}" 18 | username: "{{ mt_user }}" 19 | password: "{{ mt_pass }}" 20 | parameter: profile 21 | state: present 22 | settings: 23 | dns-name: internet.com 24 | login-by: http-pap 25 | name: Hotspot1 26 | radius-interim-update: 3m 27 | use-radius: "yes" 28 | register: profile_add 29 | failed_when: ( 30 | not ansible_check_mode 31 | ) and ( 32 | ( profile_add | changed ) 33 | ) 34 | 35 | - name: ALWAYS_CHANGES edit a hotspot profile, check changes 36 | mt_hotspot: 37 | hostname: "{{ mt_hostname }}" 38 | username: "{{ mt_user }}" 39 | password: "{{ mt_pass }}" 40 | parameter: profile 41 | state: present 42 | settings: 43 | dns-name: internet.com 44 | login-by: http-pap 45 | name: Hotspot1 46 | radius-interim-update: 4m 47 | use-radius: "yes" 48 | register: profile_edit 49 | failed_when: not ( profile_edit | changed ) 50 | 51 | - name: add a hotspot 52 | mt_hotspot: 53 | hostname: "{{ mt_hostname }}" 54 | username: "{{ mt_user }}" 55 | password: "{{ mt_pass }}" 56 | parameter: hotspot 57 | state: present 58 | settings: 59 | address-pool: pool1 60 | disabled: "no" 61 | interface: ether2 62 | name: NETACCESS1 63 | profile: Hotspot1 64 | idle-timeout: 3s 65 | 66 | - name: NEVER_CHANGES add a hotspot again, check idempotency 67 | mt_hotspot: 68 | hostname: "{{ mt_hostname }}" 69 | username: "{{ mt_user }}" 70 | password: "{{ mt_pass }}" 71 | state: present 72 | parameter: hotspot 73 | settings: 74 | address-pool: pool1 75 | disabled: "no" 76 | interface: ether2 77 | name: NETACCESS1 78 | profile: Hotspot1 79 | idle-timeout: 3s 80 | register: hotspot_add 81 | failed_when: ( 82 | not ansible_check_mode 83 | ) and ( 84 | ( hotspot_add | changed ) 85 | ) 86 | 87 | - name: ALWAYS_CHANGES edit a hotspot, check changes 88 | mt_hotspot: 89 | hostname: "{{ mt_hostname }}" 90 | username: "{{ mt_user }}" 91 | password: "{{ mt_pass }}" 92 | state: present 93 | parameter: hotspot 94 | settings: 95 | address-pool: pool1 96 | disabled: "no" 97 | interface: ether2 98 | name: NETACCESS1 99 | profile: Hotspot1 100 | idle-timeout: 4s 101 | register: hotspot_edit 102 | failed_when: not ( hotspot_edit | changed ) 103 | 104 | - name: add a walled-garden 105 | mt_hotspot: 106 | hostname: "{{ mt_hostname }}" 107 | username: "{{ mt_user }}" 108 | password: "{{ mt_pass }}" 109 | state: present 110 | parameter: walled-garden 111 | settings: 112 | comment: "Allow Personal Web Portal" 113 | dst-host: google.com 114 | server: NETACCESS1 115 | method: PUT 116 | 117 | - name: NEVER_CHANGES add a walled-garden, check idempotency 118 | mt_hotspot: 119 | hostname: "{{ mt_hostname }}" 120 | username: "{{ mt_user }}" 121 | password: "{{ mt_pass }}" 122 | state: present 123 | parameter: walled-garden 124 | settings: 125 | comment: "Allow Personal Web Portal" 126 | dst-host: google.com 127 | server: NETACCESS1 128 | method: PUT 129 | register: walled_garden_add 130 | failed_when: ( 131 | not ansible_check_mode 132 | ) and ( 133 | ( walled_garden_add | changed ) 134 | ) 135 | 136 | - name: ALWAYS_CHANGES edit walled-garden settings, check changes 137 | mt_hotspot: 138 | hostname: "{{ mt_hostname }}" 139 | username: "{{ mt_user }}" 140 | password: "{{ mt_pass }}" 141 | state: present 142 | parameter: walled-garden 143 | settings: 144 | comment: "Allow Personal Web Portal" 145 | dst-host: google.com 146 | server: NETACCESS1 147 | method: TRACE 148 | register: walled_garden_edit 149 | failed_when: not ( walled_garden_edit | changed ) 150 | 151 | - name: ALWAYS_CHANGES remove walled-garden 152 | mt_hotspot: 153 | hostname: "{{ mt_hostname }}" 154 | username: "{{ mt_user }}" 155 | password: "{{ mt_pass }}" 156 | state: absent 157 | parameter: walled-garden 158 | settings: 159 | comment: "Allow Personal Web Portal" 160 | register: walled_garden_rem 161 | failed_when: ( 162 | not ansible_check_mode 163 | ) and ( 164 | not ( walled_garden_rem | changed ) 165 | ) 166 | 167 | - name: ALWAYS_CHANGES remove a hotspot 168 | mt_hotspot: 169 | hostname: "{{ mt_hostname }}" 170 | username: "{{ mt_user }}" 171 | password: "{{ mt_pass }}" 172 | parameter: hotspot 173 | state: absent 174 | settings: 175 | name: NETACCESS1 176 | register: hotspot_rem 177 | failed_when: ( 178 | not ansible_check_mode 179 | ) and ( 180 | not ( hotspot_rem | changed ) 181 | ) 182 | 183 | - name: ALWAYS_CHANGES remove a hotspot profile 184 | mt_hotspot: 185 | hostname: "{{ mt_hostname }}" 186 | username: "{{ mt_user }}" 187 | password: "{{ mt_pass }}" 188 | parameter: profile 189 | state: absent 190 | settings: 191 | name: Hotspot1 192 | register: profile_rem 193 | failed_when: ( 194 | not ansible_check_mode 195 | ) and ( 196 | not ( profile_rem | changed ) 197 | ) 198 | -------------------------------------------------------------------------------- /tests/integration/tasks/radius-tests.yml: -------------------------------------------------------------------------------- 1 | - name: Test adding a radius item 2 | mt_radius: 3 | hostname: "{{ mt_hostname }}" 4 | username: "{{ mt_user }}" 5 | password: "{{ mt_pass }}" 6 | state: "present" 7 | parameter: radius 8 | settings: 9 | address: "192.168.12.2" 10 | comment: 'Ansible - radius test 1' 11 | secret: 'password' 12 | service: 13 | - login 14 | - hotspot 15 | - wireless 16 | timeout: '2s500ms' 17 | 18 | - name: ALWAYS_CHANGES Test editing an existing radius item (edit service item) 19 | mt_radius: 20 | hostname: "{{ mt_hostname }}" 21 | username: "{{ mt_user }}" 22 | password: "{{ mt_pass }}" 23 | state: "present" 24 | parameter: radius 25 | settings: 26 | address: "192.168.12.2" 27 | comment: 'Ansible - radius test 1' 28 | secret: 'password' 29 | service: 30 | - login 31 | - hotspot 32 | - wireless 33 | - dhcp 34 | timeout: '2s500ms' 35 | register: radius_test_1_edit 36 | failed_when: ( 37 | not ansible_check_mode 38 | ) and ( 39 | not ( radius_test_1_edit | changed ) 40 | ) 41 | 42 | - name: ALWAYS_CHANGES Test editing an existing radius item (change service list) 43 | mt_radius: 44 | hostname: "{{ mt_hostname }}" 45 | username: "{{ mt_user }}" 46 | password: "{{ mt_pass }}" 47 | state: "present" 48 | parameter: radius 49 | settings: 50 | address: "192.168.12.2" 51 | comment: 'Ansible - radius test 1' 52 | secret: 'password' 53 | service: 54 | - login 55 | - hotspot 56 | - wireless 57 | timeout: '2s500ms' 58 | register: radius_test_1_edit 59 | failed_when: ( 60 | not ansible_check_mode 61 | ) and ( 62 | not ( radius_test_1_edit | changed ) 63 | ) 64 | 65 | - name: Test adding a duplicate of the first radius item 66 | mt_radius: 67 | hostname: "{{ mt_hostname }}" 68 | username: "{{ mt_user }}" 69 | password: "{{ mt_pass }}" 70 | state: "present" 71 | parameter: radius 72 | settings: 73 | address: "192.168.12.2" 74 | comment: 'Ansible - radius test 1' 75 | secret: 'password' 76 | service: 77 | - login 78 | - hotspot 79 | - wireless 80 | timeout: '2s500ms' 81 | register: radius_test_1_duplicate 82 | failed_when: ( 83 | not ansible_check_mode 84 | ) and ( 85 | ( radius_test_1_duplicate|changed ) 86 | ) 87 | 88 | - name: ALWAYS_CHANGES Test adding another radius item to later remove 89 | mt_radius: 90 | hostname: "{{ mt_hostname }}" 91 | username: "{{ mt_user }}" 92 | password: "{{ mt_pass }}" 93 | state: "present" 94 | parameter: radius 95 | settings: 96 | address: "192.168.12.2" 97 | comment: 'Ansible - radius test 2' 98 | secret: 'password' 99 | service: 100 | - login 101 | - hotspot 102 | - wireless 103 | timeout: '2s500ms' 104 | register: radius_test_2 105 | failed_when: ( 106 | not ansible_check_mode 107 | ) and ( 108 | not ( radius_test_2 | changed ) 109 | ) 110 | 111 | - name: ALWAYS_CHANGES Test removing a radius item 112 | mt_radius: 113 | hostname: "{{ mt_hostname }}" 114 | username: "{{ mt_user }}" 115 | password: "{{ mt_pass }}" 116 | state: "absent" 117 | parameter: radius 118 | settings: 119 | comment: 'Ansible - radius test 2' 120 | register: radius_test_2_rem 121 | failed_when: ( 122 | not ansible_check_mode 123 | ) and ( 124 | not ( radius_test_2_rem | changed ) 125 | ) 126 | 127 | - name: Change incoming settings 128 | mt_radius: 129 | hostname: "{{ mt_hostname }}" 130 | username: "{{ mt_user }}" 131 | password: "{{ mt_pass }}" 132 | parameter: incoming 133 | settings: 134 | accept: "true" 135 | port: "37988" 136 | 137 | - name: ALWAYS_CHANGES Change incoming settings 138 | mt_radius: 139 | hostname: "{{ mt_hostname }}" 140 | username: "{{ mt_user }}" 141 | password: "{{ mt_pass }}" 142 | parameter: incoming 143 | settings: 144 | accept: "true" 145 | port: "37955" 146 | register: change_incoming 147 | failed_when: ( 148 | not ansible_check_mode 149 | ) and ( 150 | not ( change_incoming | changed ) 151 | ) 152 | 153 | - name: NEVER_CHANGES check idempotency of incoming settings 154 | mt_radius: 155 | hostname: "{{ mt_hostname }}" 156 | username: "{{ mt_user }}" 157 | password: "{{ mt_pass }}" 158 | parameter: incoming 159 | settings: 160 | accept: "true" 161 | port: "37955" 162 | register: idem_incoming 163 | failed_when: ( 164 | not ansible_check_mode 165 | ) and ( 166 | ( idem_incoming | changed ) 167 | ) 168 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-address-list.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test adding a firewall address-list 3 | mt_ip: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | state: "present" 8 | parameter: 'firewall/address-list' 9 | settings: "{{ item }}" 10 | with_items: 11 | - address: 192.168.1.2 12 | comment: dns1 13 | list: test_list 14 | - address: 192.168.1.3 15 | comment: dns2 16 | list: test_list 17 | - address: 192.168.1.6 18 | comment: test_comment3 19 | list: test_list 20 | 21 | - name: ALWAYS_CHANGES Test editing a firewall address-list 22 | mt_ip: 23 | hostname: "{{ mt_hostname }}" 24 | username: "{{ mt_user }}" 25 | password: "{{ mt_pass }}" 26 | state: "present" 27 | parameter: 'firewall/address-list' 28 | settings: 29 | address: 192.168.1.2 30 | comment: dns1 31 | list: test_list2 32 | register: address_list_edit_1 33 | failed_when: ( 34 | not ansible_check_mode 35 | ) and ( 36 | not ( address_list_edit_1 | changed ) 37 | ) 38 | 39 | - name: NEVER_CHANGES Test adding a duplicate address-list 40 | mt_ip: 41 | hostname: "{{ mt_hostname }}" 42 | username: "{{ mt_user }}" 43 | password: "{{ mt_pass }}" 44 | state: "present" 45 | parameter: 'firewall/address-list' 46 | settings: 47 | address: 192.168.1.3 48 | comment: dns2 49 | list: test_list 50 | register: add_address_list_add_dup_1 51 | failed_when: ( 52 | not ansible_check_mode 53 | ) and ( 54 | ( add_address_list_add_dup_1 | changed ) 55 | ) 56 | 57 | - name: ALWAYS_CHANGES Test removing a firewall address-list 58 | mt_ip: 59 | hostname: "{{ mt_hostname }}" 60 | username: "{{ mt_user }}" 61 | password: "{{ mt_pass }}" 62 | state: "absent" 63 | parameter: 'firewall/address-list' 64 | settings: 65 | address: 192.168.1.2 66 | comment: dns1 67 | list: test_list 68 | register: address_list_rem_1 69 | failed_when: ( 70 | not ansible_check_mode 71 | ) and ( 72 | not ( address_list_rem_1 | changed ) 73 | ) 74 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-bridge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: add eoip interface 3 | mt_command: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | command: /interface/eoip/add 8 | command_arguments: 9 | name: eoip-interface1 10 | ignore_errors: yes 11 | 12 | - name: Add bridge1 13 | mt_interfaces: 14 | hostname: "{{ mt_hostname }}" 15 | username: "{{ mt_user }}" 16 | password: "{{ mt_pass }}" 17 | state: present 18 | parameter: bridge 19 | settings: 20 | name: "{{ item }}" 21 | arp: proxy-arp 22 | with_items: 23 | - "bridge1" 24 | 25 | - name: Add bridge1 again (idempotency test) 26 | mt_interfaces: 27 | hostname: "{{ mt_hostname }}" 28 | username: "{{ mt_user }}" 29 | password: "{{ mt_pass }}" 30 | state: present 31 | parameter: bridge 32 | settings: 33 | name: "{{ item }}" 34 | arp: proxy-arp 35 | with_items: 36 | - "bridge1" 37 | register: mod_bridge1 38 | failed_when: ( 39 | not ansible_check_mode 40 | ) and ( 41 | ( mod_bridge1 | changed ) 42 | ) 43 | 44 | # bridge ports depend on bridge being created first 45 | 46 | - name: Add interface to bridge1 (port) 47 | mt_interfaces: 48 | hostname: "{{ mt_hostname }}" 49 | username: "{{ mt_user }}" 50 | password: "{{ mt_pass }}" 51 | parameter: "bridge port" 52 | state: present 53 | settings: 54 | bridge: "{{ item[0] }}" 55 | interface: "{{ item[1] }}" 56 | with_nested: 57 | - [ "bridge1" ] 58 | - [ "ether2" ] 59 | 60 | - name: Add interface to bridge1 (port) again (idempotency test) 61 | mt_interfaces: 62 | hostname: "{{ mt_hostname }}" 63 | username: "{{ mt_user }}" 64 | password: "{{ mt_pass }}" 65 | parameter: "bridge port" 66 | state: present 67 | settings: 68 | bridge: "{{ item[0] }}" 69 | interface: "{{ item[1] }}" 70 | with_nested: 71 | - [ "bridge1" ] 72 | - [ "ether2" ] 73 | register: mod_bridge1_port 74 | failed_when: ( 75 | not ansible_check_mode 76 | ) and ( 77 | ( mod_bridge1_port | changed ) 78 | ) 79 | 80 | - name: Add additional param to bridge port 81 | mt_interfaces: 82 | hostname: "{{ mt_hostname }}" 83 | username: "{{ mt_user }}" 84 | password: "{{ mt_pass }}" 85 | parameter: "bridge port" 86 | state: present 87 | settings: 88 | bridge: "{{ item[0] }}" 89 | interface: "{{ item[1] }}" 90 | edge: "{{ item[2] }}" 91 | with_nested: 92 | - [ "bridge1" ] 93 | - [ "ether2" ] 94 | - [ "yes-discover" ] 95 | 96 | - name: ALWAYS_CHANGES Add 2nd interface to bridge1 port 97 | mt_interfaces: 98 | hostname: "{{ mt_hostname }}" 99 | username: "{{ mt_user }}" 100 | password: "{{ mt_pass }}" 101 | parameter: "bridge port" 102 | state: present 103 | settings: 104 | bridge: bridge1 105 | interface: eoip-tunnel1 106 | register: bridge1_add_2nd_inter 107 | failed_when: ( 108 | not ansible_check_mode 109 | ) and ( 110 | not ( bridge1_add_2nd_inter | changed ) 111 | ) 112 | 113 | - name: ALWAYS_CHANGES Remove 2nd interface to bridge1 port 114 | mt_interfaces: 115 | hostname: "{{ mt_hostname }}" 116 | username: "{{ mt_user }}" 117 | password: "{{ mt_pass }}" 118 | parameter: "bridge port" 119 | state: absent 120 | settings: 121 | bridge: bridge1 122 | interface: "eoip-tunnel1" 123 | register: bridge1_rem_2nd_inter 124 | failed_when: ( 125 | not ansible_check_mode 126 | ) and ( 127 | not ( bridge1_rem_2nd_inter | changed ) 128 | ) 129 | 130 | - name: Add bridge2 131 | mt_interfaces: 132 | hostname: "{{ mt_hostname }}" 133 | username: "{{ mt_user }}" 134 | password: "{{ mt_pass }}" 135 | parameter: "bridge" 136 | state: present 137 | settings: 138 | name: "bridge2" 139 | arp: "reply-only" 140 | 141 | - name: Adjust settings 142 | mt_interfaces: 143 | hostname: "{{ mt_hostname }}" 144 | username: "{{ mt_user }}" 145 | password: "{{ mt_pass }}" 146 | parameter: "bridge settings" 147 | settings: 148 | allow-fast-path: "yes" 149 | use-ip-firewall: "yes" 150 | use-ip-firewall-for-vlan: "yes" 151 | use-ip-firewall-for-pppoe: "no" 152 | 153 | - name: Adjust settings (test changes) 154 | mt_interfaces: 155 | hostname: "{{ mt_hostname }}" 156 | username: "{{ mt_user }}" 157 | password: "{{ mt_pass }}" 158 | parameter: "bridge settings" 159 | settings: 160 | allow-fast-path: "yes" 161 | use-ip-firewall-for-vlan: "no" 162 | use-ip-firewall-for-pppoe: "no" 163 | register: bridge_settings_1 164 | failed_when: ( 165 | not ansible_check_mode 166 | ) and ( 167 | not ( bridge_settings_1 | changed ) 168 | ) 169 | 170 | - name: Adjust settings again (idempotency test) 171 | mt_interfaces: 172 | hostname: "{{ mt_hostname }}" 173 | username: "{{ mt_user }}" 174 | password: "{{ mt_pass }}" 175 | parameter: "bridge settings" 176 | settings: 177 | allow-fast-path: "yes" 178 | use-ip-firewall-for-vlan: "no" 179 | use-ip-firewall-for-pppoe: "no" 180 | register: bridge_settings_2 181 | failed_when: ( 182 | not ansible_check_mode 183 | ) and ( 184 | ( bridge_settings_2 | changed ) 185 | ) 186 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-command.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: add scheduler 3 | mt_system_scheduler: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | state: present 8 | name: ansible_test 9 | on_event: 'put "test"' 10 | interval: 1s 11 | 12 | - name: run command to disable system scheduler task 13 | mt_command: 14 | hostname: "{{ mt_hostname }}" 15 | username: "{{ mt_user }}" 16 | password: "{{ mt_pass }}" 17 | command: /system/scheduler/disable 18 | command_arguments: 19 | numbers: ansible_test 20 | 21 | - name: run command 22 | mt_command: 23 | hostname: "{{ mt_hostname }}" 24 | username: "{{ mt_user }}" 25 | password: "{{ mt_pass }}" 26 | command: "/interface/print" 27 | tags: print 28 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-dhcp-server.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test adding ip pool to be used by dhcp_server 3 | mt_ip: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | state: present 8 | parameter: pool 9 | settings: 10 | name: pool1 11 | ranges: 102.3.4.5 12 | 13 | - name: Test adding a dhcp_server 14 | mt_dhcp_server: 15 | hostname: "{{ mt_hostname }}" 16 | username: "{{ mt_user }}" 17 | password: "{{ mt_pass }}" 18 | state: "present" 19 | parameter: dhcp-server 20 | settings: 21 | name: ansible_test 22 | address-pool: 'pool1' 23 | interface: ether2 24 | always-broadcast: "yes" 25 | authoritative: after-2sec-delay 26 | 27 | - name: ALWAYS_CHANGES Test editing an existing dhcp server (change authoritative) 28 | mt_dhcp_server: 29 | hostname: "{{ mt_hostname }}" 30 | username: "{{ mt_user }}" 31 | password: "{{ mt_pass }}" 32 | state: "present" 33 | parameter: dhcp-server 34 | settings: 35 | name: ansible_test 36 | address-pool: 'pool1' 37 | interface: ether1 38 | always-broadcast: "yes" 39 | authoritative: after-10sec-delay 40 | register: dhcp_server_test_1_edit 41 | failed_when: ( 42 | not ansible_check_mode 43 | ) and ( 44 | not ( dhcp_server_test_1_edit | changed ) 45 | ) 46 | 47 | - name: NEVER_CHANGES Test adding a duplicate of the first dhcp server 48 | mt_dhcp_server: 49 | hostname: "{{ mt_hostname }}" 50 | username: "{{ mt_user }}" 51 | password: "{{ mt_pass }}" 52 | state: "present" 53 | parameter: dhcp-server 54 | settings: 55 | name: ansible_test 56 | address-pool: 'pool1' 57 | interface: ether1 58 | always-broadcast: "yes" 59 | register: dhcp_server_test_1_duplicate 60 | failed_when: ( 61 | not ansible_check_mode 62 | ) and ( 63 | ( dhcp_server_test_1_duplicate|changed ) 64 | ) 65 | 66 | - name: ALWAYS_CHANGES Test adding another dhcp server to later remove 67 | mt_dhcp_server: 68 | hostname: "{{ mt_hostname }}" 69 | username: "{{ mt_user }}" 70 | password: "{{ mt_pass }}" 71 | state: "present" 72 | parameter: dhcp-server 73 | settings: 74 | interface: "ether2" 75 | name: "ansible_test_2" 76 | register: dhcp_server_test_2 77 | failed_when: ( 78 | not ansible_check_mode 79 | ) and ( 80 | not ( dhcp_server_test_2 | changed ) 81 | ) 82 | 83 | - name: ALWAYS_CHANGES Test removing a dhcp server 84 | mt_dhcp_server: 85 | hostname: "{{ mt_hostname }}" 86 | username: "{{ mt_user }}" 87 | password: "{{ mt_pass }}" 88 | state: "absent" 89 | parameter: dhcp-server 90 | settings: 91 | name: "ansible_test_2" 92 | register: dhcp_server_test_2_rem 93 | failed_when: ( 94 | not ansible_check_mode 95 | ) and ( 96 | not ( dhcp_server_test_2_rem | changed ) 97 | ) 98 | 99 | - name: add a dhcp-server network 100 | mt_dhcp_server: 101 | hostname: "{{ mt_hostname }}" 102 | username: "{{ mt_user }}" 103 | password: "{{ mt_pass }}" 104 | state: "present" 105 | parameter: network 106 | settings: 107 | address: '192.168.10.0/24' 108 | 109 | - name: add a second dhcp-server network 110 | mt_dhcp_server: 111 | hostname: "{{ mt_hostname }}" 112 | username: "{{ mt_user }}" 113 | password: "{{ mt_pass }}" 114 | state: "present" 115 | parameter: network 116 | settings: 117 | address: 10.147.172.0/24 118 | comment: "Phones network" 119 | dns-server: 10.147.172.2 120 | gateway: 10.147.172.1 121 | 122 | - name: ALWAYS_CHANGES modify a second dhcp-server network 123 | mt_dhcp_server: 124 | hostname: "{{ mt_hostname }}" 125 | username: "{{ mt_user }}" 126 | password: "{{ mt_pass }}" 127 | state: "present" 128 | parameter: network 129 | settings: 130 | address: 10.147.172.0/24 131 | comment: "Phones network" 132 | dns-server: 10.147.172.20 133 | gateway: 10.147.172.1 134 | register: dhcp_network_mod 135 | failed_when: ( 136 | not ansible_check_mode 137 | ) and ( 138 | not ( dhcp_network_mod | changed ) 139 | ) 140 | 141 | - name: ALWAYS_CHANGES remove first dhcp-server network 142 | mt_dhcp_server: 143 | hostname: "{{ mt_hostname }}" 144 | username: "{{ mt_user }}" 145 | password: "{{ mt_pass }}" 146 | state: "absent" 147 | parameter: network 148 | settings: 149 | address: '192.168.10.0/24' 150 | register: dhcp_network_rem 151 | failed_when: ( 152 | not ansible_check_mode 153 | ) and ( 154 | not ( dhcp_network_rem | changed ) 155 | ) 156 | 157 | - name: Test adding an item to dhcp-server options 158 | mt_dhcp_server: 159 | hostname: "{{ mt_hostname }}" 160 | username: "{{ mt_user }}" 161 | password: "{{ mt_pass }}" 162 | state: "present" 163 | parameter: option 164 | settings: 165 | name: ansible_test 166 | code: "251" 167 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-facts.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Get mikrotik system facts 3 | mt_facts: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | filter: "{{ item }}" 8 | with_items: 9 | - interface_ethernet 10 | - system_ntp_client 11 | - system_routerboard 12 | - ip_route 13 | - ip_dns 14 | - ip_address 15 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-firewall-filter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test adding firewall filter rules 3 | mt_ip_firewall: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | state: "present" 8 | parameter: filter 9 | rule: "{{ item }}" 10 | with_items: 11 | - action: accept 12 | chain: forward 13 | comment: 'Ansible - fw filter rule1' 14 | place-before: '0' 15 | - action: accept 16 | chain: input 17 | comment: 'Ansible - fw filter rule2' 18 | place-before: '1' 19 | - action: passthrough 20 | chain: input 21 | comment: 'Ansible - fw filter rule3' 22 | place-before: '2' 23 | - action: reject 24 | chain: forward 25 | comment: 'Ansible - fw filter rule4' 26 | place-before: '3' 27 | src-address: 192.168.0.0/16 28 | - action: accept 29 | chain: forward 30 | comment: 'Ansible - fw filter rule5' 31 | place-before: '4' 32 | 33 | - name: add some manual rules to simulate chaos, command module 34 | mt_command: 35 | hostname: "{{ mt_hostname }}" 36 | username: "{{ mt_user }}" 37 | password: "{{ mt_pass }}" 38 | command: /ip/firewall/filter/add 39 | command_arguments: "{{ item }}" 40 | with_items: 41 | - action: accept 42 | chain: output 43 | comment: 'manual test rules' 44 | place-before: '3' 45 | - action: accept 46 | chain: output 47 | comment: 'manual test rules' 48 | place-before: '1' 49 | - action: accept 50 | chain: output 51 | comment: 'manual test rules' 52 | place-before: '0' 53 | 54 | - name: Fix firewall state 55 | mt_ip_firewall: 56 | hostname: "{{ mt_hostname }}" 57 | username: "{{ mt_user }}" 58 | password: "{{ mt_pass }}" 59 | state: "present" 60 | parameter: filter 61 | rule: "{{ item }}" 62 | with_items: 63 | - action: accept 64 | chain: forward 65 | comment: 'Ansible - fw filter rule1' 66 | place-before: '0' 67 | - action: accept 68 | chain: input 69 | comment: 'Ansible - fw filter rule2' 70 | place-before: '1' 71 | - action: passthrough 72 | chain: input 73 | comment: 'Ansible - fw filter rule3' 74 | place-before: '2' 75 | - action: reject 76 | chain: forward 77 | comment: 'Ansible - fw filter rule4' 78 | place-before: '3' 79 | src-address: 192.168.0.0/16 80 | - action: accept 81 | chain: forward 82 | comment: 'Ansible - fw filter rule5' 83 | place-before: '4' 84 | 85 | - name: NEVER_CHANGES, check idempotency 86 | mt_ip_firewall: 87 | hostname: "{{ mt_hostname }}" 88 | username: "{{ mt_user }}" 89 | password: "{{ mt_pass }}" 90 | state: "present" 91 | parameter: filter 92 | rule: "{{ item }}" 93 | with_items: 94 | - action: accept 95 | chain: forward 96 | comment: 'Ansible - fw filter rule1' 97 | place-before: '0' 98 | - action: accept 99 | chain: input 100 | comment: 'Ansible - fw filter rule2' 101 | place-before: '1' 102 | - action: passthrough 103 | chain: input 104 | comment: 'Ansible - fw filter rule3' 105 | place-before: '2' 106 | - action: reject 107 | chain: forward 108 | comment: 'Ansible - fw filter rule4' 109 | place-before: '3' 110 | src-address: 192.168.0.0/16 111 | - action: accept 112 | chain: forward 113 | comment: 'Ansible - fw filter rule5' 114 | place-before: '4' 115 | register: check_idem 116 | failed_when: ( 117 | not ansible_check_mode 118 | ) and ( 119 | ( check_idem | changed ) 120 | ) 121 | tags: test-firewall 122 | 123 | - name: ALWAYS_CHANGES Test editing existing rule 124 | mt_ip_firewall: 125 | hostname: "{{ mt_hostname }}" 126 | username: "{{ mt_user }}" 127 | password: "{{ mt_pass }}" 128 | state: "present" 129 | parameter: filter 130 | rule: "{{ item }}" 131 | with_items: 132 | - action: accept 133 | chain: forward 134 | comment: 'Ansible - fw filter rule4' 135 | src-address: 192.168.0.0/16 136 | place-before: '3' 137 | register: edit_filter_rule 138 | failed_when: ( 139 | not ansible_check_mode 140 | ) and ( 141 | not ( edit_filter_rule | changed ) 142 | ) 143 | 144 | - name: NEVER_CHANGES Test editing existing rule check idempotency again 145 | mt_ip_firewall: 146 | hostname: "{{ mt_hostname }}" 147 | username: "{{ mt_user }}" 148 | password: "{{ mt_pass }}" 149 | state: "present" 150 | parameter: filter 151 | rule: "{{ item }}" 152 | with_items: 153 | - action: accept 154 | chain: forward 155 | comment: 'Ansible - fw filter rule4' 156 | src-address: 192.168.0.0/16 157 | place-before: '3' 158 | register: edit_filter_rule_2 159 | failed_when: ( 160 | not ansible_check_mode 161 | ) and ( 162 | ( edit_filter_rule_2 | changed ) 163 | ) 164 | tags: test-firewall 165 | 166 | - name: add a rule to the bottom of the chain 167 | mt_ip_firewall: 168 | hostname: "{{ mt_hostname }}" 169 | username: "{{ mt_user }}" 170 | password: "{{ mt_pass }}" 171 | state: "present" 172 | parameter: filter 173 | rule: "{{ item }}" 174 | with_items: 175 | - action: accept 176 | chain: forward 177 | comment: 'Ansible - fw filter rule20' 178 | src-address: 192.150.0.0/16 179 | place-before: '20' 180 | 181 | - name: ALWAYS_CHANGES, ensure that rule at the bottom changes 182 | mt_ip_firewall: 183 | hostname: "{{ mt_hostname }}" 184 | username: "{{ mt_user }}" 185 | password: "{{ mt_pass }}" 186 | state: "present" 187 | parameter: filter 188 | rule: "{{ item }}" 189 | with_items: 190 | - action: reject 191 | chain: forward 192 | comment: 'Ansible - fw filter rule20' 193 | src-address: 192.150.0.0/16 194 | place-before: '20' 195 | register: edit_filter_rule_3 196 | failed_when: ( 197 | not ansible_check_mode 198 | ) and ( 199 | not ( edit_filter_rule_3 | changed ) 200 | ) 201 | 202 | - name: NEVER_CHANGES add a rule to the bottom of the chain, check_idempotency 203 | mt_ip_firewall: 204 | hostname: "{{ mt_hostname }}" 205 | username: "{{ mt_user }}" 206 | password: "{{ mt_pass }}" 207 | state: "present" 208 | parameter: filter 209 | rule: "{{ item }}" 210 | with_items: 211 | - action: reject 212 | chain: forward 213 | comment: 'Ansible - fw filter rule20' 214 | src-address: 192.150.0.0/16 215 | place-before: '20' 216 | register: edit_filter_rule_4 217 | failed_when: ( 218 | not ansible_check_mode 219 | ) and ( 220 | ( edit_filter_rule_4 | changed ) 221 | ) 222 | 223 | - name: ALWAYS_CHANGES Test removing existing rule 224 | mt_ip_firewall: 225 | hostname: "{{ mt_hostname }}" 226 | username: "{{ mt_user }}" 227 | password: "{{ mt_pass }}" 228 | state: "absent" 229 | parameter: filter 230 | rule: "{{ item }}" 231 | with_items: 232 | - place-before: '4' 233 | register: rem_filter_rule 234 | failed_when: ( 235 | not ansible_check_mode 236 | ) and ( 237 | not ( rem_filter_rule | changed ) 238 | ) 239 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-firewall-nat.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test adding firewall nat rules 3 | mt_ip_firewall: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | state: "present" 8 | parameter: nat 9 | rule: "{{ item }}" 10 | with_items: 11 | - action: accept 12 | chain: srcnat 13 | comment: 'Ansible - fw filter rule1' 14 | place-before: '0' 15 | - action: accept 16 | chain: dstnat 17 | comment: 'Ansible - fw filter rule2' 18 | place-before: '1' 19 | - action: passthrough 20 | chain: srcnat 21 | comment: 'Ansible - fw filter rule3' 22 | place-before: '2' 23 | - action: return 24 | chain: dstnat 25 | comment: 'Ansible - fw filter rule4' 26 | place-before: '3' 27 | src-address: 192.168.0.0/16 28 | - action: redirect 29 | chain: dstnat 30 | comment: 'Ansible - fw filter rule5' 31 | place-before: '4' 32 | - action: redirect 33 | chain: dstnat 34 | comment: 'Ansible - fw filter rule20' 35 | place-before: '19' 36 | 37 | - name: add some manual rules to simulate chaos, command module 38 | mt_command: 39 | hostname: "{{ mt_hostname }}" 40 | username: "{{ mt_user }}" 41 | password: "{{ mt_pass }}" 42 | command: /ip/firewall/nat/add 43 | command_arguments: "{{ item }}" 44 | with_items: 45 | - action: passthrough 46 | chain: srcnat 47 | comment: 'manual test rules' 48 | place-before: '3' 49 | - action: passthrough 50 | chain: srcnat 51 | comment: 'manual test rules' 52 | place-before: '1' 53 | - action: passthrough 54 | chain: srcnat 55 | comment: 'manual test rules' 56 | place-before: '0' 57 | 58 | - name: fix nat state 59 | mt_ip_firewall: 60 | hostname: "{{ mt_hostname }}" 61 | username: "{{ mt_user }}" 62 | password: "{{ mt_pass }}" 63 | state: "present" 64 | parameter: nat 65 | rule: "{{ item }}" 66 | with_items: 67 | - action: accept 68 | chain: srcnat 69 | comment: 'Ansible - fw filter rule1' 70 | place-before: '0' 71 | - action: accept 72 | chain: dstnat 73 | comment: 'Ansible - fw filter rule2' 74 | place-before: '1' 75 | - action: passthrough 76 | chain: srcnat 77 | comment: 'Ansible - fw filter rule3' 78 | place-before: '2' 79 | - action: return 80 | chain: dstnat 81 | comment: 'Ansible - fw filter rule4' 82 | place-before: '3' 83 | src-address: 192.168.0.0/16 84 | - action: redirect 85 | chain: dstnat 86 | comment: 'Ansible - fw filter rule5' 87 | place-before: '4' 88 | - action: redirect 89 | chain: dstnat 90 | comment: 'Ansible - fw filter rule20' 91 | place-before: '19' 92 | 93 | - name: NEVER_CHANGES check_idempotency 94 | mt_ip_firewall: 95 | hostname: "{{ mt_hostname }}" 96 | username: "{{ mt_user }}" 97 | password: "{{ mt_pass }}" 98 | state: "present" 99 | parameter: nat 100 | rule: "{{ item }}" 101 | with_items: 102 | - action: accept 103 | chain: srcnat 104 | comment: 'Ansible - fw filter rule1' 105 | place-before: '0' 106 | - action: accept 107 | chain: dstnat 108 | comment: 'Ansible - fw filter rule2' 109 | place-before: '1' 110 | - action: passthrough 111 | chain: srcnat 112 | comment: 'Ansible - fw filter rule3' 113 | place-before: '2' 114 | - action: return 115 | chain: dstnat 116 | comment: 'Ansible - fw filter rule4' 117 | place-before: '3' 118 | src-address: 192.168.0.0/16 119 | - action: redirect 120 | chain: dstnat 121 | comment: 'Ansible - fw filter rule5' 122 | place-before: '4' 123 | - action: redirect 124 | chain: dstnat 125 | comment: 'Ansible - fw filter rule20' 126 | place-before: '19' 127 | register: nat_idem 128 | failed_when: ( 129 | not ansible_check_mode 130 | ) and ( 131 | ( nat_idem | changed ) 132 | ) 133 | 134 | - name: ALWAYS_CHANGES, change rule 135 | mt_ip_firewall: 136 | hostname: "{{ mt_hostname }}" 137 | username: "{{ mt_user }}" 138 | password: "{{ mt_pass }}" 139 | state: "present" 140 | parameter: nat 141 | rule: "{{ item }}" 142 | with_items: 143 | - action: return 144 | chain: dstnat 145 | comment: 'Ansible - fw filter rule4' 146 | place-before: '3' 147 | src-address: 192.165.0.0/16 148 | register: nat_change 149 | failed_when: ( 150 | not ansible_check_mode 151 | ) and ( 152 | not ( nat_change | changed ) 153 | ) 154 | 155 | - name: ALWAYS_CHANGES, remove rule 156 | mt_ip_firewall: 157 | hostname: "{{ mt_hostname }}" 158 | username: "{{ mt_user }}" 159 | password: "{{ mt_pass }}" 160 | state: "absent" 161 | parameter: nat 162 | rule: "{{ item }}" 163 | with_items: 164 | - place-before: '4' 165 | register: nat_rem 166 | failed_when: ( 167 | not ansible_check_mode 168 | ) and ( 169 | not ( nat_rem | changed ) 170 | ) 171 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-interface-ethernet.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Add comment to ether1 3 | mt_interfaces: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | settings: "{{ item.settings }}" 8 | parameter: "ethernet" 9 | with_items: 10 | - settings: 11 | name: ether1 12 | comment: Ansible controlled ether1 13 | 14 | - name: Add comment to ether1 again (idempotency test) 15 | mt_interfaces: 16 | hostname: "{{ mt_hostname }}" 17 | username: "{{ mt_user }}" 18 | password: "{{ mt_pass }}" 19 | settings: "{{ item.settings }}" 20 | parameter: "ethernet" 21 | with_items: 22 | - settings: 23 | name: ether1 24 | comment: Ansible controlled ether1 25 | register: ether1_comment 26 | failed_when: ( 27 | not ansible_check_mode 28 | ) and ( 29 | ( ether1_comment | changed ) 30 | ) 31 | 32 | - name: USUALLY_CHANGES Modify mtu of ether2 33 | mt_interfaces: 34 | hostname: "{{ mt_hostname }}" 35 | username: "{{ mt_user }}" 36 | password: "{{ mt_pass }}" 37 | settings: "{{ item.settings }}" 38 | parameter: "ethernet" 39 | with_items: 40 | - settings: 41 | name: ether2 42 | mtu: 1500 43 | 44 | - name: ALWAYS_CHANGES Modify mtu of ether2 45 | mt_interfaces: 46 | hostname: "{{ mt_hostname }}" 47 | username: "{{ mt_user }}" 48 | password: "{{ mt_pass }}" 49 | settings: "{{ item.settings }}" 50 | parameter: "ethernet" 51 | with_items: 52 | - settings: 53 | name: ether2 54 | mtu: 1501 55 | register: ether2_mtu 56 | failed_when: ( 57 | not ansible_check_mode 58 | ) and ( 59 | not ( ether2_mtu | changed ) 60 | ) 61 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-interface-vlan.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test adding vlan 3 | mt_interfaces: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | state: present 8 | parameter: vlan 9 | settings: 10 | name: vlan_test1 11 | vlan_id: 30 12 | interface: ether1 13 | comment: Testing vlan1 14 | 15 | - name: NEVER_CHANGES Test adding duplicate vlan interface 16 | mt_interfaces: 17 | hostname: "{{ mt_hostname }}" 18 | username: "{{ mt_user }}" 19 | password: "{{ mt_pass }}" 20 | state: present 21 | parameter: vlan 22 | settings: 23 | name: vlan_test1 24 | vlan_id: 30 25 | interface: ether1 26 | register: vlan_test_1_add 27 | failed_when: ( 28 | not ansible_check_mode 29 | ) and ( 30 | ( vlan_test_1_add | changed ) 31 | ) 32 | 33 | - name: ALWAYS_CHANGES Test adding second vlan to be removed later 34 | mt_interfaces: 35 | hostname: "{{ mt_hostname }}" 36 | username: "{{ mt_user }}" 37 | password: "{{ mt_pass }}" 38 | state: present 39 | parameter: vlan 40 | settings: 41 | name: vlan_test2 42 | vlan_id: 32 43 | interface: ether2 44 | register: vlan_test_2_add 45 | failed_when: ( 46 | not ansible_check_mode 47 | ) and ( 48 | not ( vlan_test_2_add | changed ) 49 | ) 50 | 51 | - name: ALWAYS_CHANGES Test editing an existing vlan (change vlan_id) 52 | mt_interfaces: 53 | hostname: "{{ mt_hostname }}" 54 | username: "{{ mt_user }}" 55 | password: "{{ mt_pass }}" 56 | state: present 57 | parameter: vlan 58 | settings: 59 | name: vlan_test1 60 | vlan_id: 36 61 | interface: ether1 62 | comment: "testing ansible stuff" 63 | register: vlan_test_1_edit 64 | failed_when: ( 65 | not ansible_check_mode 66 | ) and ( 67 | not ( vlan_test_1_edit | changed ) 68 | ) 69 | 70 | - name: ALWAYS_CHANGES Test remove vlan 71 | mt_interfaces: 72 | hostname: "{{ mt_hostname }}" 73 | username: "{{ mt_user }}" 74 | password: "{{ mt_pass }}" 75 | state: absent 76 | parameter: vlan 77 | settings: 78 | name: vlan_test2 79 | register: vlan_test_2_rem 80 | failed_when: ( 81 | not ansible_check_mode 82 | ) and ( 83 | not ( 84 | vlan_test_2_rem | changed ) 85 | ) 86 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-interface-wireless.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: edit default security-profiles item 3 | mt_interface_wireless: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | parameter: security-profiles 8 | state: present 9 | settings: 10 | name: test1 11 | supplicant-identity: test 12 | 13 | - name: add security-profiles item 14 | mt_interface_wireless: 15 | hostname: "{{ mt_hostname }}" 16 | username: "{{ mt_user }}" 17 | password: "{{ mt_pass }}" 18 | parameter: security-profiles 19 | state: present 20 | settings: 21 | name: test1 22 | supplicant-identity: test 23 | management-protection: required 24 | 25 | - name: NEVER_CHANGES add security-profiles item, check idempotency 26 | mt_interface_wireless: 27 | hostname: "{{ mt_hostname }}" 28 | username: "{{ mt_user }}" 29 | password: "{{ mt_pass }}" 30 | parameter: security-profiles 31 | state: present 32 | settings: 33 | name: test1 34 | supplicant-identity: test 35 | register: security_prof_idem 36 | failed_when: ( 37 | not ansible_check_mode 38 | ) and ( 39 | ( security_prof_idem | changed ) 40 | ) 41 | 42 | - name: ALWAYS_CHANGES add security-profiles item, check idempotency 43 | mt_interface_wireless: 44 | hostname: "{{ mt_hostname }}" 45 | username: "{{ mt_user }}" 46 | password: "{{ mt_pass }}" 47 | parameter: security-profiles 48 | state: present 49 | settings: 50 | name: test1 51 | supplicant-identity: test 52 | management-protection: allowed 53 | register: security_prof_edit 54 | failed_when: ( 55 | not ansible_check_mode 56 | ) and ( 57 | not ( security_prof_edit | changed ) 58 | ) 59 | 60 | - name: ALWAYS_CHANGES rem security-profiles item 61 | mt_interface_wireless: 62 | hostname: "{{ mt_hostname }}" 63 | username: "{{ mt_user }}" 64 | password: "{{ mt_pass }}" 65 | parameter: security-profiles 66 | state: absent 67 | settings: 68 | name: test1 69 | register: security_prof_rem 70 | failed_when: ( 71 | not ansible_check_mode 72 | ) and ( 73 | not ( security_prof_rem | changed ) 74 | ) 75 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-ip-address.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: generate bridge interfaces for testing ip addresses 4 | mt_interfaces: 5 | hostname: "{{ mt_hostname }}" 6 | username: "{{ mt_user }}" 7 | password: "{{ mt_pass }}" 8 | state: present 9 | parameter: bridge 10 | settings: 11 | name: "{{ item }}" 12 | arp: proxy-arp 13 | with_items: 14 | - "bridge1" 15 | - "bridge2" 16 | - "bridge3" 17 | - "bridge4" 18 | 19 | - name: Remove any ip from bridge interfaces 20 | mt_ip_address: 21 | hostname: "{{ mt_hostname }}" 22 | username: "{{ mt_user }}" 23 | password: "{{ mt_pass }}" 24 | state: absent 25 | idempotent: interface 26 | settings: 27 | interface: "{{ item }}" 28 | with_items: 29 | - "bridge1" 30 | - "bridge2" 31 | - "bridge3" 32 | - "bridge4" 33 | 34 | - name: ALWAYS_CHANGES Test adding an ip addr bridge2 35 | mt_ip_address: 36 | hostname: "{{ mt_hostname }}" 37 | username: "{{ mt_user }}" 38 | password: "{{ mt_pass }}" 39 | state: "present" 40 | settings: 41 | interface: "bridge2" 42 | address: "192.168.88.2/24" 43 | network: "192.168.88.0" 44 | register: ip_addr_add_2 45 | failed_when: not ( ip_addr_add_2 | changed ) 46 | 47 | - name: NEVER_CHANGES Test adding the same ip addr again to bridge2 48 | mt_ip_address: 49 | hostname: "{{ mt_hostname }}" 50 | username: "{{ mt_user }}" 51 | password: "{{ mt_pass }}" 52 | state: "present" 53 | settings: 54 | interface: "bridge2" 55 | address: "192.168.88.2/24" 56 | network: "192.168.88.0" 57 | register: ip_addr_add_2 58 | failed_when: ip_addr_add_2 | changed 59 | 60 | - name: Test adding an ip addr with comment bridge3 61 | mt_ip_address: 62 | hostname: "{{ mt_hostname }}" 63 | username: "{{ mt_user }}" 64 | password: "{{ mt_pass }}" 65 | state: "present" 66 | settings: 67 | interface: "bridge3" 68 | address: "192.168.88.3/24" 69 | comment: "bridge #3!!!" 70 | 71 | - name: Test adding an ip addr with comment and network bridge4 72 | mt_ip_address: 73 | hostname: "{{ mt_hostname }}" 74 | username: "{{ mt_user }}" 75 | password: "{{ mt_pass }}" 76 | state: "present" 77 | settings: 78 | interface: "bridge4" 79 | address: "192.168.88.4/24" 80 | network: "192.168.88.0" 81 | comment: "bridge4 first addition" 82 | 83 | - name: Test adding a second ip addr with comment and network bridge4 84 | mt_ip_address: 85 | hostname: "{{ mt_hostname }}" 86 | username: "{{ mt_user }}" 87 | password: "{{ mt_pass }}" 88 | state: "present" 89 | settings: 90 | interface: "bridge4" 91 | address: "192.168.88.5/24" 92 | network: "192.168.88.0" 93 | comment: "bridge #4 second addition" 94 | 95 | - name: ALWAYS_CHANGES Test removing ip addr bridge2 96 | mt_ip_address: 97 | hostname: "{{ mt_hostname }}" 98 | username: "{{ mt_user }}" 99 | password: "{{ mt_pass }}" 100 | state: "absent" 101 | settings: 102 | interface: "bridge2" 103 | address: "192.168.88.2/24" 104 | register: ip_addr_rem_2 105 | failed_when: not ( ip_addr_rem_2 | changed ) 106 | 107 | - name: NEVER_CHANGES Test removing ip addr bridge2 again 108 | mt_ip_address: 109 | hostname: "{{ mt_hostname }}" 110 | username: "{{ mt_user }}" 111 | password: "{{ mt_pass }}" 112 | state: "absent" 113 | settings: 114 | interface: "bridge2" 115 | address: "192.168.88.2/24" 116 | register: ip_addr_rem_2 117 | failed_when: ip_addr_rem_2 | changed 118 | 119 | - name: ALWAYS_CHANGES Test removing an ip addr from bridge4 120 | mt_ip_address: 121 | hostname: "{{ mt_hostname }}" 122 | username: "{{ mt_user }}" 123 | password: "{{ mt_pass }}" 124 | state: "absent" 125 | settings: 126 | interface: "bridge4" 127 | address: "192.168.88.4/24" 128 | register: ip_addr_rem_4 129 | failed_when: not (ip_addr_rem_4 | changed ) 130 | 131 | - name: NEVER_CHANGES Verify ip addr from bridge4 has been removed 132 | mt_ip_address: 133 | hostname: "{{ mt_hostname }}" 134 | username: "{{ mt_user }}" 135 | password: "{{ mt_pass }}" 136 | state: "absent" 137 | settings: 138 | interface: "bridge4" 139 | address: "192.168.88.4/24" 140 | register: ip_addr_rem_4 141 | failed_when: ip_addr_rem_4 | changed 142 | 143 | - name: NEVER_CHANGES Verify that we have the right ip address in bridge4 144 | mt_ip_address: 145 | hostname: "{{ mt_hostname }}" 146 | username: "{{ mt_user }}" 147 | password: "{{ mt_pass }}" 148 | state: "present" 149 | settings: 150 | interface: "bridge4" 151 | address: "192.168.88.5/24" 152 | register: ip_addr_rem_4 153 | failed_when: ip_addr_rem_4 | changed 154 | 155 | - name: Remove any IP from bridge interfaces 156 | mt_ip_address: 157 | hostname: "{{ mt_hostname }}" 158 | username: "{{ mt_user }}" 159 | password: "{{ mt_pass }}" 160 | state: absent 161 | idempotent: interface 162 | settings: 163 | interface: "{{ item }}" 164 | with_items: 165 | - "bridge1" 166 | - "bridge2" 167 | - "bridge3" 168 | - "bridge4" 169 | 170 | - name: ALWAYS_CHANGES Add ip using idempotent interface on bridge2 171 | mt_ip_address: 172 | hostname: "{{ mt_hostname }}" 173 | username: "{{ mt_user }}" 174 | password: "{{ mt_pass }}" 175 | state: "present" 176 | idempotent: "interface" 177 | settings: 178 | interface: "bridge2" 179 | address: "192.168.89.2/24" 180 | register: ip_addr_rem_2 181 | failed_when: not (ip_addr_rem_2 | changed) 182 | 183 | - name: NEVER_CHANGES Add AGAIN ip using idempotent interface on bridge2 184 | mt_ip_address: 185 | hostname: "{{ mt_hostname }}" 186 | username: "{{ mt_user }}" 187 | password: "{{ mt_pass }}" 188 | state: "present" 189 | idempotent: "interface" 190 | settings: 191 | interface: "bridge2" 192 | address: "192.168.89.2/24" 193 | register: ip_addr_rem_2 194 | failed_when: ip_addr_rem_2 | changed 195 | 196 | - name: ALWAYS_CHANGES Add ip using idempotent interface on bridge3 197 | mt_ip_address: 198 | hostname: "{{ mt_hostname }}" 199 | username: "{{ mt_user }}" 200 | password: "{{ mt_pass }}" 201 | state: "present" 202 | idempotent: "interface" 203 | settings: 204 | interface: "bridge3" 205 | address: "192.168.89.3/24" 206 | register: ip_addr_rem_3 207 | failed_when: not (ip_addr_rem_3 | changed) 208 | 209 | - name: ALWAYS_CHANGES Remove ip using idempotent interface on bridge3 210 | mt_ip_address: 211 | hostname: "{{ mt_hostname }}" 212 | username: "{{ mt_user }}" 213 | password: "{{ mt_pass }}" 214 | state: "absent" 215 | idempotent: "interface" 216 | settings: 217 | interface: "bridge3" 218 | register: ip_addr_rem_3 219 | failed_when: not (ip_addr_rem_3 | changed) 220 | 221 | - name: NEVER_CHANGES Remove ip AGAIN using idempotent interface on bridge3 222 | mt_ip_address: 223 | hostname: "{{ mt_hostname }}" 224 | username: "{{ mt_user }}" 225 | password: "{{ mt_pass }}" 226 | state: "absent" 227 | idempotent: "interface" 228 | settings: 229 | interface: "bridge3" 230 | register: ip_addr_rem_3 231 | failed_when: ip_addr_rem_3 | changed 232 | 233 | - name: ALWAYS_CHANGES Add ip using idempotent interface on bridge4 234 | mt_ip_address: 235 | hostname: "{{ mt_hostname }}" 236 | username: "{{ mt_user }}" 237 | password: "{{ mt_pass }}" 238 | state: "present" 239 | idempotent: "interface" 240 | settings: 241 | interface: "bridge4" 242 | address: "192.168.89.4/24" 243 | register: ip_addr_rem_4 244 | failed_when: not (ip_addr_rem_4 | changed) 245 | 246 | - name: ALWAYS_CHANGES Edit ip using idempotent interface on bridge4 247 | mt_ip_address: 248 | hostname: "{{ mt_hostname }}" 249 | username: "{{ mt_user }}" 250 | password: "{{ mt_pass }}" 251 | state: "present" 252 | idempotent: "interface" 253 | settings: 254 | interface: "bridge4" 255 | address: "192.168.89.5/24" 256 | register: ip_addr_rem_4 257 | failed_when: not (ip_addr_rem_4 | changed) 258 | 259 | - name: ALWAYS_CHANGES Add comment using idempotent interface on bridge4 260 | mt_ip_address: 261 | hostname: "{{ mt_hostname }}" 262 | username: "{{ mt_user }}" 263 | password: "{{ mt_pass }}" 264 | state: "present" 265 | idempotent: "interface" 266 | settings: 267 | interface: "bridge4" 268 | comment: "This is a final comment" 269 | register: ip_addr_rem_4 270 | failed_when: not (ip_addr_rem_4 | changed) 271 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-ip-pool.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test adding ip pool 3 | mt_ip: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | state: present 8 | parameter: pool 9 | settings: 10 | name: ansible_test 11 | ranges: 102.3.4.5 12 | 13 | - name: NEVER_CHANGES Test adding duplicate ip pool 14 | mt_ip: 15 | hostname: "{{ mt_hostname }}" 16 | username: "{{ mt_user }}" 17 | password: "{{ mt_pass }}" 18 | state: present 19 | parameter: pool 20 | settings: 21 | name: ansible_test 22 | ranges: 102.3.4.5 23 | register: ip_pool_test_1_add 24 | failed_when: ( 25 | not ansible_check_mode 26 | ) and ( 27 | ( ip_pool_test_1_add | changed ) 28 | ) 29 | 30 | - name: ALWAYS_CHANGES Test adding second ip pool to be removed later 31 | mt_ip: 32 | hostname: "{{ mt_hostname }}" 33 | username: "{{ mt_user }}" 34 | password: "{{ mt_pass }}" 35 | state: present 36 | parameter: pool 37 | settings: 38 | name: ansible_test2 39 | ranges: 102.3.4.22 40 | register: ip_pool_test_2_add 41 | failed_when: ( 42 | not ansible_check_mode 43 | ) and ( 44 | not ( ip_pool_test_2_add | changed ) 45 | ) 46 | 47 | - name: Test adding ip pool to be used as next_pool 48 | mt_ip: 49 | hostname: "{{ mt_hostname }}" 50 | username: "{{ mt_user }}" 51 | password: "{{ mt_pass }}" 52 | state: present 53 | parameter: pool 54 | settings: 55 | name: next_pool1 56 | ranges: 10.1.2.30-10.2.3.40 57 | 58 | - name: ALWAYS_CHANGES Test editing an existing ip-pool item (change ranges add next_pool) 59 | mt_ip: 60 | hostname: "{{ mt_hostname }}" 61 | username: "{{ mt_user }}" 62 | password: "{{ mt_pass }}" 63 | state: present 64 | parameter: pool 65 | settings: 66 | name: ansible_test 67 | ranges: 102.3.4.6 68 | next_pool: next_pool1 69 | register: ip_pool_test_1_edit 70 | failed_when: ( 71 | not ansible_check_mode 72 | ) and ( 73 | not ( ip_pool_test_1_edit | changed ) 74 | ) 75 | 76 | - name: ALWAYS_CHANGES Test remove ip pool 77 | mt_ip: 78 | hostname: "{{ mt_hostname }}" 79 | username: "{{ mt_user }}" 80 | password: "{{ mt_pass }}" 81 | state: absent 82 | parameter: pool 83 | settings: 84 | name: ansible_test2 85 | register: ip_pool_test_2_rem 86 | failed_when: ( 87 | not ansible_check_mode 88 | ) and ( 89 | not ( ip_pool_test_2_rem | changed ) 90 | ) 91 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-neighbor.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: edit a interface discovery option 3 | mt_neighbor: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | parameter: discovery 8 | settings: 9 | name: ether2 10 | discover: "no" 11 | 12 | - name: NEVER_CHANGES edit a interface discovery option 13 | mt_neighbor: 14 | hostname: "{{ mt_hostname }}" 15 | username: "{{ mt_user }}" 16 | password: "{{ mt_pass }}" 17 | parameter: discovery 18 | settings: 19 | name: ether2 20 | discover: "no" 21 | register: discovery_edit 22 | failed_when: ( 23 | not ansible_check_mode 24 | ) and ( 25 | ( discovery_edit | changed ) 26 | ) 27 | 28 | - name: ALWAYS_CHANGES edit a interface discovery option 29 | mt_neighbor: 30 | hostname: "{{ mt_hostname }}" 31 | username: "{{ mt_user }}" 32 | password: "{{ mt_pass }}" 33 | parameter: discovery 34 | settings: 35 | name: ether2 36 | discover: "yes" 37 | register: discovery_edit 38 | failed_when: ( 39 | not ansible_check_mode 40 | ) and ( 41 | not ( discovery_edit | changed ) 42 | ) 43 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-ovpn-client.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test adding ovpn-client 3 | mt_interfaces: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | state: present 8 | parameter: ovpn-client 9 | settings: 10 | comment: "ansible test 1" 11 | user: ansible_admin 12 | connect-to: 192.168.50.170 13 | name: ansible_test 14 | password: 'blablabla' 15 | tags: vpn-client-test 16 | 17 | - name: NEVER_CHANGES Test adding duplicate ovpn-client 18 | mt_interfaces: 19 | hostname: "{{ mt_hostname }}" 20 | username: "{{ mt_user }}" 21 | password: "{{ mt_pass }}" 22 | state: present 23 | parameter: ovpn-client 24 | settings: 25 | comment: "ansible test 1" 26 | user: ansible_admin 27 | connect-to: 192.168.50.170 28 | name: ansible_test 29 | password: 'blablabla' 30 | register: ovpn_client_test_1_add 31 | failed_when: ( 32 | not ansible_check_mode 33 | ) and ( 34 | ( ovpn_client_test_1_add | changed ) 35 | ) 36 | tags: vpn-client-test 37 | 38 | - name: ALWAYS_CHANGES Test editing an existing ovpn-client item (change address) 39 | mt_interfaces: 40 | hostname: "{{ mt_hostname }}" 41 | username: "{{ mt_user }}" 42 | password: "{{ mt_pass }}" 43 | state: present 44 | parameter: ovpn-client 45 | settings: 46 | comment: "ansible test 1" 47 | user: ansible_admin 48 | connect-to: 192.168.50.171 49 | auth: "null" 50 | name: ansible_test 51 | password: 'bar' 52 | register: ovpn_client_test_1_edit 53 | failed_when: ( 54 | not ansible_check_mode 55 | ) and ( 56 | not ( ovpn_client_test_1_edit | changed ) 57 | ) 58 | tags: vpn-client-test 59 | 60 | - name: ALWAYS_CHANGES Test adding a second ovpn-client to later remove 61 | mt_interfaces: 62 | hostname: "{{ mt_hostname }}" 63 | username: "{{ mt_user }}" 64 | password: "{{ mt_pass }}" 65 | state: present 66 | parameter: ovpn-client 67 | settings: 68 | user: ansible_admin 69 | comment: "ansible test 2" 70 | connect_to: 192.168.52.111 71 | name: ansible_test2 72 | register: ovpn_client_test_2_add 73 | failed_when: ( 74 | not ansible_check_mode 75 | ) and ( 76 | not ( ovpn_client_test_2_add | changed ) 77 | ) 78 | 79 | - name: ALWAYS_CHANGES Test remove ovpn-client 80 | mt_interfaces: 81 | hostname: "{{ mt_hostname }}" 82 | username: "{{ mt_user }}" 83 | password: "{{ mt_pass }}" 84 | state: absent 85 | parameter: ovpn-client 86 | settings: 87 | name: ansible_test2 88 | register: ovpn_client_test_2_rem 89 | failed_when: ( 90 | not ansible_check_mode 91 | ) and ( 92 | not ( ovpn_client_test_2_rem | changed ) 93 | ) 94 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-scheduler.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: add scheduler 3 | mt_system_scheduler: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | state: present 8 | parameter: scheduler 9 | settings: 10 | name: ansible_test 11 | on_event: 'put "test"' 12 | interval: 1s 13 | tags: test1 14 | 15 | - name: ALWAYS_CHANGES modify existing scheduler 16 | mt_system_scheduler: 17 | hostname: "{{ mt_hostname }}" 18 | username: "{{ mt_user }}" 19 | password: "{{ mt_pass }}" 20 | state: present 21 | parameter: scheduler 22 | settings: 23 | name: ansible_test 24 | on_event: 'put "test"' 25 | interval: 5s 26 | policy: 27 | - password 28 | - sniff 29 | - write 30 | register: scheduler_mod 31 | failed_when: ( 32 | not ansible_check_mode 33 | ) and ( 34 | not ( scheduler_mod | changed ) 35 | ) 36 | tags: test1 37 | 38 | - name: NEVER_CHANGES add duplicate scheduler 39 | mt_system_scheduler: 40 | hostname: "{{ mt_hostname }}" 41 | username: "{{ mt_user }}" 42 | password: "{{ mt_pass }}" 43 | state: present 44 | parameter: scheduler 45 | settings: 46 | name: ansible_test 47 | on_event: 'put "test"' 48 | interval: 5s 49 | policy: 50 | - password 51 | - sniff 52 | - write 53 | register: scheduler_dup 54 | failed_when: ( 55 | not ansible_check_mode 56 | ) and ( 57 | ( scheduler_dup | changed ) 58 | ) 59 | tags: test1 60 | 61 | - name: ALWAYS_CHANGES remove duplicate scheduler 62 | mt_system_scheduler: 63 | hostname: "{{ mt_hostname }}" 64 | username: "{{ mt_user }}" 65 | password: "{{ mt_pass }}" 66 | state: absent 67 | parameter: scheduler 68 | settings: 69 | name: ansible_test 70 | register: scheduler_rem 71 | failed_when: ( 72 | not ansible_check_mode 73 | ) and ( 74 | not ( scheduler_rem | changed ) 75 | ) 76 | tags: test1 77 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-service.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test enabling ftp service 3 | mt_ip: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | parameter: service 8 | settings: 9 | disabled: "no" 10 | name: ftp 11 | address: 192.168.50.1/32 12 | 13 | - name: Test disabling services 14 | mt_ip: 15 | hostname: "{{ mt_hostname }}" 16 | username: "{{ mt_user }}" 17 | password: "{{ mt_pass }}" 18 | parameter: service 19 | settings: 20 | disabled: "yes" 21 | name: "{{ item }}" 22 | with_items: 23 | - ftp 24 | - telnet 25 | - api-ssl 26 | 27 | - name: ALWAYS_CHANGES Test re-enabling telnet service 28 | mt_ip: 29 | hostname: "{{ mt_hostname }}" 30 | username: "{{ mt_user }}" 31 | password: "{{ mt_pass }}" 32 | parameter: service 33 | settings: 34 | disabled: "no" 35 | name: telnet 36 | register: enable_telnet 37 | failed_when: ( 38 | not ansible_check_mode 39 | ) and ( 40 | not ( enable_telnet | changed ) 41 | ) 42 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-snmp.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: add snmp community 3 | mt_snmp: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | state: present 8 | parameter: community 9 | settings: 10 | addresses: "0.0.0.0/0" 11 | name: icghol 12 | 13 | - name: add second snmp community to remove later 14 | mt_snmp: 15 | hostname: "{{ mt_hostname }}" 16 | username: "{{ mt_user }}" 17 | password: "{{ mt_pass }}" 18 | state: present 19 | parameter: community 20 | settings: 21 | addresses: "192.168.1.0/24" 22 | name: to_remove 23 | 24 | - name: ALWAYS_CHANGES remove second snmp community 25 | mt_snmp: 26 | hostname: "{{ mt_hostname }}" 27 | username: "{{ mt_user }}" 28 | password: "{{ mt_pass }}" 29 | state: absent 30 | parameter: community 31 | settings: 32 | name: to_remove 33 | register: snmp_community_rem 34 | failed_when: ( 35 | not ansible_check_mode 36 | ) and ( 37 | not ( snmp_community_rem | changed ) 38 | ) 39 | 40 | - name: ALWAYS_CHANGES modify existing snmp community 41 | mt_snmp: 42 | hostname: "{{ mt_hostname }}" 43 | username: "{{ mt_user }}" 44 | password: "{{ mt_pass }}" 45 | state: present 46 | parameter: community 47 | settings: 48 | addresses: "10.0.0.0/8" 49 | name: icghol 50 | register: snmp_community 51 | failed_when: ( 52 | not ansible_check_mode 53 | ) and ( 54 | not ( snmp_community | changed ) 55 | ) 56 | 57 | - name: NEVER_CHANGES check idempotency on snmp community 58 | mt_snmp: 59 | hostname: "{{ mt_hostname }}" 60 | username: "{{ mt_user }}" 61 | password: "{{ mt_pass }}" 62 | state: present 63 | parameter: community 64 | settings: 65 | addresses: "10.0.0.0/8" 66 | name: icghol 67 | register: snmp_community_idem 68 | failed_when: ( 69 | not ansible_check_mode 70 | ) and ( 71 | ( snmp_community_idem | changed ) 72 | ) 73 | 74 | - name: edit snmp settings 75 | mt_snmp: 76 | hostname: "{{ mt_hostname }}" 77 | username: "{{ mt_user }}" 78 | password: "{{ mt_pass }}" 79 | parameter: snmp 80 | settings: 81 | enabled: "yes" 82 | trap-community: icghol 83 | trap-version: 2 84 | 85 | - name: NEVER_CHANGES edit snmp settings again check idempotency 86 | mt_snmp: 87 | hostname: "{{ mt_hostname }}" 88 | username: "{{ mt_user }}" 89 | password: "{{ mt_pass }}" 90 | parameter: snmp 91 | settings: 92 | enabled: "yes" 93 | trap-community: icghol 94 | trap-version: 2 95 | register: snmp_idem 96 | failed_when: ( 97 | not ansible_check_mode 98 | ) and ( 99 | ( snmp_idem | changed ) 100 | ) 101 | 102 | - name: ALWAYS_CHANGES check editing snmp 103 | mt_snmp: 104 | hostname: "{{ mt_hostname }}" 105 | username: "{{ mt_user }}" 106 | password: "{{ mt_pass }}" 107 | parameter: snmp 108 | settings: 109 | enabled: "yes" 110 | trap-community: icghol 111 | trap-version: 3 112 | register: snmp_edit 113 | failed_when: ( 114 | not ansible_check_mode 115 | ) and ( 116 | not ( snmp_edit | changed ) 117 | ) 118 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-system.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: set identity 3 | mt_system: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | parameter: identity 8 | settings: 9 | name: Test_mikrotik 10 | 11 | - name: check if physical hardware 12 | mt_command: 13 | hostname: "{{ mt_hostname }}" 14 | username: "{{ mt_user }}" 15 | password: "{{ mt_pass }}" 16 | command: /system/routerboard/print 17 | register: routerboard 18 | tags: routerboard 19 | 20 | - name: set routerboard settings on physical device 21 | mt_system: 22 | hostname: "{{ mt_hostname }}" 23 | username: "{{ mt_user }}" 24 | password: "{{ mt_pass }}" 25 | parameter: routerboard_settings 26 | settings: 27 | protected-routerboot: disabled 28 | boot-protocol: dhcp 29 | when: routerboard['msg'][0][0][1]['routerboard'] != "false" 30 | 31 | - name: set clock 32 | mt_system: 33 | hostname: "{{ mt_hostname }}" 34 | username: "{{ mt_user }}" 35 | password: "{{ mt_pass }}" 36 | parameter: clock 37 | settings: 38 | time-zone-autodetect: "no" 39 | time-zone-name: Greenwich 40 | 41 | - name: ALWAYS_CHANGES modify clock, change time-zone-name 42 | mt_system: 43 | hostname: "{{ mt_hostname }}" 44 | username: "{{ mt_user }}" 45 | password: "{{ mt_pass }}" 46 | parameter: clock 47 | settings: 48 | time-zone-name: GMT 49 | register: mt_clock 50 | failed_when: ( 51 | not ansible_check_mode 52 | ) and ( 53 | not ( mt_clock | changed ) 54 | ) 55 | 56 | - name: set ntp client 57 | mt_system: 58 | hostname: "{{ mt_hostname }}" 59 | username: "{{ mt_user }}" 60 | password: "{{ mt_pass }}" 61 | parameter: ntp_client 62 | settings: 63 | enabled: "yes" 64 | primary-ntp: 199.182.221.11 65 | secondary-ntp: 67.215.197.149 66 | 67 | - name: NEVER_CHANGES set ntp client, check idempotency 68 | mt_system: 69 | hostname: "{{ mt_hostname }}" 70 | username: "{{ mt_user }}" 71 | password: "{{ mt_pass }}" 72 | parameter: ntp_client 73 | settings: 74 | enabled: "yes" 75 | primary-ntp: 199.182.221.11 76 | secondary-ntp: 67.215.197.149 77 | register: mt_ntp_client 78 | failed_when: ( 79 | not ansible_check_mode 80 | ) and ( 81 | ( mt_ntp_client | changed ) 82 | ) 83 | 84 | - name: ALWAYS_CHANGES modify ntp client 85 | mt_system: 86 | hostname: "{{ mt_hostname }}" 87 | username: "{{ mt_user }}" 88 | password: "{{ mt_pass }}" 89 | parameter: ntp_client 90 | settings: 91 | enabled: "no" 92 | primary-ntp: 199.182.221.11 93 | secondary-ntp: 67.215.197.149 94 | register: mt_ntp_client_change 95 | failed_when: ( 96 | not ansible_check_mode 97 | ) and ( 98 | not ( mt_ntp_client_change | changed ) 99 | ) 100 | 101 | ############################################## 102 | # WIP 103 | ############################################### 104 | - name: modify logging 105 | mt_system: 106 | hostname: "{{ mt_hostname }}" 107 | username: "{{ mt_user }}" 108 | password: "{{ mt_pass }}" 109 | parameter: logging 110 | settings: "{{ item }}" 111 | with_items: 112 | - numbers: "0" 113 | action: disk 114 | disabled: "yes" 115 | - numbers: "1" 116 | action: memory 117 | disabled: "yes" 118 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-tool.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: set email settings 3 | mt_tool: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | parameter: e-mail 8 | settings: 9 | address: 192.168.1.2 10 | from: email@localhost.com 11 | 12 | - name: ALWAYS_CHANGES set email settings 13 | mt_tool: 14 | hostname: "{{ mt_hostname }}" 15 | username: "{{ mt_user }}" 16 | password: "{{ mt_pass }}" 17 | parameter: e-mail 18 | settings: 19 | address: 192.168.1.3 20 | from: email@localhost.com 21 | register: email_edit 22 | failed_when: ( 23 | not ansible_check_mode 24 | ) and ( 25 | not ( email_edit | changed ) 26 | ) 27 | 28 | - name: add netwatch item 29 | mt_tool: 30 | hostname: "{{ mt_hostname }}" 31 | username: "{{ mt_user }}" 32 | password: "{{ mt_pass }}" 33 | parameter: netwatch 34 | state: present 35 | settings: 36 | host: '192.168.10.1' 37 | up-script: test 38 | 39 | - name: NEVER_CHANGES add netwatch item, idempotency check 40 | mt_tool: 41 | hostname: "{{ mt_hostname }}" 42 | username: "{{ mt_user }}" 43 | password: "{{ mt_pass }}" 44 | parameter: netwatch 45 | state: present 46 | settings: 47 | host: '192.168.10.1' 48 | up-script: test 49 | register: netwatch_idem 50 | failed_when: ( 51 | not ansible_check_mode 52 | ) and ( 53 | ( netwatch_idem | changed ) 54 | ) 55 | 56 | - name: ALWAYS_CHANGES edit netwatch item, change up-script 57 | mt_tool: 58 | hostname: "{{ mt_hostname }}" 59 | username: "{{ mt_user }}" 60 | password: "{{ mt_pass }}" 61 | parameter: netwatch 62 | state: present 63 | settings: 64 | host: '192.168.10.1' 65 | up-script: test2 66 | register: netwatch_edit 67 | failed_when: ( 68 | not ansible_check_mode 69 | ) and ( 70 | not ( netwatch_edit | changed ) 71 | ) 72 | 73 | - name: ALWAYS_CHANGES remove netwatch item 74 | mt_tool: 75 | hostname: "{{ mt_hostname }}" 76 | username: "{{ mt_user }}" 77 | password: "{{ mt_pass }}" 78 | parameter: netwatch 79 | state: absent 80 | settings: 81 | host: '192.168.10.1' 82 | register: netwatch_rem 83 | failed_when: ( 84 | not ansible_check_mode 85 | ) and ( 86 | not ( netwatch_rem | changed ) 87 | ) 88 | -------------------------------------------------------------------------------- /tests/integration/tasks/test-user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: add a group 3 | mt_command: 4 | hostname: "{{ mt_hostname }}" 5 | username: "{{ mt_user }}" 6 | password: "{{ mt_pass }}" 7 | command: /user/group/add 8 | command_arguments: 9 | name: group_test1 10 | policy: read,write,web,!local,!telnet,!ssh 11 | comment: ansible_test 12 | failed_when: false 13 | 14 | - name: edit group 15 | mt_command: 16 | hostname: "{{ mt_hostname }}" 17 | username: "{{ mt_user }}" 18 | password: "{{ mt_pass }}" 19 | command: /user/group/set 20 | command_arguments: 21 | numbers: 3 22 | name: group_test1 23 | comment: ansible_test2 24 | 25 | - name: edit group 26 | mt_command: 27 | hostname: "{{ mt_hostname }}" 28 | username: "{{ mt_user }}" 29 | password: "{{ mt_pass }}" 30 | command: /user/group/set 31 | command_arguments: 32 | name: group_test1 33 | policy: read,write,web,winbox 34 | 35 | - name: add a test user to mikrotik 36 | mt_user: 37 | hostname: "{{ mt_hostname }}" 38 | username: "{{ mt_user }}" 39 | password: "{{ mt_pass }}" 40 | parameter: user 41 | state: present 42 | settings: 43 | name: user_test1 44 | group: read 45 | password: 123 46 | changed_when: False 47 | 48 | - name: NEVER_CHANGES, check idempotency add a user 49 | mt_user: 50 | hostname: "{{ mt_hostname }}" 51 | username: "{{ mt_user }}" 52 | password: "{{ mt_pass }}" 53 | parameter: user 54 | state: present 55 | settings: 56 | name: user_test1 57 | group: read 58 | register: user_add 59 | failed_when: ( 60 | not ansible_check_mode 61 | ) and ( 62 | ( user_add | changed ) 63 | ) 64 | 65 | - name: ALWAYS_CHANGES modify user 66 | mt_user: 67 | hostname: "{{ mt_hostname }}" 68 | username: "{{ mt_user }}" 69 | password: "{{ mt_pass }}" 70 | parameter: user 71 | state: present 72 | settings: 73 | name: user_test1 74 | group: group_test1 75 | register: user_edit 76 | failed_when: ( 77 | not ansible_check_mode 78 | ) and ( 79 | not ( user_edit | changed ) 80 | ) 81 | 82 | - name: ALWAYS_CHANGES remove user 83 | mt_user: 84 | hostname: "{{ mt_hostname }}" 85 | username: "{{ mt_user }}" 86 | password: "{{ mt_pass }}" 87 | parameter: user 88 | state: absent 89 | settings: 90 | name: user_test1 91 | register: user_rem 92 | failed_when: ( 93 | not ansible_check_mode 94 | ) and ( 95 | not ( user_rem | changed ) 96 | ) 97 | -------------------------------------------------------------------------------- /tests/integration/tests.retry: -------------------------------------------------------------------------------- 1 | 127.0.0.1 2 | -------------------------------------------------------------------------------- /tests/integration/tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Tests 3 | hosts: all 4 | gather_facts: no 5 | connection: local 6 | vars: 7 | # these should be defined at the inventory level 8 | # but must be here until the action plugin is working 9 | mt_hostname: '127.0.0.1' 10 | mt_user: 'admin' 11 | mt_pass: '' 12 | 13 | tasks: 14 | - name: Test login 15 | mt_login_test: 16 | hostname: "{{ mt_hostname }}" 17 | username: "{{ mt_user }}" 18 | password: "{{ mt_pass }}" 19 | tags: test_login 20 | 21 | ############################### 22 | # Interfaces 23 | ################################# 24 | - block: 25 | ################### 26 | ### vlan block 27 | ################### 28 | - include: tasks/test-interface-vlan.yml 29 | tags: interfaces-vlan 30 | 31 | ######################## 32 | ### ethernet block 33 | ######################## 34 | - include: tasks/test-interface-ethernet.yml 35 | tags: interfaces-ethernet 36 | 37 | tags: interfaces 38 | 39 | ################### 40 | ### ip-pool 41 | ################### 42 | - include: tasks/test-ip-pool.yml 43 | tags: ip-pool 44 | 45 | ################## 46 | ### dhcp-server 47 | ################### 48 | - include: tasks/test-dhcp-server.yml 49 | tags: dhcp-server 50 | 51 | ################### 52 | ### ovpn-client 53 | ################### 54 | - include: tasks/test-ovpn-client.yml 55 | tags: ovpn-client 56 | 57 | ################### 58 | ### radius 59 | ################### 60 | - include: tasks/radius-tests.yml 61 | tags: radius 62 | 63 | ################### 64 | ### address-list 65 | ################### 66 | - include: tasks/test-address-list.yml 67 | tags: address-list 68 | 69 | ################### 70 | ### ip_address 71 | ################### 72 | - include: tasks/test-ip-address.yml 73 | tags: ip-address 74 | 75 | ################### 76 | ### firewall block 77 | ################### 78 | - block: 79 | # filter block 80 | - include: tasks/test-firewall-filter.yml 81 | tags: firewall-filter 82 | 83 | # nat 84 | - include: tasks/test-firewall-nat.yml 85 | tags: firewall-nat 86 | 87 | tags: firewall 88 | 89 | ################### 90 | ### end firewall block 91 | ################### 92 | 93 | ################### 94 | ### ip service 95 | ################### 96 | - include: tasks/test-service.yml 97 | tags: service 98 | 99 | ################### 100 | ### interface bridge 101 | ################### 102 | - include: tasks/test-bridge.yml 103 | tags: bridge 104 | 105 | ########################### 106 | ### system scheduler 107 | ########################## 108 | 109 | - include: tasks/test-scheduler.yml 110 | tags: scheduler 111 | 112 | ########################### 113 | ### system command 114 | ########################## 115 | - include: tasks/test-command.yml 116 | tags: command 117 | 118 | ################### 119 | ### system 120 | ################### 121 | - include: tasks/test-system.yml 122 | tags: system 123 | 124 | ################### 125 | ### tool 126 | ################### 127 | - include: tasks/test-tool.yml 128 | tags: tool 129 | 130 | ################### 131 | ### snmp 132 | ################### 133 | - include: tasks/test-snmp.yml 134 | tags: snmp 135 | 136 | ################### 137 | ### hotspot 138 | ################### 139 | - include: tasks/hotspot-tests.yml 140 | tags: hotspot 141 | 142 | ################### 143 | ### neighbor 144 | ################### 145 | - include: tasks/test-neighbor.yml 146 | tags: neighbor 147 | 148 | ################### 149 | ### user 150 | ################### 151 | - include: tasks/test-user.yml 152 | tags: user 153 | ################### 154 | ### interface wireless 155 | ################### 156 | - include: tasks/test-interface-wireless.yml 157 | tags: interface-wireless 158 | 159 | - include: tasks/test-facts.yml 160 | tags: get-facts 161 | --------------------------------------------------------------------------------