#!/usr/bin/env python
# vim: tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80 smarttab expandtab

"""
* Copyright (C) 2013  Sangoma Technologies Corp.
* All Rights Reserved.
*
* Author(s)
* Johnny <jma@sangoma.com>
*
* This code is Sangoma Technologies Confidential Property.
* Use of and access to this code is covered by a previously executed
* non-disclosure agreement between Sangoma Technologies and the Recipient.
* This code is being supplied for evaluation purposes only and is not to be
* used for any other purpose.

Sangoma Pre and Post Jobs for Configuration Restore Process

Usually, customer will backup configuration before upgrade, do upgrading and
then restore the saved backup configuration

But in RPM upgrade, there are configuration files modified to work on the new
version; the process of restore saved configuration backup will overwrite these
files back to the old version. It is wrong.

Our solution is to backup these new version files before rpm upgrade
(nsc.restore.pre) and after rpm upgrade, put these files back(nsc.restore.post)

For nsc.restore.pre, it will open an xml file which has the list of what files
to backup, copy them to a temporary folder, archive them into a file in file
system, and then remember the full path to the file back into the xml file.

For nsc.restore.post, it will open the xml file, look up the full path to the
backup, locate the backup tar ball, extract it, and put files back to file
system accordingly.
"""


import sys
import os
import time
import subprocess
import logging
import tempfile
import shutil
import tarfile
from logging.handlers import SysLogHandler
from xml.dom.minidom import parse
from xml.dom.minidom import Node


SCRIPT_NAME = "sng-conf-restore-job"
TAR_TAG = "tarball"
FILE_TYPE = "file"
DIR_TYPE = "dir"
BACKUP_TYPES = [FILE_TYPE, DIR_TYPE]
BACKUP_LIST_FILE = "/usr/local/nsc/scripts/sng-conf-restore-job.xml"


def get_list(list_xml, type):

    datasource = open(list_xml)
    dom = parse(datasource)
    ret = []

    mylist = dom.getElementsByTagName(type)
    for file in mylist:
        ret.append(file.childNodes[0].nodeValue)

    return ret


def get_tarball_path(list_xml):

    datasource = open(list_xml)
    dom = parse(datasource)

    tar_path = dom.getElementsByTagName(TAR_TAG)
    for tar in tar_path:
        return tar.childNodes[0].nodeValue

    return None
    
def add_tarball_path(list_xml, tar_path):

    datasource = open(list_xml)
    dom = parse(datasource)

    x = dom.createElement(TAR_TAG)
    txt = dom.createTextNode(tar_path)
    x.appendChild(txt)
    dom.childNodes[-1].appendChild(x)

    tmpfd, tmp_file = tempfile.mkstemp(suffix=".xml")
    f = open(tmp_file, 'w')
    f.write(dom.toxml())
    f.close()

    os.remove(list_xml)
    shutil.move(tmp_file, list_xml)


def del_tarball_path(list_xml):

    datasource = open(list_xml)
    dom = parse(datasource)

    tar_path = dom.getElementsByTagName(TAR_TAG)
    for tar in tar_path:
        parentNode = tar.parentNode
        parentNode.removeChild(tar)

    tmpfd, tmp_file = tempfile.mkstemp(suffix=".xml")
    f = open(tmp_file, 'w')
    f.write(dom.toxml())
    f.close()

    os.remove(list_xml)
    shutil.move(tmp_file, list_xml)


def backup_files(file_list, dest_path):

    for file in file_list:
        if os.path.exists(file):
            real_dest_path = dest_path+os.path.dirname(file)
            if not os.path.exists(real_dest_path):
                os.makedirs(real_dest_path)
            shutil.copy(file, real_dest_path)


def backup_folders(dir_list, dest_path):

    for dir in dir_list:
        if os.path.isdir(dir):
            # do a little trick here
            # copytree() dst must not exist
            # so i will makedirs the path, and then rmtree it
            # all the parent folder there, but the exact path not
            real_dest_path = dest_path+dir
            if not os.path.exists(real_dest_path):
                os.makedirs(real_dest_path)
            shutil.rmtree(real_dest_path)
            shutil.copytree(dir, os.path.abspath(os.path.join(real_dest_path, "..")))


def put_files_back(path, filenames):

    for file in filenames:
        src_file = path + "/" + file
        if os.path.isfile(src_file):
            dest_file = "/" + file
            dest_path = os.path.dirname(dest_file)
            shutil.copy(src_file, dest_path)


def pre_restore_job(logger):

    # create temporary folder
    tempdir = tempfile.mkdtemp()

    # backup
    for type in BACKUP_TYPES:
        mylist = get_list(BACKUP_LIST_FILE, type)

        if type == FILE_TYPE:
            backup_files(mylist, tempdir)
        elif type == DIR_TYPE:
            backup_folders(mylist, tempdir)

    # archive and remove and temp folder
    tempfd, temp_file = tempfile.mkstemp(suffix=".bz2")
    os.chdir(tempdir)
    tar = tarfile.open(temp_file, "w:bz2")
    tar.add("./")
    tar.close()
    logger.debug('Pre script backup files into %s\n' % temp_file)

    shutil.rmtree(tempdir)

    # record full path of the tar ball
    add_tarball_path(BACKUP_LIST_FILE, temp_file)

    logger.debug('Pre process is successful\n')

    return True


def post_restore_job(logger):

    # look up the full path to the backup tar ball
    tar_path = get_tarball_path(BACKUP_LIST_FILE)
    if tar_path is None:
        return False
    del_tarball_path(BACKUP_LIST_FILE)

    # locate the backup tar ball and extract it
    if not os.path.exists(tar_path):
        return False
    if not tarfile.is_tarfile(tar_path):
        return False

    tar = tarfile.open(tar_path, "r:bz2")
    tempdir = tempfile.mkdtemp()
    tar.extractall(tempdir)
    filenames = tar.getnames()
    tar.close()

    # put files back to file system accordingly
    put_files_back(tempdir, filenames)
    shutil.rmtree(tempdir)
    os.remove(tar_path)

    logger.debug('Post process is successful\n')

    return True


def main():

    ret = False
    formatter = logging.Formatter('[%(asctime)s] [%(name)s] [%(process)d] [%(levelname)s] %(message)s')
    logger = logging.getLogger(SCRIPT_NAME)
    logger.setLevel(logging.INFO)

    syslog_handler = SysLogHandler('/dev/log')
    syslog_handler.setFormatter(formatter)
    logger.addHandler(syslog_handler)

    if sys.argv[0].endswith(".restore.pre"):
        ret = pre_restore_job(logger)
    elif sys.argv[0].endswith(".restore.post"):
        ret = post_restore_job(logger)
    else:
        logger.error('script name not ending with restore.pre or restore.post\n')
        sys.exit(1)

    if ret:
        sys.exit(0)
    else:
        sys.exit(1)



if __name__ == '__main__':
    main()

