#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright: (c) 2019, Kevin Breit (@kbreit) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type ANSIBLE_METADATA = { 'metadata_version': '1.1', "status": ['deprecated'], 'supported_by': 'community' } DOCUMENTATION = r''' --- module: meraki_management_interface short_description: Configure Meraki management interfaces version_added: "1.1.0" description: - Allows for configuration of management interfaces on Meraki MX, MS, and MR devices. notes: - C(WAN2) parameter is only valid for MX appliances. - C(wan_enabled) should not be provided for non-MX devies. deprecated: removed_in: '3.0.0' why: Updated modules released with increased functionality alternative: cisco.meraki.devices_management_interface options: state: description: - Specifies whether configuration template information should be queried, modified, or deleted. choices: ['absent', 'query', 'present'] default: query type: str org_name: description: - Name of organization containing the configuration template. type: str org_id: description: - ID of organization associated to a configuration template. type: str net_name: description: - Name of the network to bind or unbind configuration template to. type: str net_id: description: - ID of the network to bind or unbind configuration template to. type: str serial: description: - serial number of the device to configure. type: str required: true wan1: description: - Management interface details for management interface. aliases: [mgmt1] type: dict suboptions: wan_enabled: description: - States whether the management interface is enabled. - Only valid for MX devices. type: str choices: [disabled, enabled, not configured] using_static_ip: description: - Configures the interface to use static IP or DHCP. type: bool static_ip: description: - IP address assigned to Management interface. - Valid only if C(using_static_ip) is C(True). type: str static_gateway_ip: description: - IP address for default gateway. - Valid only if C(using_static_ip) is C(True). type: str static_subnet_mask: description: - Netmask for static IP address. - Valid only if C(using_static_ip) is C(True). type: str static_dns: description: - DNS servers to use. - Allows for a maximum of 2 addresses. type: list elements: str vlan: description: - VLAN number to use for the management network. type: int wan2: description: - Management interface details for management interface. type: dict aliases: [mgmt2] suboptions: wan_enabled: description: - States whether the management interface is enabled. - Only valid for MX devices. type: str choices: [disabled, enabled, not configured] using_static_ip: description: - Configures the interface to use static IP or DHCP. type: bool static_ip: description: - IP address assigned to Management interface. - Valid only if C(using_static_ip) is C(True). type: str static_gateway_ip: description: - IP address for default gateway. - Valid only if C(using_static_ip) is C(True). type: str static_subnet_mask: description: - Netmask for static IP address. - Valid only if C(using_static_ip) is C(True). type: str static_dns: description: - DNS servers to use. - Allows for a maximum of 2 addresses. type: list elements: str vlan: description: - VLAN number to use for the management network. type: int author: - Kevin Breit (@kbreit) extends_documentation_fragment: cisco.meraki.meraki ''' EXAMPLES = r''' - name: Set WAN2 as static IP meraki_management_interface: auth_key: abc123 state: present org_name: YourOrg net_id: YourNetId serial: AAAA-BBBB-CCCC wan2: wan_enabled: enabled using_static_ip: yes static_ip: 192.168.16.195 static_gateway_ip: 192.168.16.1 static_subnet_mask: 255.255.255.0 static_dns: - 1.1.1.1 vlan: 1 delegate_to: localhost - name: Query management information meraki_management_interface: auth_key: abc123 state: query org_name: YourOrg net_id: YourNetId serial: AAAA-BBBB-CCCC delegate_to: localhost ''' RETURN = r''' data: description: Information about queried object. returned: success type: complex contains: wan1: description: Management configuration for WAN1 interface returned: success type: complex contains: wan_enabled: description: Enabled state of interface returned: success type: str sample: enabled using_static_ip: description: Boolean value of whether static IP assignment is used on interface returned: success type: bool sample: True static_ip: description: Assigned static IP returned: only if static IP assignment is used type: str sample: 192.0.1.2 static_gateway_ip: description: Assigned static gateway IP returned: only if static IP assignment is used type: str sample: 192.0.1.1 static_subnet_mask: description: Assigned netmask for static IP returned: only if static IP assignment is used type: str sample: 255.255.255.0 static_dns: description: List of DNS IP addresses returned: only if static IP assignment is used type: list sample: ["1.1.1.1"] vlan: description: VLAN tag id of management VLAN returned: success type: int sample: 2 wan2: description: Management configuration for WAN1 interface returned: success type: complex contains: wan_enabled: description: Enabled state of interface returned: success type: str sample: enabled using_static_ip: description: Boolean value of whether static IP assignment is used on interface returned: success type: bool sample: True static_ip: description: Assigned static IP returned: only if static IP assignment is used type: str sample: 192.0.1.2 static_gateway_ip: description: Assigned static gateway IP returned: only if static IP assignment is used type: str sample: 192.0.1.1 static_subnet_mask: description: Assigned netmask for static IP returned: only if static IP assignment is used type: str sample: 255.255.255.0 static_dns: description: List of DNS IP addresses returned: only if static IP assignment is used type: list sample: ["1.1.1.1"] vlan: description: VLAN tag id of management VLAN returned: success type: int sample: 2 ''' from ansible.module_utils.basic import AnsibleModule, json from ansible.module_utils.common.dict_transformations import recursive_diff from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec def main(): # define the available arguments/parameters that a user can pass to # the module int_arg_spec = dict(wan_enabled=dict(type='str', choices=['enabled', 'disabled', 'not configured']), using_static_ip=dict(type='bool'), static_ip=dict(type='str'), static_gateway_ip=dict(type='str'), static_subnet_mask=dict(type='str'), static_dns=dict(type='list', elements='str'), vlan=dict(type='int'), ) argument_spec = meraki_argument_spec() argument_spec.update(state=dict(type='str', choices=['absent', 'query', 'present'], default='query'), net_name=dict(type='str'), net_id=dict(type='str'), serial=dict(type='str', required=True), wan1=dict(type='dict', default=None, options=int_arg_spec, aliases=['mgmt1']), wan2=dict(type='dict', default=None, options=int_arg_spec, aliases=['mgmt2']), ) # the AnsibleModule object will be our abstraction working with Ansible # this includes instantiation, a couple of common attr would be the # args/params passed to the execution, as well as if the module # supports check mode module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, ) meraki = MerakiModule(module, function='management_interface') meraki.params['follow_redirects'] = 'all' query_urls = {'management_interface': '/devices/{serial}/managementInterface'} meraki.url_catalog['get_one'].update(query_urls) if meraki.params['net_id'] and meraki.params['net_name']: meraki.fail_json('net_id and net_name are mutually exclusive.') if meraki.params['state'] == 'present': interfaces = ('wan1', 'wan2') for interface in interfaces: if meraki.params[interface] is not None: if meraki.params[interface]['using_static_ip'] is True: if len(meraki.params[interface]['static_dns']) > 2: meraki.fail_json("Maximum number of static DNS addresses is 2.") payload = dict() if meraki.params['state'] == 'present': interfaces = ('wan1', 'wan2') for interface in interfaces: if meraki.params[interface] is not None: wan_int = dict() if meraki.params[interface]['wan_enabled'] is not None: wan_int['wanEnabled'] = meraki.params[interface]['wan_enabled'] if meraki.params[interface]['using_static_ip'] is not None: wan_int['usingStaticIp'] = meraki.params[interface]['using_static_ip'] if meraki.params[interface]['vlan'] is not None: wan_int['vlan'] = meraki.params[interface]['vlan'] if meraki.params[interface]['using_static_ip'] is True: wan_int['staticIp'] = meraki.params[interface]['static_ip'] wan_int['staticGatewayIp'] = meraki.params[interface]['static_gateway_ip'] wan_int['staticSubnetMask'] = meraki.params[interface]['static_subnet_mask'] wan_int['staticDns'] = meraki.params[interface]['static_dns'] payload[interface] = wan_int # manipulate or modify the state as needed (this is going to be the # part where your module will do what it needs to do) org_id = meraki.params['org_id'] if meraki.params['org_name']: org_id = meraki.get_org_id(meraki.params['org_name']) net_id = meraki.params['net_id'] if net_id is None: nets = meraki.get_nets(org_id=org_id) net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets) if meraki.params['state'] == 'query': path = meraki.construct_path('get_one', net_id=net_id, custom={'serial': meraki.params['serial']}) response = meraki.request(path, method='GET') if meraki.status == 200: meraki.result['data'] = response elif meraki.params['state'] == 'present': path = meraki.construct_path('get_one', custom={'serial': meraki.params['serial']}) original = meraki.request(path, method='GET') update_required = False if 'wan1' in original: if 'wanEnabled' in original['wan1']: update_required = meraki.is_update_required(original, payload) else: update_required = meraki.is_update_required(original, payload, optional_ignore=['wanEnabled']) if 'wan2' in original and update_required is False: if 'wanEnabled' in original['wan2']: update_required = meraki.is_update_required(original, payload) else: update_required = meraki.is_update_required(original, payload, optional_ignore=['wanEnabled']) if update_required is True: if meraki.check_mode is True: diff = recursive_diff(original, payload) original.update(payload) meraki.result['diff'] = {'before': diff[0], 'after': diff[1]} meraki.result['data'] = original meraki.result['changed'] = True meraki.exit_json(**meraki.result) response = meraki.request(path, method='PUT', payload=json.dumps(payload)) if meraki.status == 200: diff = recursive_diff(original, response) meraki.result['diff'] = {'before': diff[0], 'after': diff[1]} meraki.result['data'] = response meraki.result['changed'] = True else: meraki.result['data'] = original # in the event of a successful module execution, you will want to # simple AnsibleModule.exit_json(), passing the key/value results meraki.exit_json(**meraki.result) if __name__ == '__main__': main()