123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- #!/usr/bin/env python
- #
- # Copyright (c) 2018 Joe Clarke <jclarke@cisco.com>
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions
- # are met:
- # 1. Redistributions of source code must retain the above copyright
- # notice, this list of conditions and the following disclaimer.
- # 2. Redistributions in binary form must reproduce the above copyright
- # notice, this list of conditions and the following disclaimer in the
- # documentation and/or other materials provided with the distribution.
- #
- # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- # SUCH DAMAGE.
- import meraki_api
- import yaml
- import argparse
- import sys
- import os
- from colorama import Fore, Back, Style
- import colorama
- BANNER = '[{}] **********************************************************'
- def main():
- parser = argparse.ArgumentParser(
- prog=sys.argv[0], description='Add devices to network')
- parser.add_argument('--config', '-c', metavar='<CONFIG FILE>',
- help='Path to the organization configuration file', required=True)
- parser.add_argument(
- '--networks', '-n', metavar='<NETWORK>[,<NETWORK>[,...]]', help='Comma-separated list of networks to process')
- args = parser.parse_args()
- colorama.init()
- if not os.path.isfile(args.config):
- print('Config file {} does not exist or is not a file!'.format(args.config))
- sys.exit(1)
- print(BANNER.format('Loading config file'))
- with open(args.config, 'r') as c:
- config = yaml.load(c)
- print('{}ok{}\n'.format(Fore.GREEN, Style.RESET_ALL))
- for key in ['api_key', 'organization', 'networks']:
- if not key in config:
- print('Invalid config: {} is missing!'.format(key))
- sys.exit(1)
- meraki = meraki_api.Meraki(key=config['api_key'])
- orgs = meraki.get_organizations()
- org = None
- for o in orgs:
- if o.get('name') == config['organization']:
- org = o
- break
- if org is None:
- print('Failed to find organization {} in this profile!'.format(
- config['organization']))
- sys.exit(1)
- nets = org.get_networks()
- inv = org.get_inventory()
- errors = 0
- configure_nets = None
- if args.networks is not None:
- configure_nets = args.networks.split(',')
- for net in config['networks']:
- nerrors = 0
- net_obj = None
- nname = net.keys()[0]
- print(BANNER.format('Configuring network {}'.format(nname)))
- if configure_nets is not None and nname not in configure_nets:
- print('{}skipping (not in specified network list){}'.format(
- Fore.BLUE, Style.RESET_ALL))
- continue
- validn = True
- for key in ['address', 'timezone']:
- if key not in net[nname]:
- print('{}Invalid network config for {}: {} is missing!{}'.format(
- Fore.RED, nname, key, Style.RESET_ALL))
- errors += 1
- validn = False
- break
- if not validn:
- continue
- for n in nets:
- if n.get('name') == nname:
- net_obj = n
- break
- if net_obj is None:
- nargs = {
- 'timezone': net[nname]['timezone']
- }
- if 'copy_from_network' in net[nname]:
- for n in nets:
- if n.get('name') == net[nname]['copy_from_network']:
- nargs['copy_from_network_id'] = n.get('id')
- break
- net_obj = org.create_network(
- nname, **nargs)
- if net_obj is None:
- print('{}Error creating new network {}!{}'.format(
- Fore.RED, nname, Style.RESET_ALL))
- errors += 1
- continue
- if 'devices' in net[nname]:
- for dev in net[nname]['devices']:
- serial = dev.keys()[0]
- if 'name' not in dev[serial]:
- print('{}Invalid device {}: name is missing!{}'.format(
- Fore.RED, serial, Style.RESET_ALL))
- nerrors += 1
- continue
- inv_dev = filter(
- lambda device: device['serial'] == serial, inv)
- if len(inv_dev) == 1:
- dev_obj = None
- if inv_dev[0]['networkId'] is not None and inv_dev[0]['networkId'] != net_obj.get('id'):
- try:
- inv_net_obj = meraki_api.Network(
- key=config['api_key'], id=inv_dev[0]['networkId'])
- dev_obj = meraki_api.Device(
- key=config['api_key'], id=inv_dev[0]['serial'], net=inv_net_obj)
- res = dev_obj.remove_device()
- if not res:
- print('{}Error removing {} from network {}!{}'.format(
- Fore.RED, inv_dev[0]['serial'], inv_dev[0]['networkId'], Style.RESET_ALL))
- nerrors += 1
- continue
- print('{}update: removed {} from network {}{}'.format(
- Fore.YELLOW, inv_dev[0]['serial'], inv_dev[0]['networkId'], Style.RESET_ALL))
- res = net_obj.claim_device(dev_obj)
- if not res:
- print('{}Error claiming {}!{}'.format(
- Fore.RED, inv_dev[0]['serial'], Style.RESET_ALL))
- nerrors += 1
- continue
- print('{}update: claimed {}{}'.format(
- Fore.YELLOW, inv_dev[0]['serial'], Style.RESET_ALL))
- dev_obj = res
- except Exception as e:
- print('{}Error updating device network membership for {}: {}{}'.format(
- Fore.RED, inv_dev[0]['serial'], e, Style.RESET_ALL))
- nerrors += 1
- continue
- elif inv_dev[0]['networkId'] is None:
- try:
- dev_obj = meraki_api.Device(
- key=config['api_key'], id=inv_dev[0]['serial'], net=net_obj)
- res = net_obj.claim_device(dev_obj)
- if not res:
- print('{}Error claiming device {}{}'.format(
- Fore.RED, inv_dev[0]['serial'], Style.RESET_ALL))
- nerrors += 1
- continue
- print('{}update: claimed {}{}'.format(
- Fore.YELLOW, inv_dev[0]['serial'], Style.RESET_ALL))
- dev_obj = res
- except Exception as e:
- print('{}Error claiming device {}: {}{}'.format(
- Fore.RED, inv_dev[0]['serial'], e, Style.RESET_ALL))
- nerrors += 1
- continue
- else:
- dev_obj = meraki_api.Device(
- key=config['api_key'], id=inv_dev[0]['serial'], net=net_obj)
- print('{}ok: {} is in network{}'.format(
- Fore.GREEN, inv_dev[0]['serial'], Style.RESET_ALL))
- dev_location = net[nname]['address']
- dev_name = dev[serial]['name']
- if 'location' in dev[serial]:
- dev_location += '\n' + dev[serial]['location']
- dev_obj.update_device(
- name=dev_name, address=dev_location, move_map_marker=True)
- print('{}update: updated {} name and location{}'.format(
- Fore.YELLOW, inv_dev[0]['serial'], Style.RESET_ALL))
- else:
- print('{}Error finding {} in inventory!{}'.format(
- Fore.RED, serial, Style.RESET_ALL))
- nerrors += 1
- if 'vlans' in net[nname]:
- # Ugh. There is no API to enable VLANs yet. So it's best to
- # make this a manual step. We could interact over the web, but
- # then we'd need to ask for a real user's credentials.
- #
- # If we copied from an existing network, then we assume that
- # network has VLANs enabled. If not, this will fail.
- #
- if 'copy_from_network' not in net[nname]:
- print('\n')
- raw_input(
- '!!! Enable VLANs for network "{}" manually in the dashboard (under Security appliance > Addressing & VLANs), then hit enter to proceed !!!'.format(nname))
- print('')
- for vlan in net[nname]['vlans']:
- vname = vlan.keys()[0]
- done_msg = ''
- if int(vlan[vname]['id']) != 1:
- vlan_obj = net_obj.create_vlan(
- vname, vlan[vname]['id'], vlan[vname]['subnet'], vlan[vname]['appliance_ip'])
- done_msg = '{}update: created VLAN {} (id={}, subnet={}, appliance_ip={}){}'.format(
- Fore.YELLOW, vname, vlan[vname]['id'], vlan[vname]['subnet'], vlan[vname]['appliance_ip'], Style.RESET_ALL)
- else:
- vlan_obj = meraki_api.Vlan(
- key=config['api_key'], id=1, net=net_obj)
- done_msg = '{}ok: VLAN with ID {} exists{}'.format(
- Fore.GREEN, vlan[vname]['id'], Style.RESET_ALL)
- if vlan_obj is None:
- print('{}Error creating VLAN {} (id={}, subnet={}, appliance_ip={})!{}'.format(
- Fore.RED, vlan[vname]['id'], vlan[vname]['subnet'], vlan[vname]['appliance_ip'], Style.RESET_ALL))
- nerrors += 1
- continue
- print(done_msg)
- vargs = {}
- for key in ['reserved_ip_ranges', 'fixed_ip_assignments', 'dns_nameservers']:
- if key in vlan[vname]:
- vargs[key] = vlan[vname][key]
- res = vlan_obj.update_vlan(**vargs)
- vargs_str = ', '.join(['{}={}'.format(k, v)
- for k, v in vargs.iteritems()])
- if not res:
- print('{}Error updating VLAN {} ({})!{}'.format(
- Fore.RED, vname, vargs_str, Style.RESET_ALL))
- nerrors += 1
- else:
- print('{}update: Update VLAN {} ({}){}'.format(
- Fore.YELLOW, vname, vargs_str, Style.RESET_ALL))
- if 'ssids' in net[nname]:
- if len(net[nname]['ssids']) > 15:
- print('{}Only fifteen SSIDs are allowed per network!{}'.format(
- Fore.RED, Style.RESET_ALL))
- nerrors += 1
- else:
- si = 0
- for ssid in net[nname]['ssids']:
- sname = ssid.keys()[0]
- ssid_obj = meraki_api.SSID(
- key=config['api_key'], id=si, name=sname, net=net_obj)
- sargs = {}
- for key in ['name', 'enabled', 'auth_mode', 'encryption_mode', 'psk', 'ip_assignment_mode']:
- if key in ssid[sname]:
- sargs[key] = ssid[sname][key]
- res = ssid_obj.update_ssid(**sargs)
- sargs_str = ', '.join(
- ['{}={}'.format(k, v) for k, v in sargs.iteritems()])
- if not res:
- print('{}Error updating SSID {} ({})!{}'.format(
- Fore.RED, sname, sargs_str, Style.RESET_ALL))
- nerrors += 1
- else:
- print('{}update: Update SSID {} ({}){}'.format(
- Fore.YELLOW, sname, sargs_str, Style.RESET_ALL))
- si += 1
- if 'switches' in net[nname]:
- for switch in net[nname]['switches']:
- serial = switch.keys()[0]
- dev_obj = meraki_api.Device(
- key=config['api_key'], id=serial, net=net_obj)
- if not dev_obj.realize():
- print('{}Device {} is not in network {}{}'.format(
- Fore.RED, serial, net_obj.get('name')), Style.RESET_ALL)
- nerrors += 1
- continue
- for switchport in switch[serial]:
- port_range = switchport.keys()[0]
- ports = []
- if isinstance(port_range, (int, long)):
- port_obj = meraki_api.SwitchPort(
- key=config['api_key'], id=port_range, dev=dev_obj)
- ports.append(port_obj)
- else:
- prs = port_range.split(',')
- for pr in prs:
- pr = pr.strip()
- if isinstance(pr, (int, long)):
- port_obj = meraki_api.SwitchPort(
- key=config['api_key'], id=pr, dev=dev_obj)
- ports.append(pr)
- else:
- if '-' not in pr:
- print('{}Port range {} is invalid.{}'.format(
- Fore.RED, pr, Style.RESET_ALL))
- nerrors += 1
- continue
- (start, end) = pr.split('-')
- start = start.strip()
- end = end.strip()
- if not isinstance(start, (int, long)) or not isinstance(end, (int, long)):
- print('{}Error with port range {} and {} must be integers{}'.format(
- Fore.RED, pr, start, end, Style.RESET_ALL))
- nerrors += 1
- continue
- if start >= end:
- print(
- '{}Error with port range {}; start must be less than end{}'.format(Fore.RED, pr, Style.RESET_ALL))
- nerrors += 1
- continue
- pi = start
- while pi <= end:
- port_obj = meraki_api.SwitchPort(
- key=config['api_key'], id=pi, dev=dev_obj)
- ports.append(port_obj)
- pi += 1
- for port in ports:
- pargs = {}
- for key in ['name', 'tags', 'enabled', 'type', 'vlan', 'voice_vlan', 'allowed_vlans', 'poe_enabled']:
- if key in switchport[port_range]:
- pargs[key] = switchport[port_range][key]
- res = port.update_switchport(**pargs)
- pargs_str = ', '.join(
- ['{}={}'.format(k, v) for k, v in pargs.iteritems()])
- if not res:
- print('{}Error updating switchport range {} ({}){}'.format(
- Fore.RED, port_range, pargs_str, Style.RESET_ALL))
- nerrors += 1
- else:
- print('{}update: Update switchport range {} ({}){}'.format(
- Fore.YELLOW, port_range, pargs_str, Style.RESET_ALL))
- if nerrors == 0:
- print('{}ok: network {} has been setup successfully!{}\n'.format(
- Fore.GREEN, nname, Style.RESET_ALL))
- else:
- print(
- '{}Error fully configuring network {}. See the errors above for more details.{}\n'.format(Fore.RED, nname, Style.RESET_ALL))
- errors += nerrors
- if errors == 0:
- print('{}ok: all networks have been setup successfully!{}\n'.format(
- Fore.GREEN, Style.RESET_ALL))
- else:
- print('{}There were errors setting up some of the networks. See the output above for more details.{}\n'.format(
- Fore.RED, Style.RESET_ALL))
- sys.exit(1)
- if __name__ == '__main__':
- main()
|