#!/usr/bin/python3
import pynetbox
import requests
import urllib3
import pexpect
import sys
import json
import gitlab
from datetime import date
today = str(date.today())

###############################################################################
#
# Set global variables
#
###############################################################################
# Netbox URL and Token
netbox_url = "https://HOST_OR_IP"
netbox_token = "TOKEN"
# Gitlab URL and Token
gitlab_url = "https://HOST_OR_IP"
gitlab_token = "TOKEN"
# SCP User, Password and Host (SCP host can be same like Netbox host)
scp_user = "USERNAME"
scp_pass = "PASSWORD"
scp_host = "HOST_OR_IP"
# Set User, Password and Enable to connect with SSH to devices
backup_user = "USERNAME"
backup_pass = "PASSWORD"
backup_enable = "ENABLE_PASSWORD"
# Set Gitlab project ID
project_id = PROJECT_ID


###############################################################################
def getDevicesFromNetbox():
  urllib3.disable_warnings()
  requests.packages.urllib3.disable_warnings()
  netbox = pynetbox.api(
    url = netbox_url,
    token = netbox_token,
    threading = True
  )
  netbox.http_session.verify = False

  # init empty device array
  devices_netbox = []
  
  #
  # Build a list with Core-Switches
  # Note: The filtering by role depends on the roles defined in netbox
  #
  # the list needs at minimum the following information:
  # device / site / hostname / status / ipv4 address
  # device: used to determine the device and what CLI commands are required to start a backup
  # site: used to build a directory structure in gitlab project
  # hostname: the hostname of the device to build .cfg
  # status: to determine if device can be backed up or is out of it
  # ipv4: IP address to connect to the device

  core_switch = netbox.dcim.devices.filter(role='net-core-switch')
  for i in range(0, len(core_switch)):
    data = netbox.dcim.devices.get(name = core_switch[i])
    hostname = str(data).split(":")
    hostname = hostname[0]
    device_details = ["switch", str(data.site), hostname, str(data.primary_ip), str(data.status)]
    devices_netbox.append(device_details)

  access_switch = netbox.dcim.devices.filter(role='net-access-switch')
  for i in range(0, len(access_switch)):
    data = netbox.dcim.devices.get(name = access_switch[i])
    hostname = str(data).split(":")
    hostname = hostname[0]
    device_details = ["switch", str(data.site), hostname, str(data.primary_ip), str(data.status)]
    devices_netbox.append(device_details)

  wifi_controller = netbox.dcim.devices.filter(role='net-wireless-lan-controller')
  for i in range(0, len(wifi_controller)):
    data = netbox.dcim.devices.get(name = wifi_controller[i])
    hostname = str(data).split(":")
    hostname = hostname[0]
    device_details = ["wificontroller", str(data.site), hostname, str(data.primary_ip), str(data.status)]
    devices_netbox.append(device_details)

  wan_firewall = netbox.dcim.devices.filter(role='net-wan-firewall')
  for i in range(0, len(wan_firewall)):
    data = netbox.dcim.devices.get(name = wan_firewall[i])
    hostname = str(data).split(":")
    hostname = hostname[0]
    device_details = ["wanfirewall", str(data.site), hostname, str(data.primary_ip), str(data.status)]
    devices_netbox.append(device_details)

  return devices_netbox

###############################################################################
def cliCiscoSwitch():
  try:
    print ("INFO: Connecting to device: " + hostname + " with IP: " + ipv4)
    sshconn = pexpect.spawn('ssh %s@%s' % (backup_user, ipv4))
    #sshconn.logfile = sys.stdout.buffer
    sshconn.timeout = 30

    sshconn.expect('.*assword:.*')
    sshconn.sendline(backup_pass)
    sshconn.expect('#')
    sshconn.sendline('term len 0')
    sshconn.expect('#')

    print ('INFO: Set exec banner')
    sshconn.sendline('conf t')
    sshconn.expect('#')
    sshconn.sendline('file prompt quiet')
    sshconn.expect('#')
    sshconn.sendline('banner exec ^')
    sshconn.sendline(today + ' - Config saved by backup2git')
    sshconn.sendline('^')
    sshconn.expect('#')
    sshconn.sendline('exit')

    print ('INFO: Executing write memory command')
    sshconn.sendline('wr mem')
    sshconn.expect('.*OK.*')

    print ('INFO: Executing copy run scp command')
    sshconn.sendline('copy run scp://' + scp_user + ':' + scp_pass + '@' + scp_host + '//tmp/' + hostname + '.cfg')
    sshconn.expect('.*copied.*')

    print ('INFO: Log out from device: ' + hostname + " with ip: " + ipv4)
    sshconn.sendline('logout')

  except pexpect.TIMEOUT:
    print ("ERROR: No login to device: " + hostname + " with ip: " + ipv4)
    pass
  
###############################################################################
def cliCiscoWirelessController():
  try:
    print ("INFO: Connecting to device: " + hostname + " with IP: " + ipv4)
    sshconn = pexpect.spawn('ssh %s@%s' % (backup_user, ipv4))
    #sshconn.logfile = sys.stdout.buffer
    sshconn.timeout = 30

    sshconn.expect('.*ser:.*')
    sshconn.sendline(backup_user)
    sshconn.expect('.*assword:.*')
    sshconn.sendline(backup_pass)
    sshconn.expect('>')
    print ('INFO: Executing save config command')
    sshconn.sendline('save config')
    sshconn.expect('(y/n)')
    sshconn.sendline('y')

    print ('INFO: set transfer parameters')
    sshconn.sendline('transfer upload datatype config')
    sshconn.expect('>')
    sshconn.sendline('transfer upload mode sftp')
    sshconn.expect('>')
    sshconn.sendline('transfer upload serverip ' + scp_host)
    sshconn.expect('>')
    sshconn.sendline('transfer upload filename ' + hostname + ".cfg")
    sshconn.expect('>')
    sshconn.sendline('transfer upload path /tmp/')
    sshconn.expect('>')
    sshconn.sendline('transfer upload username ' + scp_user)
    sshconn.expect('>')
    sshconn.sendline('transfer upload password ' + scp_pass)
    sshconn.expect('>')

    print ('INFO: Executing transfer upload start command')
    sshconn.sendline('transfer upload start')
    sshconn.expect('(y/N)')
    sshconn.sendline('y')
    sshconn.expect('successfully.')

    print ('INFO: Log out from device: ' + hostname + " with ip: " + ipv4)
    sshconn.sendline('logout')

  except pexpect.TIMEOUT:
    print ("ERROR: No login to device: " + hostname + " with ip: " + ipv4)
    pass
  
###############################################################################
def cliCiscoAsaFirewall():
  try:
    print ("INFO: Connecting to device: " + hostname + " with IP: " + ipv4)
    sshconn = pexpect.spawn('ssh %s@%s' % (backup_user, ipv4))
    #sshconn.logfile = sys.stdout.buffer
    sshconn.timeout = 30

    sshconn.expect('.*assword:.*')
    sshconn.sendline(backup_pass)
    sshconn.expect('>')
    sshconn.sendline('enable')
    sshconn.expect('.*assword:.*')
    sshconn.sendline(backup_pass)

    print ('INFO: Executing write memory command')
    sshconn.sendline('wr mem')
    sshconn.expect('.*OK.*')

    print ('INFO: Executing copy run scp command')
    sshconn.sendline('copy run scp://' + scp_user + ':' + scp_pass + '@' + scp_host + '//tmp/' + hostname + '.cfg')
    sshconn.expect('.*ource')
    sshconn.sendline()
    sshconn.expect('.*ddress')
    sshconn.sendline()
    sshconn.expect('.*sername')
    sshconn.sendline()
    sshconn.expect('.*ilename')
    sshconn.sendline()
    sshconn.expect('continue connecting')
    sshconn.sendline('yes')
    sshconn.expect('copied')
    sshconn.sendline('logout')

    print ('INFO: Log out from device: ' + hostname + " with ip: " + ipv4)
    sshconn.sendline('logout')

  except pexpect.TIMEOUT:
    print ("ERROR: No login to device: " + hostname + " with ip: " + ipv4)
    pass
  
  
###############################################################################
# Get the devices from Netbox
devices = getDevicesFromNetbox()

  # Use a for-loop to go through the devices and check in the configuration files
  for i in range(0, len(devices)):

    # Split list
    role = str(devices[i][0])
    location = str(devices[i][1])
    hostname = str(devices[i][2])
    ipv4 = str(devices[i][3])[:len(str(devices[i][3]))-3]
    status = str(devices[i][4])

    # If device is active and has a primary IPv4 address (ssh connect action)
    if status == "Active" and ipv4 != "N":

      # role is a switch
        if role == "switch":
          cliCiscoSwitch()
        # role is a wlc
        elif role == "wificontroller":
          cliCiscoWirelessController()
        # role is a firewall
        elif role == "wanfirewall":
          cliCiscoAsaFirewall()

    # handle list from netbox if device is missing ipv4 or if
    # device status is set to something else than active
    else:
      print ("WARN: device not set to active or IP missing")

    # If device is active and has primary IPv4 address (open file action)
    if status == "Active" and ipv4 != "N":
      # open config file and read it into a string variable
      try:
        content = open('/tmp/' + hostname + '.cfg').read()
      except:
        print ("ERROR: Cannot open file: " + hostname + ".cfg")

      urllib3.disable_warnings()
      requests.packages.urllib3.disable_warnings()
      gl = gitlab.Gitlab(gitlab_url, ssl_verify=False, private_token=gitlab_token)
      # authenticate
      gl.auth()
      # get gitlab project
      project = gl.projects.get(project_id)

      # try with a commit create if file does not exist on gitlab project
      try:
        data_create = {
          'branch': 'master',
          'commit_message': 'Initial commit of config file on ' + today + ' by backup2git',
          'actions': [
            {
              'action': 'create',
               'file_path': location + '/' + hostname + '.cfg',
               'content': open('/tmp/' + hostname + '.cfg').read(),
            }
          ]
        }
      except:
        print ("Cannot build commit create JSON")
        pass
      # If file exists, use commit update to check in new version of file
      try:
        data_update = {
          'branch': 'master',
          'commit_message': 'Update of config file on ' + today + ' by backup2git',
          'actions': [
            {
              'action': 'update',
              'file_path': location + '/' + hostname + '.cfg',
              'content': open('/tmp/' + hostname + '.cfg').read(),
            }
          ]
        }
      except:
        print ("Cannot build commit update JSON")
        pass