#!/usr/bin/env python
#
# Copyright (c) 2014, 2015, 2016 Samsung Electronics.Co.Ltd.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; version 2 of the License
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
"""This script is used to create SDK rootstrap"""

import re
import os
import sys
import shutil
import subprocess
import urllib2
import codecs
import base64
import json
import ast
from urllib import quote_plus
from datetime import datetime

from common.buildtrigger import trigger_info, trigger_next, remote_jenkins_build_job
from common.utils import sync, set_permissions, Workdir
from common.git import Git, clone_gitproject

from common.send_mail import prepare_mail

# remote jenkins build job
import requests

EMAIL_TITLE = "[SDK RBS] %s (%s)"

EMAIL_COUNT_MESSAGE  = "A SDK RootstrapBuildSystem trigger info\n\n" \
                       "- OBS project : %s \n" \
                       "- Snapshot url : %s \n" \
                       "- Basesnapshot url : %s \n" \
                       "- SdkRootstrap url : %s \n" \
                       "- Status : %s \n"

EMAIL_FOOTER = '\n\n--------------------------------------------------------\n'\
               'Automatically generated by SDK RootstrapBuildSystem service.\n'\
               'Please DO NOT Reply!'

EMAIL_CONTENT=[]

profile = None

class LocalError(Exception):
    """Local error exception."""
    pass

def setup_profile(project):
    """ Setup profile """

    profile_list = []

    if os.getenv('RBS_PROFILES'):
        profiles = ast.literal_eval(os.getenv('RBS_PROFILES'))
    else:
        return False

    # set up profile
    for l in profiles:
        if project == l['project']:
            profile = l
            print 'project = %s' % (project)
            #return profile
            profile_list.append(profile)

    return profile_list

def send_mail(title, msg, receiver):
    """ post message back to gerrit and send mail to tag owner """
    if 'author' in receiver and 'email' in receiver:
        msg = 'Hi, %s\n\n' % receiver['author'] + msg + EMAIL_FOOTER
        prepare_mail("%s.env" % os.getenv('BUILD_TAG'), title, msg,
                     os.getenv('NOREPLY_EMAIL_SENDER'), receiver['email'])

def copy_sshkey_to_vm(src, dst):
    """
    """
    print 'src %s dst %s' % (src, dst)
          
    if os.path.exists(src):
        shutil.copytree(src, dst)

def run_inside_vm(vm_image, vm_memory, vm_cpus, basedir):
    """
    Run build/run inside VM
    """
    cmd = 'qemu-system-x86_64 -machine accel=kvm:xen:tcg -name '\
          'ubuntu -M pc -m %d -smp %d -vga none -drive file=%s,'\
          'snapshot=on -nographic -virtfs local,id=test_dev,'\
          'path=%s,security_model=mapped,mount_tag=share ' % \
          (vm_memory, vm_cpus, vm_image, basedir)
    print "run cmd = %s" % (cmd)
    #subprocess.call(cmd, stdout=sys.stdout,
    #                stderr=sys.stderr, shell=True)
    outfile = os.path.join(basedir, 'build', 'rs.log')
    proc = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = proc.communicate()
    print 'outfile %s' %out
    if out :
        with open(outfile, 'w') as f:
            f.write('%s' % out)

    #read testresult from file
    try:
        testresult = os.path.join(basedir,'build','testresult')
        if not os.path.exists(testresult):
            print "%s not exists file" % (testresult)

        with open(os.path.join(basedir,'build','testresult')) as statusf:
            status = statusf.read().strip()
        print 'status = %s' % (status)
        if int(status) == 0:
            return 0
        else:
            return status
    except IOError, _err:
        print "error"
        return -1

def sync_to_download(src, dst, log_src):
    """
    Run Sync download server
    """
    rsync_cmd = 'rsync -avr %s/* %s/' % (src, dst)
    print 'rsync_cmd = %s' % (rsync_cmd)
    proc = subprocess.Popen(rsync_cmd,shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = proc.communicate()

    if err:
        print ("Failed rsync download: %s" % out)

    rsync_cmd = 'rsync -avr %s %s' % (log_src, dst)
    print 'rsync_cmd = %s' % (rsync_cmd)
    proc = subprocess.Popen(rsync_cmd,shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    out, err = proc.communicate()
    if err:
        print ("Failed rsync download: %s" % out)

def remote_jenkins_build_job_impl(sdkrootstrap_url):
    """ remote jenkins build job"""
    print 'remote jenkins job sdkrootstrap_url: %s ' % (sdkrootstrap_url)
    if os.getenv('REMOTE_JENKINS_URL') and os.getenv('REMOTE_JENKINS_USER') \
        and os.getenv('REMOTE_JENKINS_PW') and os.getenv('REMOTE_JOB_NAME'):
        url = '%s/job/%s/buildWithParameters?token=%s' \
              %(os.getenv('REMOTE_JENKINS_URL'), os.getenv('REMOTE_JOB_NAME'), \
                os.getenv('REMOTE_JOB_TOKEN'))
        username = os.getenv('REMOTE_JENKINS_USER')
        password = os.getenv('REMOTE_JENKINS_PW')
        data = {'NEW_ROOTSTRAP': sdkrootstrap_url}
        # Need to separate gerrit servers for git-obs-mapping
        if 'public_mirror' in sdkrootstrap_url:
            data['GERRIT_INFRA'] = 'public_gerrit'
        else:
            data['GERRIT_INFRA'] = 'spin_gerrit'
        try:
            resp = requests.post(url, params=data,
                                auth=(username, password)
                                )
            status_code = resp.status_code
            print status_code
        except requests.exceptions.Timeout as e:
            print(e)
        except requests.exceptions.ConnectionError as e:
            print(e)
        except Exception as e:
            print(e)
            raise Exception('exception')

def make_rs_snapshot_private(git_prj, git_cache, mygit, fields, profile, vm_image):
    """
    Make Rootstrap snapshot(private api)
    """

    # checkout branch
    git_branch = profile['git_branch']

    print '\n\n==========='
    print 'CREATING ROOTSTRAP FOR'
    print '  [%s] [%s] [%s] [%s] [%s]' \
            % (profile.get('project'), 'PRIVATE', \
               profile.get('git_branch'), profile.get('private_cmd'), git_branch)
    print '\nStarted at %s' % (str(datetime.now()))
    print '===========\n'

    mygit.checkout(git_branch)

    rs = os.path.join(git_cache, git_prj, profile['private_cmd'])
    if not os.path.isfile(rs):
        print '%s is not found' % rs
        return 'failed'

    build_id = fields["build_id"]
    base_repo_path = profile['base_repo_path']

    # Prepare working directory
    rootstrap = 'private-sdk-rootstrap'
    basedir = os.path.join(os.getenv('WORKSPACE'), rootstrap)
    if os.path.exists(basedir):
        shutil.rmtree(basedir)
    builddir = os.path.join(basedir, 'build')
    os.makedirs(builddir)

    # copy sshkey to builddir
    ssh_src_dir = os.path.join(os.getenv("JENKINS_HOME"),'.ssh')
    ssh_dst_dir = os.path.join(builddir,'.ssh')
    copy_sshkey_to_vm(ssh_src_dir,ssh_dst_dir)

    # build command line
    #'apt-get install -y xmlstarlet rpm2cpio ruby\n' \
    git_host = os.getenv("GERRIT_HOSTNAME")
    git_port = os.getenv("GERRIT_SSHPORT")

    git_cmd = 'git clone ssh://%s:%s/%s -b %s' % ( git_host, git_port, git_prj, git_branch)
    print 'git_cmd %s' %(git_cmd)

    # 64 bit support
    str_64_cmd = ''
    if profile.get('support_64') != 'False':
        str_64_cmd = '$WORKDIR/$SDKIMAGE/%s -u %s -b %s\n' % \
            (profile["private_cmd"].replace('.sh', '_64.sh'),
             os.path.join(profile["base_url"],profile["repo_path"],build_id),
             os.path.join(profile["base_url"],profile["base_repo_path"],'latest'))

    buildcmd = '#!/bin/bash \nset -x\n' \
               'BUILDDIR="/share/build"\nWORKDIR="/srv/work"\nRS="rbs_out"\nSDKIMAGE="sdk-image"\n' \
               'chown -R build:build $BUILDDIR\nchmod 600 $BUILDDIR/.ssh/id_rsa \n' \
               'cp -r "$BUILDDIR/.ssh/" /root/\nchmod 600 /root/.ssh/id_rsa\n' \
               'mkdir -p "$WORKDIR"\ncd "$WORKDIR"\n%s\n' \
               '$WORKDIR/$SDKIMAGE/%s -u %s -b %s\n' \
               '%s\n' \
               'cp -r $WORKDIR/$RS/ $BUILDDIR\n' % \
               (git_cmd,
                profile["private_cmd"],
                os.path.join(profile["base_url"],profile["repo_path"],build_id),
                os.path.join(profile["base_url"],profile["base_repo_path"],'latest'),
                str_64_cmd)

    print buildcmd
    with open(os.path.join(builddir, 'run'), 'w') as fcmdl:
        fcmdl.write('%s' % buildcmd)

    os.chmod(os.path.join(builddir, 'run'), 0o777)

    print 'starting BUILD inside VM to create sdk private rootstrap'
    sys.stdout.flush()

    ret = run_inside_vm(vm_image, int(os.getenv("RBS_VM_MEMORY", 8492)),
                            int(os.getenv("RBS_VM_CPUS", 4)), basedir)
        # workaround for qemu/9p bug in mapping permissions
    set_permissions(builddir, (0o644, 0o755))

    status = 'succeeded'

    if not int(ret) == 0:
        print 'Error: sdk rootstrap returned %s' % ret
        status = 'failed'

    # sync sdk rootstrap image to SYNC_DEST
    if os.getenv('IMG_SYNC_DEST_BASE'):
        sync_to_download(os.path.join(builddir, 'rbs_out'),
                         os.path.join(os.getenv('IMG_SYNC_DEST_BASE'),
                         profile['repo_path'],build_id,'builddata', rootstrap),
                         os.path.join(builddir, 'rs.log'))

    if status == 'succeeded':
        print "The build was successful."
        #### abs update ####
        # notify remote jenkins build job
        remote_rs_url = os.path.join(profile["base_url"],profile["repo_path"],build_id,'builddata', rootstrap)
        #remote_jenkins_build_job_impl(remote_rs_url)
        dataContainer = {"NEW_ROOTSTRAP": remote_rs_url}
        if 'public_mirror' in remote_rs_url:
            dataContainer['GERRIT_INFRA'] = 'public_gerrit'
        else:
            dataContainer['GERRIT_INFRA'] = 'spin_gerrit'
        trigger_next("UPDATE_ROOTSTRAP", dataContainer)

    snapshot_url = os.path.join(profile["base_url"],profile["repo_path"],build_id)
    basesnapshot_url = os.path.join(profile["base_url"],profile["base_repo_path"],'latest')
    sdkrootstrap_url = os.path.join(snapshot_url, 'builddata', rootstrap)

    if status == 'failed':
    # Notify email to RBS_MAILINGLIST
        title = EMAIL_TITLE % ( status.upper(), profile["project"] )
        msg = EMAIL_COUNT_MESSAGE % (profile["project"],
                                     snapshot_url,
                                     basesnapshot_url,
                                     sdkrootstrap_url,
                                     status)

        if profile.get('mailing_list'):
            recevier = { 'author' : 'RBS Administrator',
                         'email'  : profile.get('mailing_list') }
        else:
            recevier = { 'author' : 'RBS Administrator',
                         'email'  : os.getenv('RBS_MAILINGLIST') }

        if recevier:
            send_mail(title, msg, recevier)

    return status

def make_rs_snapshot_public(git_prj, git_cache, mygit, fields, profile, vm_image, \
                            specific_branch=None, specific_cmd=None):
    """
    Make Rootstrap snapshot(public api)
    """

    # checkout branch
    if specific_branch is not None:
        git_branch = specific_branch
    else:
        git_branch = profile['git_branch']

    if specific_cmd is not None:
        cmd_to_use = specific_cmd
    else:
        cmd_to_use = profile['public_cmd']

    print '\n\n==========='
    print 'CREATING ROOTSTRAP FOR'
    print '  [%s] [%s] [%s] [%s] [%s]' \
            % (profile.get('project'), 'PUBLIC', \
               profile.get('git_branch'), cmd_to_use, git_branch)
    print '\nStarted at %s' % (str(datetime.now()))
    print '===========\n'

    mygit.checkout(git_branch)

    rs = os.path.join(git_cache, git_prj, cmd_to_use)
    if not os.path.isfile(rs):
        print '%s is not found' % rs
        return 'failed'

    build_id = fields["build_id"]
    base_repo_path = profile['base_repo_path']


    # Prepare working directory
    rootstrap = 'public-sdk-rootstrap'
    basedir = os.path.join(os.getenv('WORKSPACE'), rootstrap)
    if os.path.exists(basedir):
        shutil.rmtree(basedir)
    builddir = os.path.join(basedir, 'build')
    os.makedirs(builddir)

    # copy sshkey to builddir
    ssh_src_dir = os.path.join(os.getenv("JENKINS_HOME"),'.ssh')
    ssh_dst_dir = os.path.join(builddir,'.ssh')
    copy_sshkey_to_vm(ssh_src_dir,ssh_dst_dir)

    # build command line
    #'apt-get install -y xmlstarlet rpm2cpio ruby\n' \
    git_host = os.getenv("GERRIT_HOSTNAME")
    git_port = os.getenv("GERRIT_SSHPORT")

    git_cmd = 'git clone ssh://%s:%s/%s -b %s' % ( git_host, git_port, git_prj, git_branch)
    print 'git_cmd %s' %(git_cmd)

    # 64 bit support
    str_64_cmd = ''
    if profile.get('support_64') != 'False':
        str_64_cmd = '$WORKDIR/$SDKIMAGE/%s -u %s -b %s\n' % \
            (cmd_to_use.replace('.sh', '_64.sh'),
             os.path.join(profile["base_url"],profile["repo_path"],build_id),
             os.path.join(profile["base_url"],profile["base_repo_path"],'latest'))

    buildcmd = '#!/bin/bash \n' \
               'BUILDDIR="/share/build"\nWORKDIR="/srv/work"\nRS="rbs_out"\nSDKIMAGE="sdk-image"\n' \
               'chown -R build:build $BUILDDIR\nchmod 600 $BUILDDIR/.ssh/id_rsa \n' \
               'cp -r "$BUILDDIR/.ssh/" /root/\nchmod 600 /root/.ssh/id_rsa\n' \
               'mkdir -p "$WORKDIR"\ncd "$WORKDIR"\n%s\n' \
               '$WORKDIR/$SDKIMAGE/%s -u %s -b %s\n' \
               '%s\n' \
               'cp -r $WORKDIR/$RS/ $BUILDDIR\n' % \
               (git_cmd,
                cmd_to_use,
                os.path.join(profile["base_url"],profile["repo_path"],build_id),
                os.path.join(profile["base_url"],profile["base_repo_path"],'latest'),
                str_64_cmd)

    print buildcmd
    with open(os.path.join(builddir, 'run'), 'w') as fcmdl:
        fcmdl.write('%s' % buildcmd)

    os.chmod(os.path.join(builddir, 'run'), 0o777)

    print 'starting BUILD inside VM to create sdk public rootstrap'
    sys.stdout.flush()

    ret = run_inside_vm(vm_image, int(os.getenv("RBS_VM_MEMORY", 8492)),
                            int(os.getenv("RBS_VM_CPUS", 4)), basedir)
        # workaround for qemu/9p bug in mapping permissions
    set_permissions(builddir, (0o644, 0o755))

    status = 'succeeded'

    if not int(ret) == 0:
        print 'Error: sdk rootstrap returned %s' % ret
        status = 'failed'

    # sync sdk rootstrap image to SYNC_DEST
    if os.getenv('IMG_SYNC_DEST_BASE'):
        sync_to_download(os.path.join(builddir, 'rbs_out'),
                         os.path.join(os.getenv('IMG_SYNC_DEST_BASE'),
                         profile['repo_path'],build_id,'builddata', rootstrap),
                         os.path.join(builddir, 'rs.log'))

    snapshot_url = os.path.join(profile["base_url"],profile["repo_path"],build_id)
    basesnapshot_url = os.path.join(profile["base_url"],profile["base_repo_path"],'latest')
    sdkrootstrap_url = os.path.join(snapshot_url, 'builddata', rootstrap)

    if status == 'failed':
        # Notify email to RBS_MAILINGLIST
        title = EMAIL_TITLE % ( status.upper(), profile["project"] )
        msg = EMAIL_COUNT_MESSAGE % (profile["project"],
                                     snapshot_url,
                                     basesnapshot_url,
                                     sdkrootstrap_url,
                                     status)

        if profile.get('mailing_list'):
            recevier = { 'author' : 'RBS Administrator',
                         'email'  : profile.get('mailing_list') }
        else:
            recevier = { 'author' : 'RBS Administrator',
                         'email'  : os.getenv('RBS_MAILINGLIST') }

        if recevier:
            send_mail(title, msg, recevier)


    return status

def update_dashboard(project, version, status, reason, infra="LOCAL"):
    try:
        event_type = "ROOTSTRAP_UPDATED"
        data = {"event_type": event_type, \
                "project":project, \
                "rootstrap_version":version, \
                "status":status, \
                "reason":reason, \
                "infra":infra}
        trigger_next("UPDATE_RBS_STATUS", data)
    except Exception as err:
        print repr(err)
        print 'Updating dashboard failed'

def main():
    """The main body"""

    # Check if environment variables are set
    for var in ('WORKSPACE', 'IMG_SYNC_DEST_BASE'):
        if not os.getenv(var):
            print 'Error: environment variable %s is not set' % var
            return -1

    fields = trigger_info(os.getenv('TRIGGER_INFO'))
    base_path = os.getenv('PATH_REPO_BASE')

    # Check if we've got required fieldsdk-rootstraps in TRIGGER_INFO
    for field in ('build_id', 'project'):
        if field not in fields:
            print 'Error: TRIGGER_INFO doesn\'t contain %s' % field
            return -1
    
    profile_list = setup_profile(fields['project'])

    for profile in profile_list:
        if not profile:
            print 'Skip TRIGGER job for the project %s' % fields['project']
            return 0

        vm_image = os.getenv("RBS_VM_IMAGE")

        if not vm_image:
           print 'VM not vm_image'
           return -1

        if not os.access(vm_image, os.R_OK):
           print 'VM not access'
           return -1

        # check if tarball exists
        if not vm_image or not os.access(vm_image, os.R_OK):
            print 'VM image %s is not found' % vm_image
            return -1

        # clone git sdk-image
        git_prj = profile['git_rs_prj']

        git_cache = os.getenv("GIT_CACHE_DIR")
        print 'Cloning %s' % git_prj
        if not clone_gitproject(git_prj, \
                os.path.join(git_cache, git_prj)):
            raise LocalError('Error cloning %s' % git_prj)

        mygit = Git(os.path.join(git_cache, git_prj))

        status = 'succeeded'
        reason = 'ok'

        if profile.get('private_cmd', 'False') != 'False':
            #### Make Rootstrap Snapshot (PRIVATE)####
            status = make_rs_snapshot_private(git_prj, git_cache, mygit, fields, profile, vm_image)

            if status == 'succeeded':
                print "The RBS(Internal) build was successful."
            else:
                print "The RBS(Internal) build was failed."
                reason = 'RBS build fail'

            try:
                update_dashboard(fields['project'], \
                                 fields['build_id'].split('_')[-1], 
                                 status, reason,
                                 fields.get('infra', 'LOCAL')
                                 )
            except Exception as err:
                print repr(err)

        status = 'succeeded'
        #### Make Rootstrap Snapshot (PUBLIC)####
        status = make_rs_snapshot_public(git_prj, git_cache, mygit, fields, profile, vm_image)

        #### Misc public rootstraps ####
        if profile.get('misc_branches'):
            for mb in profile.get('misc_branches'):
                status_1 = make_rs_snapshot_public(git_prj, git_cache, mygit, fields, profile, \
                                                   vm_image, specific_branch=mb)
                if status_1 != 'succeeded':
                    status = status_1
        if profile.get('public_misc_cmds'):
            for item in profile.get('public_misc_cmds'):
                print 'MISC command: %s' % item
                mc = item.split(',')[0]
                mb = None
                if len(item.split(',')) >= 2:
                    mb = item.split(',')[1]
                make_rs_snapshot_public(git_prj, git_cache, mygit, fields, profile, \
                                        vm_image, specific_branch=mb, specific_cmd=mc)

        if status == 'succeeded':
            print "The RBS(Public) build was successful."
        else:
            print "The RBS(Public) build was failed."

    print '\nFinished at %s' % (str(datetime.now()))

    return 0

if __name__ == "__main__":
    sys.exit(main())
