diff --git a/library/mt_command.py b/library/mt_command.py new file mode 100644 index 0000000..6dc4d7f --- /dev/null +++ b/library/mt_command.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_command +author: + - "Valentin Gurmeza" +version_added: "2.3" +short_description: Issue mikrotik command +requirements: + - mt_api +description: + - Issue a mikrotik command +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + command: + description: + - command to be sent to the router. The command must be a command path using + - '/' for word separation + required: True + command_arguments: + description: + - parameters to pass with the command. Must be a dictionary +''' + +EXAMPLES = ''' +- mt_command: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + name: /system/backup/save + command_arguments: + name: ansible_test + password: 123 +''' + +import mt_api +from mt_common import clean_params +from ansible.module_utils.basic import AnsibleModule + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname=dict(required=True), + username=dict(required=True), + password=dict(required=True), + command=dict(required=True, type='str'), + command_arguments=dict(required=False, type='dict'), + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + changed = False + changed_message = [] + + mk = mt_api.Mikrotik(hostname, username, password) + try: + mk.login() + except: + module.fail_json( + msg="Could not log into Mikrotik device." + + " Check the username and password.", + ) + + api_path = module.params['command'] + + if module.params['command_arguments'] != None: + response = mk.api_command(base_path=api_path, params=module.params['command_arguments']) + else: + response = mk.api_command(base_path=api_path) + + if response[-1][0] == '!done': + changed = True + changed_message.append(response) + changed_message.append(api_path) + if module.params['command_arguments'] != None: + changed_message.append(module.params['command_arguments']) + + if changed: + module.exit_json( + failed=False, + changed=True, + msg=changed_message + ) + else: + module.exit_json( + failed=False, + changed=False, + msg="Command failed" + ) +if __name__ == '__main__': + main() diff --git a/library/mt_dhcp_server.py b/library/mt_dhcp_server.py new file mode 100644 index 0000000..a28eaa2 --- /dev/null +++ b/library/mt_dhcp_server.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_dhcp_server.py +author: + - "Valentin Gurmeza" +version_added: "2.4" +short_description: Manage mikrotik dhcp-server endpoints +requirements: + - mt_api +description: + - Mikrotik dhcp-server generic module +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + parameter: + description: + - sub endpoint for mikrotik tool + required: True + options: + - netwatch + - e-mail + settings: + description: + - All Mikrotik compatible parameters for this particular endpoint. + Any yes/no values must be enclosed in double quotes + state: + description: + - absent or present +''' + +EXAMPLES = ''' +- mt_dhcp_server: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + parameter: network + settings: + address: 192.168.1.0/24 + dns: 192.168.1.20 +''' + +from mt_common import clean_params, MikrotikIdempotent +from ansible.module_utils.basic import AnsibleModule + + + +def main(): + module = AnsibleModule( + argument_spec = dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + settings = dict(required=False, type='dict'), + parameter = dict( + required = True, + choices = ['network', 'option', 'dhcp-server'], + type = 'str' + ), + state = dict( + required = False, + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + idempotent_parameter = None + params = module.params + + if params['parameter'] == 'network': + idempotent_parameter = 'address' + params['parameter'] = "dhcp-server/network" + + if params['parameter'] == 'option': + idempotent_parameter = 'name' + params['parameter'] = "dhcp-server/option" + + if params['parameter'] == 'dhcp-server': + idempotent_parameter = 'name' + + mt_obj = MikrotikIdempotent( + hostname = params['hostname'], + username = params['username'], + password = params['password'], + state = params['state'], + desired_params = params['settings'], + idempotent_param = idempotent_parameter, + api_path = '/ip/' + str(params['parameter']), + + ) + + mt_obj.sync_state() + + if mt_obj.failed: + module.fail_json( + msg = mt_obj.failed_msg + ) + elif mt_obj.changed: + module.exit_json( + failed=False, + changed=True, + msg=mt_obj.changed_msg, + diff={ "prepared": { + "old": mt_obj.old_params, + "new": mt_obj.new_params, + }}, + ) + else: + module.exit_json( + failed=False, + changed=False, + #msg='', + msg=params['settings'], + ) + +if __name__ == '__main__': + main() diff --git a/library/mt_hotspot.py b/library/mt_hotspot.py new file mode 100644 index 0000000..7cdbdb5 --- /dev/null +++ b/library/mt_hotspot.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_snmp +author: + - "Valentin Gurmeza" +version_added: "2.4" +short_description: Manage mikrotik hotspot endpoints +requirements: + - mt_api +description: + - Generic mikrotik hotspot module. +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + parameter: + description: + - sub endpoint for mikrotik hotspot + required: True + options: + - netwatch + - e-mail + settings: + description: + - All Mikrotik compatible parameters for this particular endpoint. + Any yes/no values must be enclosed in double quotes + state: + description: + - absent or present +''' + +EXAMPLES = ''' +- mt_hotspot: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + parameter: profile + settings: + name: Hotspot_Crew + use-radius: yes +''' + +from mt_common import clean_params, MikrotikIdempotent +from ansible.module_utils.basic import AnsibleModule + + + +def main(): + module = AnsibleModule( + argument_spec = dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + settings = dict(required=False, type='dict'), + parameter = dict( + required = True, + choices = ['hotspot', 'profile', 'walled-garden'], + type = 'str' + ), + state = dict( + required = False, + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + idempotent_parameter = None + params = module.params + idempotent_parameter = 'name' + + if params['parameter'] == 'profile': + params['parameter'] = "hotspot/profile" + + if params['parameter'] == 'walled-garden': + idempotent_parameter = 'comment' + params['parameter'] = "hotspot/walled-garden" + + mt_obj = MikrotikIdempotent( + hostname = params['hostname'], + username = params['username'], + password = params['password'], + state = params['state'], + desired_params = params['settings'], + idempotent_param = idempotent_parameter, + api_path = '/ip/' + str(params['parameter']), + + ) + + mt_obj.sync_state() + + if mt_obj.failed: + module.fail_json( + msg = mt_obj.failed_msg + ) + elif mt_obj.changed: + module.exit_json( + failed=False, + changed=True, + msg=mt_obj.changed_msg, + diff={ "prepared": { + "old": mt_obj.old_params, + "new": mt_obj.new_params, + }}, + ) + else: + module.exit_json( + failed=False, + changed=False, + #msg='', + msg=params['settings'], + ) + +if __name__ == '__main__': + main() diff --git a/library/mt_interface_bridge.py b/library/mt_interface_bridge.py new file mode 100644 index 0000000..90602b4 --- /dev/null +++ b/library/mt_interface_bridge.py @@ -0,0 +1,291 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_interface_bridge +author: + - "Valentin Gurmeza" + - "Shaun Smiley" +version_added: "2.3" +short_description: Manage mikrotik bridge +requirements: + - mt_api +description: + - add, remove, or modify a bridge. +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + state: + description: + - bridge present or absent + required: True # if modifying bridge + choices: + - present + - absent + name: + description: + - name of the bridge + comment: + description: + - brige comment + admin_mac: + description: + - Static MAC address of the bridge (takes effect if auto-mac=no) + ageing_time: + description: + - How long a host's information will be kept in the bridge database + arp: + description: + - Address Resolution Protocol setting + choices: + - disabled + - enabled + - proxy-arp + - reply-only + auto_mac: + description: + - Automatically select one MAC address of bridge ports as a bridge MAC address + choices: + - yes + - no + forward_delay: + description: + - Time which is spent during the initialization phase of the bridge interface (i.e., after router startup or enabling the interface) in listening/learning state before the bridge will start functioning normally + max_message_age: + description: + - How long to remember Hello messages received from other bridges + mtu: + description: + - Maximum Transmission Unit + priority: + description: + - Spanning tree protocol priority for bridge interface + protocol_mode: + description: + - Select Spanning tree protocol (STP) or Rapid spanning tree protocol (RSTP) to ensure a loop-free topology for any bridged LAN + choices: + - none + - rstp + - stp + transmit_hold_count: + description: + - The Transmit Hold Count used by the Port Transmit state machine to limit transmission rate + settings: + description: + - Bridge settings. If defined this argument is a key/value dictionary + choices: + - allow-fast-path: yes/no + - use-ip-firewall: yes/no + - use-ip-firewall-for-ppoe: yes/no + - use-ip-firewall-for-bridge: yes/no + + +''' + +EXAMPLES = ''' +- mt_interface_bridge: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + state: present + name: bridge_native + interface: ether7 + comment: ansible_test +''' + +import mt_api +from mt_common import clean_params +from ansible.module_utils.basic import AnsibleModule + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + name = dict(required=False, type='str'), + comment = dict(required=False, type='str'), + admin_mac = dict(required=False, type='str'), + auto_mac = dict(required=False, type='str'), + ageing_time = dict(required=False, type='str'), + forward_delay = dict(required=False, type='str'), + max_message_age=dict(required=False, type='str'), + transmit_hold_count=dict(required=False, type='str'), + arp = dict( + required = False, + choices = ['disabled', 'enabled', 'proxy-arp', 'reply-only'], + type='str' + ), + protocol_mode= dict( + required = False, + choices = ['none', 'rstp', 'stp'], + type='str' + ), + settings= dict( + required = False, + type='dict' + ), + state= dict( + required = False, + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + state = module.params['state'] + ansible_bridge_name = module.params['name'] + changed = False + changed_message = [] + msg = "" + + mk = mt_api.Mikrotik(hostname, username, password) + try: + mk.login() + except: + module.fail_json( + msg="Could not log into Mikrotik device." + + " Check the username and password.", + ) + + bridge_path = '/interface/bridge' + + response = mk.api_print(base_path=bridge_path) + bridge_params = module.params + mikrotik_bridge = {} + for item in response: + if 'name' in item[1]: + if ansible_bridge_name == item[1]['name']: + mikrotik_bridge = item[1] + + ######################################################## + # Check if we need to edit the bridge settings + ######################################################## + if bridge_params['settings'] is not None: + settings_path = '/interface/bridge/settings' + settings_response = mk.api_print(settings_path) + settings_response = settings_response[0][1] + settings = bridge_params['settings'] + bridge_settings_diff_keys = {} + + for key in settings: + if isinstance(settings[key], bool): + settings[key] = str(settings[key]) + settings[key] = str.lower(settings[key]) + else: + if settings[key] == "yes": + settings[key] = "true" + if settings[key] == "no": + settings[key] = "false" + + for key in settings: + if key in settings_response: + if settings[key] != settings_response[key]: + bridge_settings_diff_keys[key] = settings[key] + else: + bridge_settings_diff_keys[key] = settings[key] + + if bridge_settings_diff_keys != {}: + mk.api_edit(base_path=settings_path, params=bridge_settings_diff_keys) + changed_message.append(bridge_settings_diff_keys) + changed = True + else: + changed = False + + ####################################### + # remove unneeded parameters + # clean up parameters + ###################################### + + remove_params = ['hostname', 'username', 'password', 'state', 'settings'] + for i in remove_params: + del bridge_params[i] + + clean_params(bridge_params) + + if '.id' in mikrotik_bridge: + client_id = mikrotik_bridge['.id'] + else: + client_id = False + + ################################################################## + # We need to make sure that bridge_bridge name is a string + # if it's null then it has not been defined. + ################################################################### + if (state == "present" and isinstance(ansible_bridge_name, str)): + if mikrotik_bridge == {}: + mk.api_add( + base_path=bridge_path, + params=bridge_params + ) + changed_message.append(ansible_bridge_name + " added") + changed = True, + else: + bridge_diff_keys = {} + + for key in bridge_params: + if key in mikrotik_bridge: + if bridge_params[key] != mikrotik_bridge[key]: + bridge_diff_keys[key] = bridge_params[key] + else: + bridge_diff_keys[key] = bridge_params[key] + if bridge_diff_keys != {}: + bridge_diff_keys['numbers'] = client_id + mk.api_edit(base_path=bridge_path, params=bridge_diff_keys) + changed = True + changed_message.append("Changed bridge: " + bridge_params['name']) + else: + #################### + # Already up date + ################### + if not changed: + changed = False + + elif state == "absent": + if client_id: + mk.api_remove(base_path=bridge_path, remove_id=client_id) + changed_message.append(bridge_params['name'] + " removed") + changed = True + ##################################################### + # if client_id is not set there is nothing to remove + ##################################################### + else: + if not changed: + changed = False + elif settings: + ######################################################## + # if settings were set we were modifying bridge settings + # only + pass + else: + module.exit_json( + failed=True, + changed=False, + ) + + if changed: + module.exit_json( + failed=False, + changed=True, + msg=changed_message + ) + else: + module.exit_json( + failed=False, + changed=False, + ) +if __name__ == '__main__': + main() diff --git a/library/mt_interface_bridge_port.py b/library/mt_interface_bridge_port.py new file mode 100644 index 0000000..8aa24c9 --- /dev/null +++ b/library/mt_interface_bridge_port.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_interface_bridge_port +author: + - "Valentin Gurmeza" + - "Shaun Smiley" +version_added: "2.3" +short_description: Manage mikrotik bridge_port +requirements: + - mt_api +description: + - add, remove, or modify a bridge_port. +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + state: + description: + - inteface present or absent in the bridge + required: True + choices: + - present + - absent + comment: + description: + - brige comment + auto_isolate: + description: + - Prevents STP blocking port from erroneously moving into a forwarding state if no BPDU's are received on the bridge + choices: + - yes + - no + bridge: + description: + - The bridge interface the respective interface is grouped in + edge: + description: + - Set port as edge port or non-edge port, or enable automatic detection. Edge ports are connected to a LAN that has no other bridge attached. If the port is configured to discover edge port then as soon as the bridge_ detects a BPDU coming to an edge port, the port becomes a non-edge port + choices: + - auto + - no + - no-discover + - yes + - yes-discover + external_fdb: + description: + - Whether to use wireless registration table to speed up bridge host learning + choices: + - yes + - no + - auto + horizon: + description: + - Use split horizon bridging to prevent bridging loops + interface: + description: + - Name of the interface + path_cost: + description: + - Path cost to the interface, used by STP to determine the "best" path + point_to_point: + description: + - point to point + choices: + - yes + - no + - auto + priority: + description: + - The priority of the interface in comparison with other going to the same subnet +''' + +EXAMPLES = ''' +- mt_interface_bridge_port: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + state: present + name: bridge_port_native + interface: ether7 + comment: ansible_test +''' + +import mt_api +from mt_common import clean_params +from ansible.module_utils.basic import AnsibleModule + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname =dict(required=True), + username =dict(required=True), + password =dict(required=True), + interface =dict(required=True, type='str'), + bridge =dict(required=False, type='str'), + comment =dict(required=False, type='str'), + path_cost =dict(required=False, type='str'), + priority =dict(required=False, type='str'), + horizon =dict(required=False, type='str'), + external_fdb=dict( + required=False, + choices=['yes', 'no', 'auto'], + type='str' + ), + auto_isolate=dict( + required=False, + choices=['yes', 'no'], + type='str' + ), + edge=dict( + required=False, + choices=['auto', 'yes', 'no', 'no-discover', 'yes-discover'], + type='str' + ), + point_to_point=dict( + required=False, + choices=['yes', 'no', 'auto'], + type='str' + ), + state=dict( + required=True, + choices=['present', 'absent'], + type='str' + ), + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + state = module.params['state'] + ansible_bridge_port_interface = module.params['interface'] + changed = False + changed_message = [] + msg = "" + + mk = mt_api.Mikrotik(hostname, username, password) + try: + mk.login() + except: + module.fail_json( + msg="Could not log into Mikrotik device." + + " Check the username and password.", + ) + + bridge_port_path = '/interface/bridge/port' + + response = mk.api_print(base_path=bridge_port_path) + bridge_port_params = module.params + mikrotik_bridge_port = {} + for item in response: + if 'interface' in item[1].keys(): + if ansible_bridge_port_interface == item[1]['interface']: + mikrotik_bridge_port = item[1] + + ####################################### + # remove unneeded parameters + ###################################### + + remove_params = ['hostname', 'username', 'password', 'state'] + for i in remove_params: + del bridge_port_params[i] + + ########################################## + # modify clean_params in place + ############################################ + clean_params(bridge_port_params) + + if '.id' in mikrotik_bridge_port: + client_id = mikrotik_bridge_port['.id'] + else: + client_id = False + + if state == "present": + if mikrotik_bridge_port == {}: + mk.api_add( + base_path=bridge_port_path, + params=bridge_port_params + ) + changed_message.append(ansible_bridge_port_interface + " added to bridge") + changed = True, + else: + bridge_port_diff_keys = {} + + for key in bridge_port_params: + if key in mikrotik_bridge_port: + if bridge_port_params[key] != mikrotik_bridge_port[key]: + bridge_port_diff_keys[key] = bridge_port_params[key] + else: + bridge_port_diff_keys[key] = bridge_port_params[key] + if bridge_port_diff_keys != {}: + bridge_port_diff_keys['numbers'] = client_id + mk.api_edit(base_path=bridge_port_path, params=bridge_port_diff_keys) + changed = True + changed_message.append("Changed bridge port: " + bridge_port_params['bridge']) + else: + #################### + # Already up date + ################### + if not changed: + changed = False + + elif state == "absent": + if client_id: + mk.api_remove(base_path=bridge_port_path, remove_id=client_id) + changed_message.append(bridge_port_params['interface'] + " removed") + changed = True + ##################################################### + # if client_id is not set there is nothing to remove + ##################################################### + else: + if not changed: + changed = False + else: + module.exit_json( + failed=True, + changed=False, + ) + + if changed: + module.exit_json( + failed=False, + changed=True, + msg=changed_message + ) + else: + module.exit_json( + failed=False, + changed=False, + ) +if __name__ == '__main__': + main() diff --git a/library/mt_interface_ovpn_client.py b/library/mt_interface_ovpn_client.py new file mode 100644 index 0000000..4572d53 --- /dev/null +++ b/library/mt_interface_ovpn_client.py @@ -0,0 +1,277 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_interface_ovpn_client +author: + - "Valentin Gurmeza" + - "Shaun Smiley" +version_added: "2.3" +short_description: Manage mikrotik openvpn client +requirements: + - mt_api +description: + - add, remove, or modify an openvpn client. Mikrotik uses ovpn alias for openvpn. +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + state: + description: + - ovpn client present or absent + required: True + choices: + - present + - absent + comment: + description: + - ovpn client comment + required: False + name: + description: + - name of the ovpn client + user: + description: + - vpn user name + required: True # if state is present + connect_to: + description: + - Remote address of the OVPN server + required: True # if state is present + vpn_password: + description: + - ovpn client password + required: True # if state is present + port: + description: + - ovpn client port + required: False + max_mtu: + description: + - Maximum Transmission Unit. Max packet size that OVPN interface will be able to send without packet fragmentation. + required: False + profile: + description: + - Used PPP profile + required: False + certificate: + description: + - Name of the client certificate + required: False + mac_address: + description: + - Mac address of OVPN interface + required: False + add_default_route: + description: + - Whether to add OVPN remote address as a default route + required: False + choices: + - yes + - no + cipher: + description: + - Allowed ciphers + required: False + choices: + - blowfish128 + - aes128 + - aes192 + - aes256 + auth: + description: + - Allowed authentication methods + required: False + choices: + - sha1 + - md5 + - null + - aes256 + mode: + description: + - Layer3 or layer2 tunnel mode (alternatively tun, tap) + required: False + choices: + - ip + - ethernet +''' + +EXAMPLES = ''' +- mt_interface_ovpn_client: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + state: present + vpn_user: ansible_admin + connect_to: 192.168.230.1 + client_name: ansible_test + vpn_password: 'password' +''' + +import mt_api +import re +from ansible.module_utils.basic import AnsibleModule + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + name = dict(required=True, type='str'), + user = dict(required=False, type='str'), + connect_to = dict(required=False, type='str'), + comment = dict(required=False, type='str'), + vpn_password = dict(required=False, type='str'), + port = dict(required=False, type='str'), + max_mtu = dict(required=False, type='str'), + profile = dict(required=False, type='str'), + certificate = dict(required=False, type='str'), + mac_address = dict(required=False, type='str'), + add_default_route = dict( + required = False, + choices = ['yes', 'no'], + type='str' + ), + cipher = dict( + required = False, + choices = ['blowfish128', 'aes128', 'aes192', 'aes256'], + type='str' + ), + auth = dict( + required = False, + choices = ['sha1', 'md5', 'null'], + type='str' + ), + mode = dict( + required = False, + choices = ['ip', 'ethernet'], + type='str' + ), + state = dict( + required = False, + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + state = module.params['state'] + ansible_client_name = module.params['name'] + ansible_mac_address = module.params['mac_address'] + changed = False + msg = "" + + mk = mt_api.Mikrotik(hostname, username, password) + try: + mk.login() + except: + module.fail_json( + msg="Could not log into Mikrotik device." + + " Check the username and password.", + ) + + ovpn_client_path = '/interface/ovpn-client' + + response = mk.api_print(base_path=ovpn_client_path) + ovpn_client_params = module.params + mikrotik_client_name = "" + mikrotik_ovpn_client = {} + for item in response: + if 'name' in item[1].keys(): + if ansible_client_name == item[1]['name']: + mikrotik_client_name = item[1]['name'] + mikrotik_ovpn_client = item[1] + + ####################################### + # remove keys with empty values + # remove unneeded parameters + # modify keys with '_' to match mikrotik parameters + ###################################### + remove_params = ['hostname', 'username', 'password', 'state'] + for i in remove_params: + del ovpn_client_params[i] + for key in ovpn_client_params.keys(): + if ovpn_client_params[key] is None: + del ovpn_client_params[key] + + for key in ovpn_client_params.keys(): + if 'vpn_password' == key: + ovpn_client_params['password'] = ovpn_client_params[key] + del ovpn_client_params[key] + + for key in ovpn_client_params.keys(): + new_key = re.sub('_','-', key) + if new_key != key: + ovpn_client_params[new_key] = ovpn_client_params[key] + del ovpn_client_params[key] + + if state == "present": + if mikrotik_ovpn_client == {}: + mk.api_add( + base_path=ovpn_client_path, + params=ovpn_client_params + ) + module.exit_json( + changed=True, + failed=False, + msg=ansible_client_name + " client added" + ) + else: + mikrotik_ovpn_client['add-default-route'] = 'no' + if 'comment' in ovpn_client_params and 'comment' not in mikrotik_ovpn_client: + mikrotik_ovpn_client['comment'] = None + client_id = mikrotik_ovpn_client['.id'] + for i in ['.id', 'running']: + mikrotik_ovpn_client.pop(i) + update_keys = {} + for key, value in ovpn_client_params.items(): + if value != mikrotik_ovpn_client[key]: + update_keys[key] = value + if update_keys == {}: + module.exit_json( + changed=False, + failed=False, + ) + else: + update_keys['numbers'] = client_id + mk.api_edit( + base_path=ovpn_client_path, + params=update_keys + ) + module.exit_json( + changed=True, + failed=False, + msg=update_keys, + ) + else: + if mikrotik_ovpn_client == {}: + module.exit_json( + changed=False, + failed=False, + ) + else: + remove_response = mk.api_remove( + base_path=ovpn_client_path, + remove_id=mikrotik_ovpn_client['.id'] + ) + module.exit_json( + changed=True, + failed=False, + msg=remove_response[0] + ) + +if __name__ == '__main__': + main() diff --git a/library/mt_interface_wireless.py b/library/mt_interface_wireless.py new file mode 100644 index 0000000..c82302e --- /dev/null +++ b/library/mt_interface_wireless.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_interface_wireless +author: + - "Valentin Gurmeza" +version_added: "2.4" +short_description: Manage mikrotik interface_wireless endpoints +requirements: + - mt_api +description: + - Generic mikrotik interface wireless module. +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + parameter: + description: + - sub endpoint for mikrotik interface wireless + required: True + options: + - security-profiles + settings: + description: + - All Mikrotik compatible parameters for this particular endpoint. + Any yes/no values must be enclosed in double quotes + state: + description: + - absent or present +''' + +EXAMPLES = ''' +- mt_interface_wireless: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + parameter: security-profiles + state: present + settings: + name: test1 + supplicant-identity: test + +''' + +from mt_common import clean_params, MikrotikIdempotent +from ansible.module_utils.basic import AnsibleModule + + + +def main(): + module = AnsibleModule( + argument_spec = dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + settings = dict(required=False, type='dict'), + parameter = dict( + required = True, + choices = ['security-profiles'], + type = 'str' + ), + state = dict( + required = False, + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + idempotent_parameter = None + params = module.params + + idempotent_parameter = 'name' + + mt_obj = MikrotikIdempotent( + hostname = params['hostname'], + username = params['username'], + password = params['password'], + state = params['state'], + desired_params = params['settings'], + idempotent_param = idempotent_parameter, + api_path = '/interface/wireless/' + str(params['parameter']), + + ) + + mt_obj.sync_state() + + if mt_obj.failed: + module.fail_json( + msg = mt_obj.failed_msg + ) + elif mt_obj.changed: + module.exit_json( + failed=False, + changed=True, + msg=mt_obj.changed_msg, + diff={ "prepared": { + "old": mt_obj.old_params, + "new": mt_obj.new_params, + }}, + ) + else: + module.exit_json( + failed=False, + changed=False, + #msg='', + msg=params['settings'], + ) + +if __name__ == '__main__': + main() diff --git a/library/mt_interfaces.py b/library/mt_interfaces.py new file mode 100644 index 0000000..ee3fa4f --- /dev/null +++ b/library/mt_interfaces.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_interface.py +author: + - "Shaun Smiley" + - "Valentin Gurmeza" +version_added: "2.4" +short_description: Manage mikrotik interfaces +requirements: + - mt_api +description: + - manage settings on interfaces +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + parameter: + description: + - sub endpoint for mikrotik tool + required: True + options: + - ethernet + - vlan + settings: + description: + - All Mikrotik compatible parameters for this particular endpoint. + Any yes/no values must be enclosed in double quotes + required: True + state: + description: + - absent or present +''' + +EXAMPLES = ''' +- mt_interfaces: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + parameter: "ethernet" + state: present + settings: + name: ether2 + comment: Ansible controlled ether2 + mtu: 1501 +''' + +from mt_common import clean_params, MikrotikIdempotent +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule( + argument_spec=dict( + hostname=dict(required=True), + username=dict(required=True), + password=dict(required=True), + settings=dict(required=True, type='dict'), + parameter = dict( + required = True, + choices = ['ethernet', 'vlan'], + type = 'str' + ), + state = dict( + required = False, + choices = ['present', 'absent'], + type = 'str' + ) + ), + supports_check_mode=True + ) + + params = module.params + idempotent_parameter = 'name' + + mt_obj = MikrotikIdempotent( + hostname = params['hostname'], + username = params['username'], + password = params['password'], + state = params['state'], + desired_params = params['settings'], + idempotent_param = idempotent_parameter, + api_path = '/interface/' + str(params['parameter']), + check_mode = module.check_mode, + ) + + # exit if login failed + if not mt_obj.login_success: + module.fail_json( + msg = mt_obj.failed_msg + ) + + # add, remove or edit things + mt_obj.sync_state() + + if mt_obj.failed: + module.fail_json( + msg = mt_obj.failed_msg + ) + elif mt_obj.changed: + module.exit_json( + failed=False, + changed=True, + msg=mt_obj.changed_msg, + diff={ "prepared": { + "old": mt_obj.old_params, + "new": mt_obj.new_params, + }}, + ) + else: + module.exit_json( + failed=False, + changed=False, + #msg='', + msg=params['settings'], + ) + +if __name__ == '__main__': + main() diff --git a/library/mt_ip_address.py b/library/mt_ip_address.py new file mode 100644 index 0000000..750dab9 --- /dev/null +++ b/library/mt_ip_address.py @@ -0,0 +1,183 @@ +DOCUMENTATION = ''' +module: mt_ip_address +author: + - "Valentin Gurmeza" + - "Shaun Smiley" +version_added: "2.3" +short_description: Manage mikrotik /ip/addresses +requirements: + - rosapi +description: + - FILL ME OUT +options: + hostname: + description: + - + username: + description: + - + password: + description: + - + interface: + description: + - + address: + description: + - + network: + description: + - + state: + description: + - + force: + description: + - True/False value to force removing the address on an interface + even if the address does not match. +''' + +EXAMPLES = ''' +- mt_ip_address: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + interface: "ether2" + address: "192.168.88.2/24" + network: "192.168.88.0/24" + state: "present" + comment: "link 3" +''' + +import mt_api +import socket + +#import mt_action #TODO: get this working + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + interface = dict(required=True, type='str'), + address = dict(required=True, type='str', aliases=['ip', 'addr', 'ip_address']), + network = dict(required=False, type='str', default=""), + comment = dict(required=False, type='str', default=""), + state = dict( + required = False, + default = "present", + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + ip_address = module.params['address'] + interface = module.params['interface'] + network = module.params['network'] + ip_state = module.params['state'] + comment = module.params['comment'] + changed = False + msg = "" + + interface_path = '/interface' + address_path = '/ip/address' + address_print_params = { + ".proplist": "interface,address,.id,network,netmask,comment" + } + interface_print_params = { + ".proplist": "name,.id,type" + } + mk = mt_api.Mikrotik(hostname,username,password) + try: + mk.login() + interfaces = mk.api_print(interface_path, interface_print_params) + except: + module.fail_json( + msg="Could not log into Mikrotik device." + + " Check the username and password.", + ) + + ################################### + # Check if interface is present + # exit if interface is not present + ################################### + interfacelist = [] + exitmessage = [] + for i in range(0, len(interfaces) - 1): + interfacelist.append(interfaces[i][1]["name"]) + intExists = False + + if (interface in interfacelist): + intExists = True + # module.exit_json(failed=False, changed=False, msg=interfacelist) + if intExists: + pass + #exitmessage.append("Interface " + interface + " exists.") #this is never used + else: + exitmessage.append("Interface " + interface + " does not exist.") + module.fail_json(failed=True, msg=exitmessage) + + ############################################## + # Check if IP address is set on the interface + # make no changes if address already set + ############################################## + ip_addresses = mk.api_print(address_path, address_print_params) + + iplist = [] + for i in range(0, len(ip_addresses) - 1): + iplist.append(ip_addresses[i][1]["address"]) + if ip_addresses[i][1]["address"] == ip_address: + ip_id = ip_addresses[i][1][".id"] + + if ip_state == "present": + if ip_address in iplist: + module.exit_json( + failed=False, + #msg="IP Address: " + ip_address + + #" is already configured" + + #" on interface " + interface, + ) + + else: + add_dict = { + 'address': ip_address, + 'interface': interface, + 'comment': comment + } + response = mk.api_add(address_path, add_dict) + module.exit_json( + failed=False, + changed=True, + #msg="IP address: " + ip_address + " has been configured" + + #" on interface " + interface + ) + + if ip_state == "absent": + if ip_address in iplist: + response = mk.api_remove(address_path, ip_id) + module.exit_json( + failed=False, + changed=True, + #msg="IP Address: " + ip_address + + #" has been removed" + ) + + else: + module.exit_json( + failed=False, + changed=False, + #msg="IP Address: " + ip_address + + #" is already absent" + ) + +if __name__ == '__main__': + main() diff --git a/library/mt_ip_firewall.py b/library/mt_ip_firewall.py new file mode 100644 index 0000000..7353391 --- /dev/null +++ b/library/mt_ip_firewall.py @@ -0,0 +1,254 @@ +DOCUMENTATION = ''' +module: mt_ip_firewall +author: + - "Valentin Gurmeza" + - "Shaun Smiley" +version_added: "2.3" +short_description: Manage mikrotik /ip/firewall/ +requirements: + - mt_api +description: + - Generic mikrotik firewall module +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + parameter: + description: + - sub endpoint for mikrotik tool + required: True + options: + - netwatch + - e-mail + rule: + description: + - a list containing dictionary parameters. + action, chain, comment, and place-before keys are required + parameter: + description: + - sub endpoint for mikrotik firewall + required: True + options: + - filter + - nat + settings: + description: + - All Mikrotik compatible parameters for this particular endpoint. + Any yes/no values must be enclosed in double quotes + - if a firewall list containing dictionary parameters, + action, chain, comment, and place-before keys are required + state: + description: + - absent or present + force: + description: + - True/False value to force remove the rule regardless of the position in the rule list. +''' + +EXAMPLES = ''' +- mt_ip_firewall: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + state: present + parameter: filter + settings: + action: accept + chain: forward + comment: controlled by ansible + place-before: "2" +''' + +from ansible.module_utils.basic import AnsibleModule +import mt_api +import re +from copy import copy + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + rule = dict(required=False, type='dict'), + parameter = dict(required=True, type='str'), + state = dict( + required = False, + default = "present", + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + rule = module.params['rule'] + state = module.params['state'] + api_path = '/ip/firewall/' + module.params['parameter'] +# ############################################## +# Check if "place-before" is an integer +# ############################################# + try: + desired_order = int(rule['place-before']) + except: + module.exit_json( + failed=True, + changed=False, + msg="place-before is not set or is not set to an integer", + ) + changed = False + msg = "" + + mk = mt_api.Mikrotik(hostname, username, password) + try: + mk.login() + except: + module.fail_json( + msg="Could not log into Mikrotik device." + + " Check the username and password.", + ) + + filter_response = mk.api_print(api_path) + current_rule = None + current_id = None + existing_order = None + last_item = len(filter_response) - 2 + changed_msg = [] + + # Always set the comment to the order_number + if 'comment' in rule: + rule['comment'] = str(desired_order) + " " + str(rule['comment']) + else: + rule['comment'] = str(desired_order) + + if desired_order <= last_item: + placed_at_the_end = False + else: + placed_at_the_end = True + # remove the place-before if we are placing + # the rule at the bottom of the chain + rule.pop('place-before', None) + + # Check rule is not present + # find existing rule + # current_rule is what's on mikrotik right now + for index, current_param in enumerate(filter_response): + if 'comment' in current_param[1]: + if re.search(r"^" + str(desired_order) + "\s+", current_param[1]['comment']): + current_id = current_param[1]['.id'] + existing_order = index + current_rule = current_param[1] + # remove the place-before since we'll be editing not moving it + rule.pop('place-before', None) + + # ensure the rule if state is present + if state == "present": + # if we don't have an existing rule to match + # the desired we create a new one + if not current_rule: + mk.api_add(api_path, rule) + changed = True, + # if current_rule is true we need to ensure the changes + else: + out_params = {} + old_params = {} + for desired_param in rule: + rule[desired_param] = str(rule[desired_param]) + if desired_param in current_rule: + if current_rule[desired_param] != rule[desired_param]: + out_params[desired_param] = rule[desired_param] + old_params[desired_param] = current_rule[desired_param] + else: + out_params[desired_param] = rule[desired_param] + if desired_param in current_rule: + old_params[desired_param] = current_rule[desired_param] + + # When out_params has been set it means we found our diff + # and will set it on the mikrotik + if out_params: + if current_id is not None: + out_params['.id'] = current_id + + mk.api_edit( + base_path = api_path, + params = out_params + ) + + # we don't need to show the .id in the changed message + if '.id' in out_params: + del out_params['.id'] + changed = True + + changed_msg.append({ + "new_params": out_params, + "old_params": old_params, + }) + + # ensure the rule is in right position + if current_id: + if int(existing_order) != int(desired_order): + api_path += '/move' + params = False + if placed_at_the_end: + if existing_order > last_item: + params = { + '.id': current_id, + } + else: + params = { + '.id': current_id, + 'destination': desired_order + } + if params: + mk.api_command(api_path, params) + changed_msg.append({ + "moved": existing_order, + "to": old_params, + }) + changed = True + +##################################### +# Remove the rule +##################################### + elif state == "absent": + if current_rule: + mk.api_remove(api_path, current_id) + changed = True + changed_msg.append("removed rule: " + str(desired_order)) + else: + failed = True + + if changed: + module.exit_json( + failed=False, + changed=True, + msg=changed_msg + ) + elif not changed: + module.exit_json( + failed=False, + changed=False, + ) + else: + module.fail_json() + + ########################################## + # To DO: + # Clean up duplicate items + ########################################### + +if __name__ == '__main__': + main() diff --git a/library/mt_ip_firewall_addresslist.py b/library/mt_ip_firewall_addresslist.py new file mode 100644 index 0000000..8e5d4f4 --- /dev/null +++ b/library/mt_ip_firewall_addresslist.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_ip_firewall_filter +author: + - "Valentin Gurmeza" + - "Shaun Smiley" +version_added: "2.3" +short_description: Manage mikrotik /ip/firewall/filter +requirements: + - mt_api +description: + - FILL ME OUT +options: + hostname: + description: + - + username: + description: + - + password: + description: + - + list-name: + description: + - name of the address-list + state: + description: + - present or absent + address_list: + description: + - A list of single IP addresses or range of IPs to add to address-list. + Can also be a set to a hostname which will create a dynamic entry + in the list with the proper IP address for the record (as of 6.38.1) +''' + +EXAMPLES = ''' +- mt_ip_firewall_addresslist: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + state:   "present" + name:   "block_all" + dynamic: false + address_list: + - 192.168.10.1 + - yahoo.com + - 19.134.52.23/23 +''' + +import mt_api +from ansible.module_utils.basic import AnsibleModule + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + list_name = dict(required=True, type='str'), + address_list = dict(required=False, type='list'), + state = dict( + required = False, + default = "present", + choices = ['present', 'absent', 'force'], + type = 'str' + ), + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + ansible_list_name = module.params['list_name'] + ansible_address_list = module.params['address_list'] + state = module.params['state'] + changed = False + msg = "" + + address_list_path = '/ip/firewall/address-list' + mk = mt_api.Mikrotik(hostname, username, password) + try: + mk.login() + except: + module.fail_json( + msg="Could not log into Mikrotik device." + + " Check the username and password.", + ) + + response = mk.api_print(address_list_path) + mikrotik_address_list = [] + mikrotik_address_id = {} + list_name = ansible_list_name + for item in response: + if 'list' in item[1].keys(): + address = item[1]['address'] + if item[1]['list'] == list_name: + temp_dict = {} + temp_dict['address'] = item[1]['address'] + if 'comment' in item[1].keys(): + temp_dict['comment'] = item[1]['comment'] + mikrotik_address_list.append(dict(temp_dict)) + mikrotik_address_id[address] = item[1]['.id'] + + if state == "present": + if ansible_address_list == mikrotik_address_list: + module.exit_json( + changed = False, + failed = False, + msg = "list up to date", + ) + common_list = [] + for item in ansible_address_list: + for item2 in mikrotik_address_list: + if item['address'] in item2['address']: + common_list.append(item['address']) + if item['comment'] in item2['comment']: + ################## + # update comment + ################# + pass + + ################################# + # build add_list + # add item missing from mikrotik + ################################# + add_list = [] + for item in ansible_address_list: + if item['address'] not in common_list: + temp_dict = {} + temp_dict['address'] = item['address'] + temp_dict['comment'] = item['comment'] + add_list.append(dict(temp_dict)) + + for i in add_list: + #address = i['address'] + #comment = i['comment'] + add_dictionary = { + "address": i['address'], + "list": list_name, + "comment": i['comment'] + } + mk.api_add(address_list_path, add_dictionary) + changed = True + + ##################### + # build remove list + ###################### + remove_list = [] + for item in mikrotik_address_list: + if item['address'] not in common_list: + remove_list.append(item['address']) + ####################################### + # Remove every item in the address_list + ####################################### + for i in remove_list: + remove_id = mikrotik_address_id[i] + mk.api_remove(address_list_path, remove_id) + if not changed: + changed = True + else: + ####################################### + # Remove every item + ####################################### + for remove_id in mikrotik_address_id.values(): + mk.api_remove(address_list_path, remove_id) + if not changed: + changed = True + + if changed: + module.exit_json( + changed = True, + failed = False, + msg = ansible_list_name + "has been modified", + ) + else: + module.exit_json( + changed = False, + failed = False, + msg = ansible_list_name + " is up to date", + ) + + +if __name__ == '__main__': + main() diff --git a/library/mt_ip_firewall_filter.py b/library/mt_ip_firewall_filter.py new file mode 100644 index 0000000..f6541a9 --- /dev/null +++ b/library/mt_ip_firewall_filter.py @@ -0,0 +1,178 @@ +DOCUMENTATION = ''' +module: mt_ip_firewall_filter +author: + - "Valentin Gurmeza" + - "Shaun Smiley" +version_added: "2.3" +short_description: Manage mikrotik /ip/firewall/filter +requirements: + - mt_api +description: + - FILL ME OUT +options: + hostname: + description: + - + username: + description: + - + password: + description: + - + rule: + description: + - a list containing dictionary parameters. + action, chain, comment, and place-before keys are required + force: + description: + - True/False value to force remove the rule regardless of the position in the rule list. +''' + +EXAMPLES = ''' +- mt_ip_firewall_filter: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + rule: + action: accept + chain: forward + comment: controlled by ansible + place-before: "2" +''' + +import mt_api + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + rule = dict(required=False, type='dict'), + state = dict( + required = False, + default = "present", + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + rule = module.params['rule'] + rule_state = module.params['state'] + changed = False + msg = "" + + filter_path = '/ip/firewall/filter' + mk = mt_api.Mikrotik(hostname, username, password) + try: + mk.login() + except: + module.fail_json( + msg="Could not log into Mikrotik device." + + " Check the username and password.", + ) + +# ############################################## +# Check if "place-before" is an integer +# ############################################# + try: + placement = int(rule['place-before']) + if isinstance(placement, int): + pass + except: + module.exit_json( + failed=True, + changed=False, + msg="place-before is not set or is not set to an integer", + ) +####################################################### +# Construct the fluff to ignore the extras from mikrotik response +######################################################## + if "log" not in rule: + rule['log'] = "false" + if "log-prefix" not in rule: + rule['log-prefix'] = "" + fluff = ( + "bytes", + "disabled", + "invalid", + "packets", + "dynamic", + "invalid", + ".id", + ) + +################################### +# Check if rule is present +# exit if rule is not present +################################### + + filter_response = mk.api_print(filter_path) + last_item = len(filter_response) - 2 + order_number = int(rule['place-before']) + if order_number <= last_item: + clean_response = filter_response[order_number][1] + clean_response['place-before'] = rule['place-before'] + placed_at_the_end = False + remove_id = clean_response['.id'] + for f in fluff: + clean_response.pop(f, None) + else: + placed_at_the_end = True + if rule_state == "present": + if placed_at_the_end is False: + if rule == clean_response: + module.exit_json( + failed=False, + changed=False, + ) +################################ +# add the rule +################################## + else: + mk.api_remove(filter_path, remove_id) + mk.api_add(filter_path, rule) + module.exit_json( + failed=False, + changed=True, + ) + else: + rule.pop('place-before', None) + mk.api_add(filter_path, rule) + module.exit_json( + failed=False, + changed=True, + ) +##################################### +# Remove the rule +##################################### + elif rule_state == "absent": + if placed_at_the_end: + module.exit_json( + failed=False, + changed=False + ) + else: + if rule == clean_response: + mk.api_remove(filter_path, remove_id) + module.exit_json( + failed=False, + changed=True + ) + else: + module.exit_json( + failed=False, + changed=False + ) + + +if __name__ == '__main__': + main() diff --git a/library/mt_ip_pool.py b/library/mt_ip_pool.py new file mode 100644 index 0000000..c180a2a --- /dev/null +++ b/library/mt_ip_pool.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_dhcp_server +author: + - "Valentin Gurmeza" + - "Shaun Smiley" +version_added: "2.3" +short_description: Manage mikrotik ip pools +requirements: + - mt_api +description: + - Add or remove ip pool +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + state: + description: + - ip pool present or absent + required: True + name: + description: + - name of the ip pool + required: True + ranges: + description: + - IP address list of non-overlapping IP address ranges + required: False + next_pool: + description: + - When address is acquired from pool that has no free addresses, and next_pool property is set to another pool, then next IP address will be acquired from next_pool + required: False +''' + +EXAMPLES = ''' +# Add a new dhcp server +- mt_dhcp_server: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + state: present + name: ansible_test + address_pool: 'pool1' + interface: vlan15 +''' + +import mt_api +from ansible.module_utils.basic import AnsibleModule +import re + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname=dict(required=True), + username=dict(required=True), + password=dict(required=True), + name=dict(required=True, type='str'), + ranges=dict(required=False, type='str'), + next_pool=dict(required=False, type='str'), + state=dict( + required = True, + choices = ['present', 'absent'], + type = 'str' + ) + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + ip_pool_name = module.params['name'] + state = module.params['state'] + changed = False + msg = "" + + mk = mt_api.Mikrotik(hostname, username, password) + try: + mk.login() + except: + module.fail_json( + msg="Could not log into Mikrotik device." + + " Check the username and password.", + ) + + ip_pool_path = '/ip/pool' + response = mk.api_print(base_path=ip_pool_path) + ip_pool_params = module.params + mikrotik_ip_pool= {} + for item in response: + if 'name' in item[1]: + if ip_pool_name == item[1]['name']: + ip_pool_name = item[1]['name'] + mikrotik_ip_pool = item[1] + + ################################################################# + # Since we are grabbing all the parameters passed by the module + # We need to remove the one that won't be used + # as mikrotik parameters + ################################################# + remove_params = ['hostname', 'username', 'password', 'state'] + for i in remove_params: + ip_pool_params.pop(i) + ####################################### + # remove keys with empty values + # convert vars with '_' to '-' + # convert yes/no to true/false + ###################################### + for key in ip_pool_params.keys(): + if ip_pool_params[key] is None: + ip_pool_params.pop(key) + + for key in ip_pool_params.keys(): + new_key = re.sub('_','-', key) + if new_key != key: + ip_pool_params[new_key] = ip_pool_params[key] + del ip_pool_params[key] + + for key in ip_pool_params: + if ip_pool_params[key] == "yes": + ip_pool_params[key] = "true" + if ip_pool_params[key] == "no": + ip_pool_params[key] = "false" + + ########################################################## + # Define client_id to be used by remove and edit function + ########################################################## + if '.id' in mikrotik_ip_pool: + client_id = mikrotik_ip_pool['.id'] + else: + client_id = False + + if state == "present": + if mikrotik_ip_pool == {}: + mk.api_add(base_path=ip_pool_path, params=ip_pool_params) + module.exit_json( + failed=False, + changed=True, + msg="Added dhcp server " + ip_pool_name + ) + ################################################### + # If an item exists we check if all the parameters + # match what we have in ansible + ###################################### + else: + ip_pool_diff_keys = {} + + for key in ip_pool_params: + if key in mikrotik_ip_pool: + if ip_pool_params[key] != mikrotik_ip_pool[key]: + ip_pool_diff_keys[key] = ip_pool_params[key] + else: + ip_pool_diff_keys[key] = ip_pool_params[key] + if ip_pool_diff_keys != {}: + ip_pool_diff_keys['numbers'] = client_id + mk.api_edit(base_path=ip_pool_path, params=ip_pool_diff_keys) + module.exit_json( + failed=False, + changed=True, + msg="Changed dhcp sever item: " + ip_pool_params['name'], + ) + else: + #################### + # Already up date + module.exit_json( + failed=False, + changed=False, + ) + elif state == "absent": + if client_id: + mk.api_remove(base_path=ip_pool_path, remove_id=client_id) + module.exit_json( + failed=False, + changed=True, + msg=ip_pool_params['name'] + " removed" + ) + ##################################################### + # if client_id is not set there is nothing to remove + ##################################################### + else: + module.exit_json( + failed=False, + changed=False, + ms=mikrotik_ip_pool + ) + else: + module.exit_json( + failed=True, + changed=False, + ) +if __name__ == '__main__': + main() diff --git a/library/mt_login_test.py b/library/mt_login_test.py new file mode 100644 index 0000000..75ea270 --- /dev/null +++ b/library/mt_login_test.py @@ -0,0 +1,41 @@ +#! /usr/bin/python + +import json +import mt_api + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname=dict(required=True), + username=dict(required=True), + password=dict(required=True), + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + changed = False + msg = "" + + mk = mt_api.Mikrotik(hostname,username,password) + try: + mk.login() + except: + module.fail_json( + msg="Could not log into Mikrotik device. Check the username and password." + ) + + + # response = apiros.talk([b'/ip/address/add', b'=address=192.168.15.2/24', b'=interface=ether7']) + module.exit_json( + changed=False, + failed=False, + ) + +if __name__ == '__main__': + main() diff --git a/library/mt_neighbor.py b/library/mt_neighbor.py new file mode 100644 index 0000000..371bbc4 --- /dev/null +++ b/library/mt_neighbor.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_snmp +author: + - "Valentin Gurmeza" +version_added: "2.4" +short_description: Manage mikrotik neighbor endpoints +requirements: + - mt_api +description: + - Generic mikrotik neighbor module. +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + parameter: + description: + - sub endpoint for mikrotik neighbor + required: True + options: + - netwatch + - e-mail + settings: + description: + - All Mikrotik compatible parameters for this particular endpoint. + Any yes/no values must be enclosed in double quotes + state: + description: + - absent or present +''' + +EXAMPLES = ''' +- mt_hotspot: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + parameter: discovery + settings: + name: ether7 + discover: "yes" +''' + +from mt_common import clean_params, MikrotikIdempotent +from ansible.module_utils.basic import AnsibleModule + + + +def main(): + module = AnsibleModule( + argument_spec = dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + settings = dict(required=False, type='dict'), + parameter = dict( + required = True, + choices = ['discovery'], + type = 'str' + ), + state = dict( + required = False, + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + idempotent_parameter = None + params = module.params + idempotent_parameter = 'name' + + + mt_obj = MikrotikIdempotent( + hostname = params['hostname'], + username = params['username'], + password = params['password'], + state = params['state'], + desired_params = params['settings'], + idempotent_param = idempotent_parameter, + api_path = '/ip/neighbor/' + str(params['parameter']), + + ) + + mt_obj.sync_state() + + if mt_obj.failed: + module.fail_json( + msg = mt_obj.failed_msg + ) + elif mt_obj.changed: + module.exit_json( + failed=False, + changed=True, + msg=mt_obj.changed_msg, + diff={ "prepared": { + "old": mt_obj.old_params, + "new": mt_obj.new_params, + }}, + ) + else: + module.exit_json( + failed=False, + changed=False, + #msg='', + msg=params['settings'], + ) + +if __name__ == '__main__': + main() diff --git a/library/mt_radius.py b/library/mt_radius.py new file mode 100644 index 0000000..f0e8f8d --- /dev/null +++ b/library/mt_radius.py @@ -0,0 +1,259 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_radius +author: + - "Valentin Gurmeza" + - "Shaun Smiley" +version_added: "2.3" +short_description: Manage mikrotik radius client +requirements: + - mt_api +description: + - Add or remove a radius client +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + state: + description: + - client present or absent + required: True + choices: + - present + - absent + comment: + description: + - This module only ensures entries that match the comment field. + Thus, you should make unique comments for every entry. + required: True # only if state is present + address: + description: + - IPv4 or IPv6 address of RADIUS server + required: False + secret: + description: + - Shared secret used to access the RADIUS server + required: False + default: null + timeout: + description: + - Timeout after which the request should be resend + required: False + default: null + service: + description: + - Router services that will use this RADIUS server: + choices: + - 'hotspot' # HotSpot authentication service + - 'login' # router's local user authentication + - 'ppp # Point-to-Point clients authentication + - 'wireless # wireless client authentication (client's MAC address is sent as User-Name) + - 'dhcp # DHCP protocol client authentication (client's MAC address is sent as User-Name)IPv4 or IPv6 address of RADIUS server + required: False + default: null + incoming: + accept: + choices: ['true', 'false' ] + port: "3799" + description: + - Whether to accept the unsolicited messages. + Also include the port number to listen for the requests on. + Accept and port values must be strings + required: False + default: null +''' + +EXAMPLES = ''' +# Add a new radius entry +- mt_radius: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + state: present + address: 192.168.230.1 + comment: ansible_test + secret: 'password' + service: + - login + - hotspot + - wireless + timeout: '2s500ms' +''' + +import mt_api +from ansible.module_utils.basic import AnsibleModule + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname= dict(required=True), + username= dict(required=True), + password= dict(required=True), + address = dict(required=False, type='str'), + comment = dict(required=True, type='str'), + secret = dict(required=False, type='str'), + service = dict(required=False, type='list'), + timeout = dict(required=False, type='str'), + incoming= dict(required=False, type='dict'), + state = dict( + required = True, + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + state = module.params['state'] + changed = False + msg = "" + + radius_path = '/radius' + mk = mt_api.Mikrotik(hostname, username, password) + try: + mk.login() + except: + module.fail_json( + msg="Could not log into Mikrotik device." + + " Check the username and password.", + ) + + response = mk.api_print(radius_path) + radius_params = module.params + + ######################################################## + # Check if we need to edit the incoming radius settings + ######################################################## + if radius_params['incoming'] is not None: + incoming_path = '/radius/incoming' + incoming_response = mk.api_print(incoming_path) + incoming = radius_params['incoming'] + if incoming_response[0][1]['accept'] == incoming['accept']: + if incoming_response[0][1]['port'] == incoming['port']: + # nothing to do + pass + else: + # edit port + mk.api_edit(base_path=incoming_path, params=incoming) + else: + # edit the accept and the port + mk.api_edit(base_path=incoming_path, params=incoming) + ####################################### + # Since we are grabbing all the parameters passed by the module + # We need to remove the one that won't be used + # as mikrotik parameters + remove_params = ['hostname', 'username', 'password', 'state', 'incoming'] + for i in remove_params: + radius_params.pop(i) + ####################################### + # remove keys with empty values + # convert service list to stings + ###################################### + for key in radius_params.keys(): + if radius_params[key] is None: + radius_params.pop(key) + + + ################################################# + # Convert service list to comma separated string + ################################################# + list_to_string = "" + if 'service' in radius_params: + list_to_string = ','.join(map(str, radius_params['service'])) + radius_params['service'] = list_to_string + + ################################################ + # mikrotik_radius is the dictionary with the parameters + # we get from mikrotik + ################################# + # We grab the first radius item to + # match the comment + ################################# + mikrotik_radius = {} + for i in response: + if 'comment' in i[1]: + if i[1]['comment'] == radius_params['comment']: + mikrotik_radius = i[1] + break + + ########################################################## + # Define radius_id to be used by remove and edit function + ########################################################## + if '.id' in mikrotik_radius: + radius_id = mikrotik_radius['.id'] + else: + radius_id = False + + ###################################################### + # If the state is present and we can't find matching + # radius comment we add a new item with all the parameters + # from Ansible + ####################################################### + if state == "present": + if mikrotik_radius == {}: + mk.api_add(base_path=radius_path, params=radius_params) + module.exit_json( + failed=False, + changed=True, + msg="Added radius item", + ) + ################################################### + # If an item exists we check if all the parameters + # match what we have in ansible + ###################################### + else: + radius_diff_keys = {} + for key in radius_params: + if radius_params[key] != mikrotik_radius[key]: + radius_diff_keys[key] = radius_params[key] + if radius_diff_keys != {}: + radius_diff_keys['numbers'] = radius_id + mk.api_edit(base_path=radius_path, params=radius_diff_keys) + module.exit_json( + failed=False, + changed=True, + msg="Changed radius item: " + radius_params['comment'] + ) + else: + #################### + # Already up date + module.exit_json( + failed=False, + changed=False, + ) + elif state == "absent": + if radius_id: + mk.api_remove(base_path=radius_path, remove_id=radius_id) + module.exit_json( + failed=False, + changed=True, + msg=radius_params['comment'] + " removed" + ) + ##################################################### + # if radius_id is not set there is nothing to remove + ##################################################### + else: + module.exit_json( + failed=False, + changed=False, + ) + else: + module.exit_json( + failed=True, + changed=False, + ) +if __name__ == '__main__': + main() diff --git a/library/mt_service.py b/library/mt_service.py new file mode 100644 index 0000000..459d967 --- /dev/null +++ b/library/mt_service.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_service +author: + - "Valentin Gurmeza" + - "Shaun Smiley" +version_added: "2.3" +short_description: Manage mikrotik ip service +requirements: + - mt_api +description: + - enable, disable, or modify a ip service +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + disabled: + description: + - service enabled or disabled + required: True + choices: + - no + - yes + name: + description: + - name of the service + required: True + choices: + - api + - api-ssl + - ftp + - ssh + - telnet + - winbox + - www + - www-ssl + address: + description: + - List of IP/IPv6 prefixes from which the service is accessible + certificate: + description: + - The name of the certificate used by particular service. Applicable only for services that depends on certificates (www-ssl, api-ssl) + port: + description: + - The port particular service listens on +''' + +EXAMPLES = ''' +- mt_service: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + disabled: no + name: ftp + address: 192.168.52.3 +''' + +import mt_api +import re +from ansible.module_utils.basic import AnsibleModule + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + interface = dict(required=False, type='str'), + address = dict(required=False, type='str'), + certificate= dict(required=False, type='str'), + name = dict( + required=True, + choices=[ + 'api', + 'api-ssl', + 'ftp', + 'ssh', + 'telnet', + 'winbox', + 'www', + 'www-ssl' + ], + type='str' + ), + disabled=dict( + required = True, + choices = ['yes', 'no'], + type = 'str' + ), + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + ansible_service_name = module.params['name'] + changed = False + msg = "" + + mk = mt_api.Mikrotik(hostname, username, password) + try: + mk.login() + except: + module.fail_json( + msg="Could not log into Mikrotik device." + + " Check the username and password.", + ) + + service_path = '/ip/service' + + response = mk.api_print(base_path=service_path) + service_params = module.params + mikrotik_service = {} + for item in response: + if 'name' in item[1].keys(): + if ansible_service_name == item[1]['name']: + mikrotik_service = item[1] + + ####################################### + # remove keys with empty values + # remove unneeded parameters + # modify keys with '_' to match mikrotik parameters + # convert yes/no to true/false + ###################################### + + remove_params = ['hostname', 'username', 'password'] + for i in remove_params: + del service_params[i] + + for key in service_params.keys(): + if service_params[key] is None: + del service_params[key] + + for key in service_params: + if service_params[key] == "yes": + service_params[key] = "true" + if service_params[key] == "no": + service_params[key] = "false" + + if '.id' in mikrotik_service: + client_id = mikrotik_service['.id'] + else: + client_id = False + + service_diff_keys = {} + + for key in service_params: + if key in mikrotik_service: + if service_params[key] != mikrotik_service[key]: + service_diff_keys[key] = service_params[key] + else: + service_diff_keys[key] = service_params[key] + if service_diff_keys == {}: + #################### + # Already up date + ################### + module.exit_json( + failed=False, + changed=False, + ) + elif service_diff_keys != {}: + service_diff_keys['numbers'] = client_id + mk.api_edit(base_path=service_path, params=service_diff_keys) + module.exit_json( + failed=False, + changed=True, + msg="Changed service item: " + service_params['name'], + ) + else: + #################### + # Failure + ################### + module.exit_json( + failed=True, + changed=False + ) + +if __name__ == '__main__': + main() diff --git a/library/mt_snmp.py b/library/mt_snmp.py new file mode 100644 index 0000000..c9ee049 --- /dev/null +++ b/library/mt_snmp.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_snmp +author: + - "Valentin Gurmeza" +version_added: "2.4" +short_description: Manage mikrotik snmp endpoints +requirements: + - mt_api +description: + - Generic mikrotik snmp module. +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + parameter: + description: + - sub endpoint for mikrotik snmp + required: True + options: + - netwatch + - e-mail + settings: + description: + - All Mikrotik compatible parameters for this particular endpoint. + Any yes/no values must be enclosed in double quotes + state: + description: + - absent or present +''' + +EXAMPLES = ''' +- mt_snmp: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + parameter: e-mail + settings: + address: 192.168.1.1 + from: foo@bar.com +''' + +from mt_common import clean_params, MikrotikIdempotent +from ansible.module_utils.basic import AnsibleModule + + + +def main(): + module = AnsibleModule( + argument_spec = dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + settings = dict(required=False, type='dict'), + parameter = dict( + required = True, + choices = ['community', 'snmp'], + type = 'str' + ), + state = dict( + required = False, + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + idempotent_parameter = None + params = module.params + + + if params['parameter'] == 'community': + idempotent_parameter = 'name' + params['parameter'] = "snmp/community" + + mt_obj = MikrotikIdempotent( + hostname = params['hostname'], + username = params['username'], + password = params['password'], + state = params['state'], + desired_params = params['settings'], + idempotent_param = idempotent_parameter, + api_path = '/' + str(params['parameter']), + + ) + + mt_obj.sync_state() + + if mt_obj.failed: + module.fail_json( + msg = mt_obj.failed_msg + ) + elif mt_obj.changed: + module.exit_json( + failed=False, + changed=True, + msg=mt_obj.changed_msg, + diff={ "prepared": { + "old": mt_obj.old_params, + "new": mt_obj.new_params, + }}, + ) + else: + module.exit_json( + failed=False, + changed=False, + #msg='', + msg=params['settings'], + ) + +if __name__ == '__main__': + main() diff --git a/library/mt_system.py b/library/mt_system.py new file mode 100644 index 0000000..d2cc72e --- /dev/null +++ b/library/mt_system.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_system.py +author: + - "Valentin Gurmeza" +version_added: "2.4" +short_description: Manage mikrotik system endpoints +requirements: + - mt_api +description: + - manage mikrotik system parameters +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + parameter: + description: + - sub enpoint for mikrotik system + required: True + options: + - ntp_client + - clock + - logging + - routerboard + - identity + settings: + description: + - All Mikrotik compatible parameters for this particular endpoint. + Any yes/no values must be enclosed in double quotes + state: + description: + - absent or present +''' + +EXAMPLES = ''' +- mt_system: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + parameter: identity + settings: + name: test_ansible +''' + +from mt_common import clean_params, MikrotikIdempotent +from ansible.module_utils.basic import AnsibleModule + + + +def main(): + module = AnsibleModule( + argument_spec = dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + settings = dict(required=False, type='dict'), + parameter = dict( + required = True, + choices = ['ntp_client', 'clock', 'identity', 'logging', 'routerboard_settings'], + type = 'str' + ), + state = dict( + required = False, + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + params = module.params + + if params['parameter'] == 'routerboard_settings': + params['parameter'] = 'routerboard/settings' + + if params['parameter'] == 'ntp_client': + params['parameter'] = 'ntp/client' + + clean_params(params['settings']) + mt_obj = MikrotikIdempotent( + hostname = params['hostname'], + username = params['username'], + password = params['password'], + state = params['state'], + desired_params = params['settings'], + idempotent_param= None, + api_path = '/system/' + params['parameter'], + ) + + mt_obj.sync_state() + + if mt_obj.failed: + module.fail_json( + msg = mt_obj.failed_msg + ) + elif mt_obj.changed: + module.exit_json( + failed=False, + changed=True, + msg=mt_obj.changed_msg, + diff={ "prepared": { + "old": mt_obj.old_params, + "new": mt_obj.new_params, + }}, + ) + else: + module.exit_json( + failed=False, + changed=False, + #msg='', + msg=params['settings'], + ) + +if __name__ == '__main__': + main() diff --git a/library/mt_system_scheduler.py b/library/mt_system_scheduler.py new file mode 100644 index 0000000..cd0cf9b --- /dev/null +++ b/library/mt_system_scheduler.py @@ -0,0 +1,232 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_system_scheduler +author: + - "Valentin Gurmeza" +version_added: "2.3" +short_description: Manage mikrotik system scheduler +requirements: + - mt_api +description: + - add, remove, or modify a system scheduler task +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + state: + description: + - scheduler task present or absent + required: True + choices: + - present + - absent + comment: + description: + - task comment + interval: + description: + - interval between two script executions, if time interval is set to zero, the script is only executed at its start time, otherwise it is executed repeatedly at the time interval is specified + name: + description: + - name of the task + required: True + on_event: + description: + - name of the script to execute. + start_date: + description: + - date of the first script execution + start_time: + description: + - time of the first script execution + policy: + description: + - policy +''' + +EXAMPLES = ''' +- mt_system_scheduler: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + state: present + name: test_by_ansible + comment: ansible_test + on_event: put "hello" +''' + +import mt_api +from mt_common import clean_params +from ansible.module_utils.basic import AnsibleModule + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + hostname =dict(required=True), + username =dict(required=True), + password =dict(required=True), + name =dict(required=True, type='str'), + on_event =dict(required=False, type='str'), + comment =dict(required=False, type='str'), + interval =dict(required=False, type='str'), + policy =dict(required=False, type='list'), + start_date=dict(required=False, type='str'), + start_time=dict(required=False, type='str'), + state=dict( + required=True, + choices=['present', 'absent'], + type='str' + ), + ) + ) + + hostname = module.params['hostname'] + username = module.params['username'] + password = module.params['password'] + state = module.params['state'] + ansible_scheduler_name = module.params['name'] + changed = False + changed_message = [] + msg = "" + + mk = mt_api.Mikrotik(hostname, username, password) + try: + mk.login() + except: + module.fail_json( + msg="Could not log into Mikrotik device." + + " Check the username and password.", + ) + + api_path = '/system/scheduler' + + response = mk.api_print(base_path=api_path) + ansible_scheduler_params = module.params + mikrotik_scheduler_task = {} + for item in response: + if 'name' in item[1]: + if ansible_scheduler_name == item[1]['name']: + mikrotik_scheduler_task = item[1] + + ####################################### + # remove unneeded parameters + ###################################### + + remove_params = ['hostname', 'username', 'password', 'state'] + for i in remove_params: + del ansible_scheduler_params[i] + + + ########################################## + # modify params in place + ############################################ + clean_params(ansible_scheduler_params) + + + if '.id' in mikrotik_scheduler_task: + client_id = mikrotik_scheduler_task['.id'] + else: + client_id = False + + if state == "present": + ################################################# + # Convert policy list to comma separated string + ################################################# + + if mikrotik_scheduler_task == {}: + if 'policy' in ansible_scheduler_params: + list_to_string = "" + list_to_string = ','.join(map(str, ansible_scheduler_params['policy'])) + ansible_scheduler_params['policy'] = list_to_string + mk.api_add( + base_path=api_path, + params=ansible_scheduler_params + ) + changed_message.append(ansible_scheduler_name + " added to bridge") + changed = True, + else: + scheduler_diff_keys = {} + + ######################################################################## + # policy parameter is a comma separated list of values in a string that + # we receive from mikrotik + # we need to convert it to a list and then do a comparison against + # ansible policy list to get the difference + # if there is a difference between the two we need to convert the + # ansible_scheduler_params['policy'] to a string with comma separated values + ######################################################################### + + if 'policy' in ansible_scheduler_params: + dif_list = [] + if 'policy' in mikrotik_scheduler_task: + policy = mikrotik_scheduler_task['policy'].split(',') + dif_list = set(ansible_scheduler_params['policy']) & set(policy) + + if dif_list == []: + list_to_string = "" + list_to_string = ','.join(map(str, ansible_scheduler_params['policy'])) + scheduler_diff_keys['policy'] = list_to_string + + for key in ansible_scheduler_params: + if key != 'policy': + if key in mikrotik_scheduler_task: + if ansible_scheduler_params[key] != mikrotik_scheduler_task[key]: + scheduler_diff_keys[key] = ansible_scheduler_params[key] + else: + scheduler_diff_keys[key] = ansible_scheduler_params[key] + if scheduler_diff_keys != {}: + scheduler_diff_keys['numbers'] = client_id + mk.api_edit(base_path=api_path, params=scheduler_diff_keys) + changed = True + changed_message.append( + "Changed scheduler task : " + ansible_scheduler_params['name'] + ) + else: + #################### + # Already up date + ################### + if not changed: + changed = False + + elif state == "absent": + if client_id: + mk.api_remove(base_path=api_path, remove_id=client_id) + changed_message.append(ansible_scheduler_params['name'] + " removed") + changed = True + ##################################################### + # if client_id is not set there is nothing to remove + ##################################################### + else: + if not changed: + changed = False + else: + module.exit_json( + failed=True, + changed=False, + msg="state is invalid" + ) + + if changed: + module.exit_json( + failed=False, + changed=True, + msg=changed_message + ) + else: + module.exit_json( + failed=False, + changed=False, + ) +if __name__ == '__main__': + main() diff --git a/library/mt_tool.py b/library/mt_tool.py new file mode 100644 index 0000000..73ca5af --- /dev/null +++ b/library/mt_tool.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_tool +author: + - "Valentin Gurmeza" +version_added: "2.4" +short_description: Manage mikrotik tool endpoints +requirements: + - mt_api +description: + - Generic mikrotik tool module. +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + parameter: + description: + - sub endpoint for mikrotik tool + required: True + options: + - netwatch + - e-mail + settings: + description: + - All Mikrotik compatible parameters for this particular endpoint. + Any yes/no values must be enclosed in double quotes + state: + description: + - absent or present +''' + +EXAMPLES = ''' +- mt_tool: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + parameter: e-mail + settings: + address: 192.168.1.1 + from: foo@bar.com +''' + +from mt_common import clean_params, MikrotikIdempotent +from ansible.module_utils.basic import AnsibleModule + + + +def main(): + module = AnsibleModule( + argument_spec = dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + settings = dict(required=False, type='dict'), + parameter = dict( + required = True, + choices = ['e-mail', 'netwatch'], + type = 'str' + ), + state = dict( + required = False, + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + idempotent_parameter = None + params = module.params + + if params['parameter'] == 'netwatch': + idempotent_parameter = 'host' + +# clean_params(params['settings']) + + mt_obj = MikrotikIdempotent( + hostname = params['hostname'], + username = params['username'], + password = params['password'], + state = params['state'], + desired_params = params['settings'], + idempotent_param = idempotent_parameter, + api_path = '/tool/' + str(params['parameter']), + + ) + + mt_obj.sync_state() + + if mt_obj.failed: + module.fail_json( + msg = mt_obj.failed_msg + ) + elif mt_obj.changed: + module.exit_json( + failed=False, + changed=True, + msg=mt_obj.changed_msg, + diff={ "prepared": { + "old": mt_obj.old_params, + "new": mt_obj.new_params, + }}, + ) + else: + module.exit_json( + failed=False, + changed=False, + #msg='', + msg=params['settings'], + ) + +if __name__ == '__main__': + main() diff --git a/library/mt_user.py b/library/mt_user.py new file mode 100644 index 0000000..417c59b --- /dev/null +++ b/library/mt_user.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +DOCUMENTATION = ''' +module: mt_user +author: + - "Valentin Gurmeza" +version_added: "2.4" +short_description: Manage mikrotik user endpoints +requirements: + - mt_api +description: + - Generic mikrotik user module. +options: + hostname: + description: + - hotstname of mikrotik router + required: True + username: + description: + - username used to connect to mikrotik router + required: True + password: + description: + - password used for authentication to mikrotik router + required: True + parameter: + description: + - sub endpoint for mikrotik user + required: True + options: + - user + - active + - aaa + settings: + description: + - All Mikrotik compatible parameters for this particular endpoint. + Any yes/no values must be enclosed in double quotes + state: + description: + - absent or present +''' + +EXAMPLES = ''' +- mt_user: + hostname: "{{ inventory_hostname }}" + username: "{{ mt_user }}" + password: "{{ mt_pass }}" + parameter: user + settings: + name: test1 + group: read +''' + +from mt_common import clean_params, MikrotikIdempotent +from ansible.module_utils.basic import AnsibleModule + + + +def main(): + module = AnsibleModule( + argument_spec = dict( + hostname = dict(required=True), + username = dict(required=True), + password = dict(required=True), + settings = dict(required=False, type='dict'), + parameter = dict( + required = True, + choices = ['user', 'active', 'aaa'], + type = 'str' + ), + state = dict( + required = False, + choices = ['present', 'absent'], + type = 'str' + ), + ) + ) + + idempotent_parameter = None + params = module.params + idempotent_parameter = 'name' + + if params['parameter'] == 'group': + params['parameter'] = 'user/group' + mt_obj = MikrotikIdempotent( + hostname = params['hostname'], + username = params['username'], + password = params['password'], + state = params['state'], + desired_params = params['settings'], + idempotent_param = idempotent_parameter, + api_path = '/' + str(params['parameter']), + + ) + + mt_obj.sync_state() + + if mt_obj.failed: + module.fail_json( + msg = mt_obj.failed_msg + ) + elif mt_obj.changed: + module.exit_json( + failed=False, + changed=True, + msg=mt_obj.changed_msg, + diff={ "prepared": { + "old": mt_obj.old_params, + "new": mt_obj.new_params, + }}, + ) + else: + module.exit_json( + failed=False, + changed=False, + #msg='', + msg=params['settings'], + ) + +if __name__ == '__main__': + main()