#! /usr/bin/python3
#
#    Copyright (C) 2015 VMware, Inc. All rights reserved.
#    photonpublish.py
#    Allows pushing rpms and other artifacts to
#    a bintray repository.
#    Allows queying a repository to get existing
#    file details.
#
#    Author(s): Priyesh Padmavilasom
#

import sys
import getopt
import json
import glob
import os

import requests
from requests.auth import HTTPBasicAuth

from publishutils import publishUtils
from publishconst import publishConst


const = publishConst()

class photonPublish:

    def __init__(self, context):
        self._context = context
        self._config = {}
        self.loadConfig()

    def loadConfig(self):
        confFile = self._context['config']
        if not confFile:
            return
        with open(confFile) as jsonFile:
            self._config = json.load(jsonFile)
        #override cmdline supplied params
        if 'user' in self._context and self._context['user']:
            self._config['user'] = self._context['user']
        if 'apikey' in self._context and self._context['apikey']:
            self._config['apikey'] = self._context['apikey']


    #get details of existing files in remote
    def getPackages(self):
        auth = HTTPBasicAuth(self._config['user'], self._config['apikey'])

        #form url: https://api.com/content/vmware/photon/releases/0.9 for eg.
        url = '%s/packages/%s/%s/%s/versions/%s/files?include_unpublished=1' % \
              (self._config['baseurl'],\
               self._config['subject'],\
               self._config['repo'],\
               self._config['package'],\
               self._config['version'])

        req = requests.get(url, auth=auth)
        if req.status_code >= 300:
            raise Exception(req.text)

        return req.json()

    #return list of unpublished content
    #works with details from config file
    def getUnpublished(self):
        result = []
        pkgs = self.getPackages()
        for pkg in pkgs:
            if not pkg[const.published]:
                result.append(pkg)
        return result

    #Check if the local path has any diffs with the
    #remote repo. Compare sha1 hash
    def check(self, pkgsRoot):
        result = {
            const.updates:[],
            const.new:[],
            const.obsoletes:[],
            const.verified:[]
            }
        localFiles = publishUtils.getFilesWithRelativePath(pkgsRoot)
        pkgs = self.getPackages()
        for pkg in pkgs:
            remotePath = pkg[const.path]

            if remotePath in localFiles:
                localFiles.remove(remotePath)

            localPath = os.path.join(pkgsRoot, remotePath)
            if os.path.isfile(localPath):
                sha1 = publishUtils.sha1OfFile(localPath)
                if sha1 == pkg[const.sha1]:
                    result[const.verified].append(pkg)
                else:
                    result[const.updates].append(pkg)
            else:
                result[const.obsoletes].append(pkg)

        for newFile in localFiles:
            result[const.new].append({const.path:newFile})

        return result

    #exec results from a check against remote
    #this will make remote and local in sync
    def syncRemote(self, root, checkResults):
        updateCount = len(checkResults[const.updates])
        newCount = len(checkResults[const.new])

        if updateCount + newCount == 0:
            print('Remote is up to date.')
            return

        #push updates
        print('Updating %d files' % updateCount)
        for new in checkResults[const.updates]:
            filePath = new[const.path]
            fileName = os.path.basename(filePath)
            fullPath = os.path.join(root, filePath)
            pathName = filePath.rstrip(fileName).rstrip('/')
            self.updateFile(fullPath, pathName)

        #push new files
        print('Pushing %d new files' % newCount)
        for new in checkResults[const.new]:
            filePath = new[const.path]
            fileName = os.path.basename(filePath)
            fullPath = os.path.join(root, filePath)
            pathName = filePath.rstrip(fileName).rstrip('/')
            self.pushFile(fullPath, pathName)

    #push a folder with rpms to a specified pathname
    #in the remote repo
    def push(self, filePath, pathName):
        results = []
        filesToPush = glob.glob(filePath)
        for fileToPush in filesToPush:
            result = self.pushFile(fileToPush, pathName)
            print(result)
            results.append(result)

    def pushFile(self, fileToPush, pathName):
        print('Pushing file %s to path %s' % (fileToPush, pathName))
        result = {}
        auth = HTTPBasicAuth(self._config['user'], self._config['apikey'])

        fileName = os.path.basename(fileToPush)
        if pathName:
            pathAndFileName = '%s/%s' % (pathName, fileName)
        else:
            pathAndFileName = fileName

        #form url: https://api.com/content/vmware/photon/releases/0.9 for eg.
        url = '%s/content/%s/%s/%s/%s/%s' % \
              (self._config['baseurl'],\
               self._config['subject'],\
               self._config['repo'],\
               self._config['package'],\
               self._config['version'],\
               pathAndFileName)

        headers = {'Content-Type': 'application/octet-stream'}
        with open(fileToPush, 'rb') as chunkedData:
            req = requests.put(url,
                               auth=auth,
                               data=chunkedData,
                               headers=headers)

        if req.status_code >= 300:
            raise Exception(req.text)

        result['destPath'] = pathAndFileName
        result['sourcePath'] = fileToPush
        result['returnCode'] = req.status_code
        result['msg'] = req.text
        return result

    def updateFile(self, fileToPush, pathName):
        print('Updating file %s at path %s' % (fileToPush, pathName))
        result = {}
        auth = HTTPBasicAuth(self._config['user'], self._config['apikey'])

        fileName = os.path.basename(fileToPush)
        if pathName:
            pathAndFileName = '%s/%s' % (pathName, fileName)
        else:
            pathAndFileName = fileName

        #form url: https://api.com/content/vmware/photon/releases/0.9 for eg.
        url = '%s/content/%s/%s/%s/%s/%s?override=1' % \
              (self._config['baseurl'],\
               self._config['subject'],\
               self._config['repo'],\
               self._config['package'],\
               self._config['version'],\
               pathAndFileName)

        headers = {'Content-Type': 'application/octet-stream'}
        with open(fileToPush, 'rb') as chunkedData:
            req = requests.put(url,
                               auth=auth,
                               data=chunkedData,
                               headers=headers)

        if req.status_code >= 300:
            raise Exception(req.text)

        result['destPath'] = pathAndFileName
        result['sourcePath'] = fileToPush
        result['returnCode'] = req.status_code
        result['msg'] = req.text
        return result

    #publishes pending content. Works with details from conf file.
    def publish(self):
        print('Publishing pending files to %s/%s/%s' \
               % (self._config['repo'],
                  self._config['package'],
                  self._config['version']))
        auth = HTTPBasicAuth(self._config['user'], self._config['apikey'])

        #form url: https://api.com/content/vmware/photon/releases/0.9 for eg.
        url = '%s/content/%s/%s/%s/%s/publish' % \
              (self._config['baseurl'],\
               self._config['subject'],\
               self._config['repo'],\
               self._config['package'],\
               self._config['version'])

        req = requests.post(url, auth=auth)

        if req.status_code >= 300:
            raise Exception(req.text)

        return req.json()

def showUsage():
    print('photonpublish.py --files /rpms/*.rpm')
    print('if you need to override config, --config /conf.conf')
    print('if you need to override user/apikey, provide')
    print('--user username --apikey apikey')

def validate(context):
    return context['config'] and context['files']

#test photonPublish
def main(argv):
    try:
        context = {
            'config':'/etc/photonpublish.conf',
            'user':'',
            'apikey':'',
            'files':'',
            'path':'',
            'mode':''
            }
        opts, args = getopt.getopt(
            sys.argv[1:],
            '',
            ['config=', 'user=', 'apikey=', 'files=', 'path=', 'mode='])
        for opt, arg in opts:
            if opt == '--config':
                context['config'] = arg
            elif opt == '--user':
                context['user'] = arg
            elif opt == '--apikey':
                context['apikey'] = arg
            elif opt == '--files':
                context['files'] = arg
            elif opt == '--path':
                context['path'] = arg
            elif opt == '--mode':
                context['mode'] = arg

        if not validate(context):
            showUsage()
            sys.exit(1)

        publish = photonPublish(context)
        if context['mode'] == 'upload':
            publish.push(context['files'], context['path'])
        elif context['mode'] == 'check':
            print(publish.check(context['files']))

    except Exception as e:
        print("Error: %s" % e)
        sys.exit(1)

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