#!/usr/bin/env python3

"""
merakibackup.py

Iterates through all organizations and networks visable to the APIKEY,
retreives data from all known API endpoints and then outputs as
a single JSON array.

Before running the script, it's best to put the APIKEY
into the MERAKI_DASHBOARD_API_KEY environment variable.  Otherwise
use the less security --apikey option.

export MERAKI_DASHBOARD_API_KEY="the_api_key_here"

curl -s -L -H "X-Cisco-Meraki-API-Key: $MERAKI_DASHBOARD_API_KEY" -X GET \
 -H 'Content-Type: application/json' 'https://dashboard.meraki.com/api/v0/organizations' | jq '.'

"""

import getopt
import json
import os
import re
import sys

import meraki

class MerakiBackup:
    """
    Create a backup JSON of a Meraki dashboard
    """
    def __init__(self, verbose=False):
        self.configuration = {}
        self.verbose = verbose
        self.debug = False
        self.apikey = None
        self.output_filename = None
        self.dashboard = None

    def getNetworkApplianceConnectivityMonitoringDestinations(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceConnectivityMonitoringDestinations(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceContentFiltering(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceContentFiltering(networkId)
        except meraki.exceptions.APIError:
            return None

    def getDeviceLldpCdp(self, networkId: str, serial: str) -> dict:
        try:
            return self.dashboard.devices.getDeviceLldpCdp(serial)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceFirewallFirewalledServices(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceFirewallFirewalledServices(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkGroupPolicies(self, networkId: str) -> dict:
        try:
            return self.dashboard.networks.getNetworkGroupPolicies(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceSecurityIntrusion(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceSecurityIntrusion(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceSecurityMalware(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceSecurityMalware(networkId)
        except meraki.exceptions.APIError:
            return None

    def getDeviceManagementInterface(self, networkId: str, serial: str) -> dict:
        try:
            return self.dashboard.devices.getDeviceManagementInterface(serial)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceFirewallOneToOneNatRules(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceFirewallOneToOneNatRules(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceFirewallOneToManyNatRules(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceFirewallOneToManyNatRules(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceFirewallL3FirewallRules(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceFirewallL3FirewallRules(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceFirewallL7FirewallRules(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceFirewallL7FirewallRules(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceFirewallL7FirewallRulesApplicationCategories(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceFirewallL7FirewallRulesApplicationCategories(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceFirewallPortForwardingRules(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceFirewallPortForwardingRules(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceStaticRoutes(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceStaticRoutes(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkAppliancePorts(self, networkId: str):
        try:
            return self.dashboard.appliance.getNetworkAppliancePorts(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceWarmSpare(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceWarmSpare(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceVpnSiteToSiteVpn(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceVpnSiteToSiteVpn(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkSnmp(self, networkId: str) -> dict:
        try:
            return self.dashboard.networks.getNetworkSnmp(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkWirelessSsids(self, networkId: str) -> dict:
        try:
            return self.dashboard.wireless.getNetworkWirelessSsids(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkSyslogServers(self, networkId: str) -> dict:
        try:
            return self.dashboard.networks.getNetworkSyslogServers(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceVlans(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceVlans(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkApplianceVlansSettings(self, networkId: str) -> dict:
        try:
            return self.dashboard.appliance.getNetworkApplianceVlansSettings(networkId)
        except meraki.exceptions.APIError:
            return None

    def getNetworkWirelessSettings(self, networkId: str) -> dict:
        try:
            return self.dashboard.wireless.getNetworkWirelessSettings(networkId)
        except meraki.exceptions.APIError:
            return None

    def backup(self, only_organization_id: None, networkFilter: str):

        if networkFilter:
            filterRe = "^" + networkFilter + ".*"
            regex = re.compile(filterRe, flags=re.IGNORECASE)

        organizations = self.dashboard.organizations.getOrganizations()
        if not organizations:
            print("No organizations found")
            return

        for organization in organizations:
            if self.verbose:
                #print("org={}".format(organizations))
                print("Organization: {} {}".format(organization['id'], organization['name']))
            if only_organization_id and only_organization_id != organization['id']:
                if self.verbose:
                    print("skipping")
                    continue
            networks = self.dashboard.organizations.getOrganizationNetworks(organization['id'])
            if networks:
                for network in networks:
                    if self.verbose:
                        print("Network: {}".format(network['name']))
                        #print(f'debug: network={network}')

                    if networkFilter and not regex.match(network['name']):
                        continue

                    alertSettings = self.dashboard.networks.getNetworkAlertsSettings(network['id'])
                    network['alertSettings'] = alertSettings

                    httpServers = self.dashboard.networks.getNetworkWebhooksHttpServers(network['id'])
                    network['httpServers'] = httpServers

                    if 'appliance' in network['productTypes']:

                        network['connectivityMonitoringDestinations'] = self.getNetworkApplianceConnectivityMonitoringDestinations(network['id'])

                        vlansSettings = self.getNetworkApplianceVlansSettings(network['id'])
                        network['vlansSettings'] = vlansSettings

                        if vlansSettings and vlansSettings['vlansEnabled']:
                            network['vlans'] = self.getNetworkApplianceVlans(network['id'])
                            network['appliancePorts'] = self.getNetworkAppliancePorts(network['id'])

                        network['firewalledServices'] = self.getNetworkApplianceFirewallFirewalledServices(network['id'])

                        network['staticRoutes'] = self.getNetworkApplianceStaticRoutes(network['id'])

                        network['siteToSiteVpn'] = self.getNetworkApplianceVpnSiteToSiteVpn(network['id'])

                        network['l3FirewallRules'] = self.getNetworkApplianceFirewallL3FirewallRules(network['id'])

                        # should figure out if MX
                        network['intrusionSettings'] = self.getNetworkApplianceSecurityIntrusion(network['id'])

                        network['malwareSettings'] = self.getNetworkApplianceSecurityMalware(network['id'])

                        network['warmSpareSettings'] = self.getNetworkApplianceWarmSpare(network['id'])

                        network['l7FirewallRules'] = self.getNetworkApplianceFirewallL7FirewallRules(network['id'])

                        network['l7ApplicationCategories'] = self.getNetworkApplianceFirewallL7FirewallRulesApplicationCategories(network['id'])

                        network['contentFiltering'] = self.getNetworkApplianceContentFiltering(network['id'])

                        network['oneToOneNatRules'] = self.getNetworkApplianceFirewallOneToOneNatRules(network['id'])

                        network['oneToManyNatRules'] = self.getNetworkApplianceFirewallOneToManyNatRules(network['id'])

                        network['portForwardingRules'] = self.getNetworkApplianceFirewallPortForwardingRules(network['id'])


                    network['groupPolicies'] = self.getNetworkGroupPolicies(network['id'])

                    network['syslogServers'] = self.getNetworkSyslogServers(network['id'])

                    network['snmp'] = self.getNetworkSnmp(network['id'])

                    if 'wireless' in network['productTypes']:
                        network['wirelessSsids'] = self.getNetworkWirelessSsids(network['id'])
                        network['wirelessSettings'] = self.getNetworkWirelessSettings(network['id'])

                    devices = self.dashboard.networks.getNetworkDevices(network['id'])
                    network['devices'] = devices
                    if devices:
                        for device in devices:
                            device_name = None
                            if 'name' in device:
                                device_name = device['name']
                            #print(f'debug: device={device}')
                            if self.verbose:
                                print(" Device: {}".format(device_name))

                            if device['model'].startswith('MX'):
                                uplink = self.dashboard.appliance.getDeviceApplianceUplinksSettings(device['serial'])
                                device['uplink'] = uplink

                            lldp_cdp = self.getDeviceLldpCdp(network['id'], device['serial'])
                            device['lldp_cdp'] = lldp_cdp

                            if device['model'].startswith('MX') or device['model'].startswith('MR'):
                                device['managementInterfaceSettings'] = self.getDeviceManagementInterface(network['id'], device['serial'])

            organization['networks'] = networks

            admins = self.dashboard.organizations.getOrganizationAdmins(organization['id'])
            organization['admins'] = admins

            vpnFirewallRules = self.dashboard.appliance.getOrganizationApplianceVpnVpnFirewallRules(organization['id'])
            organization['vpnFirewallRules'] = vpnFirewallRules

            thirdPartyVPNPeers = self.dashboard.appliance.getOrganizationApplianceVpnThirdPartyVPNPeers(organization['id'])
            organization['thirdPartyVPNPeers'] = thirdPartyVPNPeers

            samlRoles = self.dashboard.organizations.getOrganizationSamlRoles(organization['id'])
            organization['samlRoles'] = samlRoles

        if self.output_filename:
            file = open(self.output_filename, "w")
            file.write(json.dumps(organizations, indent=2, sort_keys=True))
            file.close()
            if self.verbose:
                print("Wrote output to {}".format(self.output_filename))
        else:
            print("{}".format(json.dumps(organizations, indent=2, sort_keys=True)))

    def main(self):
        """
        Parse command line options and perform backup.
        """
        organization = None
        networkFilter = None
        options = getopt.getopt(sys.argv[1:],
                                'a:f:o:w:v',
                                [
                                    'apikey=',
                                    'filter=',
                                    'organization=',
                                    'write=',
                                    'verbose',
                                ])[0]

        for opt, arg in options:
            if opt in ('-w', '--write'):
                self.output_filename = arg
            elif opt in ('-a', '--apikey'):
                self.apikey = arg
            elif opt in ('-o', '--organization'):
                organization = arg
            elif opt in ('-f', '--filter'):
                networkFilter = arg
            elif opt in ('-v', '--verbose'):
                self.verbose = True

        if 'MERAKI_APIKEY' in os.environ:
            self.apikey = os.environ['MERAKI_APIKEY']
        if 'MERAKI_DASHBOARD_API_KEY' in os.environ:
            self.apikey = os.environ['MERAKI_DASHBOARD_API_KEY']

        if not self.apikey:
            print("Missing apikey")
            sys.exit(2)

        try:
            self.dashboard = meraki.DashboardAPI(api_key=self.apikey,
                                                 output_log=self.debug,
                                                 print_console=self.debug)
        except meraki.exceptions.APIKeyError as exception:
            print("Error: {}".format(exception))
            return

        self.backup(organization, networkFilter)

if __name__ == "__main__":
    MerakiBackup().main()
