#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
###########################################################################
# Eole - 2014
# Copyright Pole de Competence Eole  (Ministere Education - Academie Dijon)
# Licence CeCill  cf /root/LicenceEole.txt
# eole@ac-dijon.fr
#
# migration_2.4.py
#
# squelette de script permettant de générer des configurations de migration
# en mode batch (via l'API XMLRPC de Zéphir)
#
# Ce script génère une configuration de la même façon qu'en utilisant
# le bouton "générer un configuration de migration' de l'application web.
#
# Il est possible de personnaliser la fonction update_conf pour gérer
# des variables supplémentaires non gérées par la fonction d'origine.
# (variables déclarées au niveau des variantes / serveurs).
#
###########################################################################

import csv,getpass,os
import dico,base64,xmlrpclib
from zephir.utils.creolewrap import ZephirDict
import sys
reload(sys)

# force l'encodage par défaut à utf-8 pour les chaînes unicode
sys.setdefaultencoding('utf-8')

# à redéfinir si utilisé à distance
adresse_zephir='localhost'

# délimiteur de champ pour le fichier CSV décrivant les serveurs à traiter
delimiter = ';'
# contenu des champs du fichier csv:
# id_serveur;id_variante_destination

# !!  mettre à True pour vérifier les valeurs lors de tests (la configuration ne sera pas sauvegardée) !!
debug = False

#######################################################
## FONCTIONS UTILITAIRES POUR LE TRAITEMENT DES VALEURS

def is_empty(value):
    # renvoie True si value est 'vide' :
    # liste vide / chaine vide / liste de chaines vides
    return len(u"".join(value)) == 0

def is_equal(dico, nom_var, test):
    """renvoie True si la valeur de la variable nom_var est équivalente à test
    gère le cas des chaines unicode ou non et des listes 'vides'
    """
    if nom_var in dico.liste_vars:
        if test in ([], [''], [u'']):
            if dico.get_value(nom_var) in ([], [''], [u'']):
                return True
        if dico.get_value(nom_var) == test or \
           dico.get_value(nom_var) == [test] or \
           dico.get_value(nom_var) == [unicode(test)]:
            return True
        return False
    else:
        print u"Appel de is_equal sur une variable inconnue : {0}".format(nom_var)

def copy_val(dico, nom_var, value):
    """ définit une valeur sur une variable en transformant
    les chaînes non unicode en unicode dico est une configuration 2.4
    """
    if nom_var in dico.liste_vars:
        if value == "[u'']" or  value == "['']":
            value = []
        if value != '[]' and type(value) != list:
            if value.startswith(u"["):
                value = eval(value)
        # si le dictionnaire de destination est de type 'creole3' (eole 2.4),
        # on transforme les type 'str' en 'unicode'
        if dico.version == 'creole3':
            if type(value) == list:
                for  index ,val in enumerate(value):
                    if type(val) == str:
                        value[index] = unicode(val, 'utf-8')
            elif type(value) == str:
                value = unicode(value, 'utf-8')
        dico.get_var(nom_var)
        dico.set_value(value)
    else:
        print u"Appel de copy_val sur une variable inconnue : {0}".format(nom_var)

def read_val(dico, nom_var):
    if nom_var in dico.liste_vars:
        value = dico.get_value(nom_var)
        if value == "[u'']" or  value == "['']" or value == ['']:
            value = []
        if value != '[]' and type(value) != list:
            if value.startswith(u"["):
                value = eval(value)
        if type(value) == list:
            for index,v in enumerate(value):
                if type(v) == str:
                        value[index] = unicode(v, 'utf-8')
            return value
        else:
            return unicode(dico.get_value(nom_var), 'utf-8')
    else:
        print u"Appel de read_val sur une variable inconnue : {0}".format(nom_var)

######################################
## FONCTION PERSONNALISEE DE MIGRATION

def update_conf(d_eole1, d_eole2, variante, proxy):
    """
    traitement automatique des variables

    met à jour la configuration de migration en fonction de la configuration actuelle

    d_eole1 : dictionnaire avec la configuration d'origine du serveur
    d_eole2 : dictionnaire qui sera sauvegardé sur Zéphir comme 'migration.eol'
              (configuration à appliquer sur le serveur migré)
    variante: identifiant de la variante du serveur après migration
    proxy: proxy XMLRPC utilisable pour faire des appels à l'API Zéphir

    lire la valeur d'une variable (dico 1 ou 2):

          d_eoleX.get_value('nom_var')
          !ATTENTION! : get_value renvoie toujours une liste de valeur.
          En cas de test sur une valeur, récupérer la valeur voulue
          ex: if read_val(d_eole1,'activer_squid_auth')[0] == 'pedago':

    copier la valeur d'une variable de dico1 vers une variabel de dico2:
          copy_val(d_eole2,'nom_variable2.4',d_eole1.get_value('nom_variable2.2'))

    modifier la valeur d'une variable du dictionnaire eole 2:

          copy_val(d_eole2,'nom_var',u'valeur')

    pour une variable multiple :  copy_val(d_eole2,'nom_var',[u'valeur1',u'valeur2', ...])

    vérifier si une variable est présente dans un dictionnaire:

          if 'nom_variable' in d_eoleX.liste_vars:
              .....

    une bonne pratique peut être de créer des sous fonctions spécifiques à chaque variante

    Pour plus d'informations sur l'utilisation de l'API de Zéphir:
    https://<adresse_ip_zephir>:8070/aide/devel
    https://<adresse_ip_zephir>:8070/aide/api
    """

    # ----- inclure les traitements des variables ici
    # --- !! pour éviter des problèmes d'indentation, configurez l'éditeur pour remplacer
    # --- les tabulations par 4 espaces (ou éditez le fichier avec vi sur un module eole)
    #
    # --- Dans la mesure du possible, utilisez de préférence la modification des valeurs par défaut
    # --- des modules/variantes eole NG dans l'application web si les valeurs à renseigner sont les mêmes
    # --- pour tous les serveurs d'un(e) variante/module.


    ### Attention, pour la migration vers EOLE 2.4 :
    #
    # pour set_value sur d_eole2, il faut utiliser des chaînes unicode pour les chaînes de caractères :
    # par exemple:
    # - d_eole2.get_var('passerelle_smtp') : sélectionne la variable
    # - d_eole2.set_value(u'smtp.in.ac-dijon.fr') : applique la valeur (unicode)
    #
    # utiliser de préférence les fonction copy_val et read_val, qui convertissent les chaînes si besoin :
    # copy_val(d_eole2, 'passerelle_smtp', u'smtp.in.ac-dijon.fr') : ici, u est optionnel
    ###

    # exemple1 : mettre le nombre de cartes à 3 si le modèle de pare-feu dans la configuration amon-1.5
    # contient la chaine '3zones'
    #
    #if 'type_amon' in d_eole1.liste_vars:
    #    if '3zones' in d_eole1.get_value('type_amon'):
    #        d_eole2.dico.set_value('nombre_interfaces','3')

    # exemple2 : pour la variante 8, activer le serveur sso si le frontend ead-web est activé
    #
    #if variante == 8:
    #    if isequal(d_eole2, 'ead_web', 'oui'):
    #        copy_val(d_eole2,'adresse_ip_sso', read_val(d_eole1,'adresse_ip_eth0'))

    # exemple 3:
    #
    # passage à oui de 'activer_ajout_hosts' si des noms supplémentaires ont été ajoutés par la variante 2.4
    # (forcé à non au moment de l'upgrade si la configuration d_eole1 ne contient pas de noms supplémentaire)
    #
    #if len(d_eole2.get_value('adresse_ip_hosts')) != 0:
    #    copy_val(d_eole2, 'activer_ajout_hosts', u'oui')

    # ---- fin des traitements

    return d_eole2

######################
## FONCTION PRINCIPALE

def migrate_serveurs(fic_csv_name="migration.csv", main_loop=False):
    """
    boucle principal de parcours des serveurs
    """
    login = raw_input('login zephir :')
    passwd = getpass.getpass('mot de passe :')
    print ""
    # lecture du fichier csv
    if fic_csv_name == "":
        fic_csv_name = raw_input('emplacement du fichier csv : ')
    fic_csv = open(fic_csv_name)
    parser = csv.reader(fic_csv,delimiter=delimiter)
    if adresse_zephir in ('localhost', '127.0.0.1'):
        # si lancé sur Zéphir, on utilise le port XMLRPC local (sans TLS, plus rapide)
        proxy = xmlrpclib.ServerProxy('http://%s:%s@%s:7081' % (login,passwd,adresse_zephir))
    else:
        proxy = xmlrpclib.ServerProxy('https://%s:%s@%s:7080' % (login,passwd,adresse_zephir))
    # informations sur les module et variantes

    # parsing des données
    erreurs = []
    while True:
        try:
            # lecture d'une ligne du fichier csv
            data_serveur = parser.next()
        except StopIteration:
            # fin du fichier
            fic_csv.close()
            break
        # chaque ligne indique un numéro de serveur et le n° de la variante à utiliser après migration
        id_serveur, variante = data_serveur
        id_serveur = int(id_serveur)
        variante = int(variante)
        # migration du serveur (équivalent de 'générer la 'générer les données de migration' dans  l'appli web)
        try:
            # récupération du dictionnaire creole du serveur dans sa version actuelle
            code, data_ori = proxy.serveurs.get_dico(id_serveur,'modif_config',True)
            d_eole1 = ZephirDict(mode='')
            d_eole1.init_from_zephir(data_ori)
            # initialisation du dictionnaire creole2
            code, data_migration = proxy.serveurs.migrate_conf(int(id_serveur), 'migration', int(variante))
            if code == 0:
                raise Exception, data_migration
            ##  IMPORTANT, pour la migration vers eole 2.4, bien spécifier version='creole3'
            d_eole2 = ZephirDict(mode='', version='creole3')
            # initialisation du dictionnaire depuis les valeurs renvoyées par Zéphir
            d_eole2.init_from_zephir(data_migration)
            # déblocage temporaire des variables cachées et vérouillées ?
            #settings = d_eole2.dico.cfgimpl_get_settings()
            #settings.remove('disabled')
            #settings.remove('frozen')
            # mise à jour du dictionnaire de migration
            # APPEL DE LA FONCTION DE MIGRATION PERSONNALISEE
            d_eole2 = update_conf(d_eole1, d_eole2, int(variante), proxy)
            if debug:
                print "\n--- configuration du serveur %s\n" % id_serveur
                for var in d_eole2.liste_vars:
                    print var, '-->', d_eole2.get_value(var)
                if not main_loop:
                    # mod debug et appel depuis shell python: on retourne
                    # les 2 dictionnaires pour le premier serveur du fichier CSV
                    # pour faciliter les diagnostics
                    fic_csv.close()
                    return d_eole1, d_eole2
            else:
                save_data = d_eole2.save(force=False) #,eol_file='/tmp/test.txt')
                # sauvegarde de la conf sur zephir (migration.eol)
                code, detail = proxy.serveurs.save_conf(id_serveur, save_data, 'migration')
                print "serveur modifié : %s" % id_serveur
        except Exception, e:
            if debug:
                # trace complète en mode debug
                import traceback
                traceback.print_exc()
            print "erreur de modification du serveur %s : %s" % (id_serveur, str(e))
            erreurs.append(delimiter.join(data_serveur))

    # on stocke dans un fichier csv les serveurs non ajoutés
    if len(erreurs) > 0:
        f=open("error.csv","w")
        f.write('\n'.join(erreurs))
        f.close()
        print "\nDes erreurs ont été rencontrées :\n"
        print "- Vous pouvez consulter les logs du service zephir pour plus d'informations:\n"
        print "     /var/log/rsyslog/local/zephir_backend/zephir_backend.info.log\n"
        print "- un fichier error.csv a été généré pour relancer ce script sur les serveurs en erreur\n"

if __name__ == '__main__':
    try:
        # on regarde si un fichier csv est passé en argument
        assert os.path.isfile(sys.argv[1])
        fic_csv = sys.argv[1]
    except:
        fic_csv = ""
    migrate_serveurs(fic_csv, main_loop=True)
