#!/usr/bin/python
# -*- coding: utf-8 -*-

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
# 
#   http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.


import os
import sys
import subprocess
import glob
from random import choice
import string
from optparse import OptionParser
import commands
import shutil

# squelch mysqldb spurious warnings
import warnings
warnings.simplefilter('ignore')
# ---- This snippet of code adds the sources path and the waf configured PYTHONDIR to the Python path ----
# ---- We do this so cloud_utils can be looked up in the following order:
# ---- 1) Sources directory
# ---- 2) waf configured PYTHONDIR
# ---- 3) System Python path
for pythonpath in (
        "/usr/lib/python2.6/site-packages/",
        os.path.join(os.path.dirname(__file__),os.path.pardir,os.path.pardir,"python","lib"),
    ):
        if os.path.isdir(pythonpath): sys.path.insert(0,pythonpath)
# ---- End snippet of code ----

def runCmd(cmds):
    process = subprocess.Popen(' '.join(cmds), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = process.communicate()
    if process.returncode != 0:
        raise Exception(stderr)
    return stdout

class DBDeployer(object):
    parser = None
    options = None
    args = None
    isDebug = False
    mgmtsecretkey = None
    dbsecretkey = None
    encryptiontype = None
    dbConfPath = r"/etc/cloudstack/management"
    dbDotProperties = {}
    dbDotPropertiesIndex = 0
    encryptionKeyFile = '/etc/cloudstack/management/key'
    encryptionJarPath = '/usr/share/cloudstack-common/lib/jasypt-1.9.0.jar'
    success = False
    magicString = 'This_is_a_magic_string_i_think_no_one_will_duplicate'

    def preRun(self):
        def backUpDbDotProperties():
            dbpPath = os.path.join(self.dbConfPath, 'db.properties')
            copyPath = os.path.join(self.dbConfPath, 'db.properties.origin')
            
            if os.path.isfile(dbpPath):
                shutil.copy2(dbpPath, copyPath)
        
        backUpDbDotProperties()
    
    def postRun(self):
        def cleanOrRecoverDbDotProperties():
            dbpPath = os.path.join(self.dbConfPath, 'db.properties')
            copyPath = os.path.join(self.dbConfPath, 'db.properties.origin')
            if os.path.isfile(copyPath):
                if not self.success:
                    shutil.copy2(copyPath, dbpPath)
                os.remove(copyPath)
        
        cleanOrRecoverDbDotProperties()
                
        
    def info(self, msg, result=None):
        output = ""
        if msg is not None:
            output = "%-80s"%msg
            
        if result is True:
            output += "[ \033[92m%-2s\033[0m ]\n"%"OK"
        elif result is False:
            output += "[ \033[91m%-6s\033[0m ]\n"%"FAILED"
        sys.stdout.write(output)
        sys.stdout.flush()
    
    def debug(self, msg):
        msg = "DEBUG:%s"%msg
        sys.stdout.write(msg)
        sys.stdout.flush()

    def putDbProperty(self, key, value):
        if self.dbDotProperties.has_key(key):
            (oldValue, index) = self.dbDotProperties[key]
            self.dbDotProperties[key] = (value, index)
        else:
            self.dbDotProperties[key] = (value, self.dbDotPropertiesIndex)
            self.dbDotPropertiesIndex += 1
    
    def getDbProperty(self, key):
        if not self.dbDotProperties.has_key(key):
            return None
        (value, index) = self.dbDotProperties[key]
        return value
        
    def errorAndExit(self, msg):
        self.postRun()
        err = '''\n\nWe apologize for below error:
***************************************************************      
%s
*************************************************************** 
Please run:

    cloud-setup-encryption -h

for full help
''' % msg
        sys.stderr.write(err)
        sys.stderr.flush()
        sys.exit(1)

    def prepareDBFiles(self):
        def prepareDBDotProperties():
            dbpPath = os.path.join(self.dbConfPath, 'db.properties')
            dbproperties = file(dbpPath).read().splitlines()
            newdbp = []
            emptyLine = 0
            for line in dbproperties:
                passed = False
                line = line.strip()
                if line.startswith("#"): key = line; value = ''; passed = True
                if line == '' or line == '\n': key = self.magicString + str(emptyLine); value = ''; emptyLine += 1; passed = True
                
                try:
                    if not passed:
                        (key, value) = line.split('=', 1)
                except Exception, e:
                    err = '''Wrong format in %s (%s):
Besides comments beginning "#" and empty line, all key-value pairs must be in formula of
                    key=value
for example:
                    db.cloud.username = cloud
                    ''' % (dbpPath, line)
                    self.errorAndExit(err)
                self.putDbProperty(key, value)
            self.info("Preparing %s"%dbpPath, True)
        
        prepareDBDotProperties()    
    
    def finalize(self):
        def finalizeDbProperties():
            entries = []
            for key in self.dbDotProperties.keys():
                (value, index) = self.dbDotProperties[key]
                if key.startswith("#"):
                    entries.insert(index, key)
                elif key.startswith(self.magicString):
                    entries.insert(index, '')
                else:
                    entries.insert(index, "%s=%s"%(key, value))
            file(os.path.join(self.dbConfPath, 'db.properties'), 'w').write('\n'.join(entries))
        
        self.info("Finalizing setup ...", None)
        finalizeDbProperties()
        self.info(None, True)
        self.success = True # At here, we have done successfully and nothing more after this flag is set
                        
    def processEncryptionStuff(self):
        def encrypt(input):
            cmd = ['java','-classpath',self.encryptionJarPath,'org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI', 'encrypt.sh', 'input=\'%s\''%input, 'password=%s'%self.mgmtsecretkey,'verbose=false']
            return runCmd(cmd).strip('\n')
        
        def saveMgmtServerSecretKey():
            if self.encryptiontype == 'file':
                file(self.encryptionKeyFile, 'w').write(self.mgmtsecretkey)
                
        def formatEncryptResult(value):
            return 'ENC(%s)'%value
        
        def encryptDBSecretKey():
            self.putDbProperty('db.cloud.encrypt.secret', formatEncryptResult(encrypt(self.dbsecretkey)))
        
        def encryptDBPassword():
            dbPassword = self.getDbProperty('db.cloud.password')       
            if dbPassword == '': return # Don't encrypt empty password
            if dbPassword == None: self.errorAndExit('Cannot find db.cloud.password in %s'%os.path.join(self.dbConfPath, 'db.properties'))
            self.putDbProperty('db.cloud.password', formatEncryptResult(encrypt(dbPassword)))
            
            usagePassword = self.getDbProperty('db.usage.password')
            if usagePassword == '': return # Don't encrypt empty password
            if usagePassword == None: self.errorAndExit('Cannot find db.usage.password in %s'%os.path.join(self.dbConfPath, 'db.properties'))
            self.putDbProperty('db.usage.password', formatEncryptResult(encrypt(usagePassword)))
        
        self.info("Processing encryption ...", None)
        self.putDbProperty("db.cloud.encryption.type", self.encryptiontype)
        saveMgmtServerSecretKey()
        encryptDBSecretKey()
        encryptDBPassword()
        self.info(None, True)
        
    def parseOptions(self):
        def parseOtherOptions():                     
            self.encryptiontype = self.options.encryptiontype
            self.mgmtsecretkey = self.options.mgmtsecretkey
            self.dbsecretkey = self.options.dbsecretkey
            self.isDebug = self.options.debug
            
            
        def validateParameters():
            if self.encryptiontype != 'file' and self.encryptiontype != 'web':
                self.errorAndExit('Wrong encryption type %s, --encrypt-type can only be "file" or "web'%self.encryptiontype)
            
    #---------------------- option parsing and command line checks ------------------------
        usage = """%prog [-e ENCRYPTIONTYPE] [-m MGMTSECRETKEY] [-k DBSECRETKEY] [--debug]
    
    This command sets up the CloudStack Encryption.    
    
    """
        self.parser = OptionParser(usage=usage)
        self.parser.add_option("-v", "--debug", action="store_true", dest="debug", default=False,
                          help="If enabled, print the commands it will run as they run")
        self.parser.add_option("-e", "--encrypt-type", action="store", type="string", dest="encryptiontype", default="file",
                          help="Encryption method used for db password encryption. Valid values are file, web. Default is file.")
        self.parser.add_option("-m", "--managementserver-secretkey", action="store", type="string", dest="mgmtsecretkey", default="password",
                          help="Secret key used to encrypt confidential parameters in db.properties. A string, default is password")
        self.parser.add_option("-k", "--database-secretkey", action="store", type="string", dest="dbsecretkey", default="password",
                          help="Secret key used to encrypt sensitive database values. A string, default is password")
        
        (self.options, self.args) = self.parser.parse_args()
        parseOtherOptions()
        validateParameters()
    
    def run(self):
        try:
            self.preRun()
            self.parseOptions()
            self.prepareDBFiles()            
            self.processEncryptionStuff()
            self.finalize()
        finally:
            self.postRun()
        
        print ''
        print "CloudStack has successfully setup Encryption"
        print ''

if __name__ == "__main__":
   o = DBDeployer()
   o.run()
        
