Blob Blame History Raw
# Copyright (c) 2018 - Red Hat Inc.
#
# 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; either version 2 of the License, or (at your
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
# the full text of the license.


"""Interact with the Red Hat lookaside cache

We need to override the pyrpkg.lookasidecache module to handle our custom
download path.
"""

import io
import os
import pycurl
import six
import sys


from pyrpkg.errors import InvalidHashType, UploadError
from pyrpkg.lookaside import CGILookasideCache


class StreamLookasideCache(CGILookasideCache):
    def __init__(self, hashtype, download_url, upload_url):
        super(StreamLookasideCache, self).__init__(
            hashtype, download_url, upload_url)

        self.download_path = (
            '%(name)s/%(filename)s/%(hashtype)s/%(hash)s/%(filename)s')


class SIGLookasideCache(CGILookasideCache):
    def __init__(self, hashtype, download_url, upload_url, name, branch):
        super(SIGLookasideCache, self).__init__(
            hashtype, download_url, upload_url, client_cert="/home/bstinson/.centos.cert")
        self.branch = branch

        self.download_path = (
            '%(name)s/%(branch)s/%(hash)s')

    def remote_file_exists(self, name, filename, hash):
        """Verify whether a file exists on the lookaside cache

        :param str name: The name of the module. (usually the name of the
            SRPM). This can include the namespace as well (depending on what
            the server side expects).
        :param str filename: The name of the file to check for.
        :param str hash: The known good hash of the file.
        """

        # RHEL 7 ships pycurl that does not accept unicode. When given unicode
        # type it would explode with "unsupported second type in tuple". Let's
        # convert to str just to be sure.
        # https://bugzilla.redhat.com/show_bug.cgi?id=1241059
        if six.PY2 and isinstance(filename, six.text_type):
            filename = filename.encode('utf-8')

        if six.PY2 and isinstance(self.branch, six.text_type):
            self.branch = self.branch.encode('utf-8')

        post_data = [('name', name),
                     ('%ssum' % self.hashtype, hash),
                     ('branch', self.branch),
                     ('filename', filename)]

        with io.BytesIO() as buf:
            c = pycurl.Curl()
            c.setopt(pycurl.URL, self.upload_url)
            c.setopt(pycurl.WRITEFUNCTION, buf.write)
            c.setopt(pycurl.HTTPPOST, post_data)

            if self.client_cert is not None:
                if os.path.exists(self.client_cert):
                    c.setopt(pycurl.SSLCERT, self.client_cert)
                else:
                    self.log.warning("Missing certificate: %s"
                                     % self.client_cert)

            if self.ca_cert is not None:
                if os.path.exists(self.ca_cert):
                    c.setopt(pycurl.CAINFO, self.ca_cert)
                else:
                    self.log.warning("Missing certificate: %s", self.ca_cert)

            c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
            c.setopt(pycurl.USERPWD, ':')

            try:
                c.perform()
                status = c.getinfo(pycurl.RESPONSE_CODE)

            except Exception as e:
                raise UploadError(e)

            finally:
                c.close()

            output = buf.getvalue().strip()

        if status != 200:
            self.raise_upload_error(status)

        # Lookaside CGI script returns these strings depending on whether
        # or not the file exists:
        if output == b'Available':
            return True

        if output == b'Missing':
            return False

        # Something unexpected happened
        self.log.debug(output)
        raise UploadError('Error checking for %s at %s'
                          % (filename, self.upload_url))

    def upload(self, name, filepath, hash):
        """Upload a source file

        :param str name: The name of the module. (usually the name of the SRPM)
            This can include the namespace as well (depending on what the
            server side expects).
        :param str filepath: The full path to the file to upload.
        :param str hash: The known good hash of the file.
        """
        filename = os.path.basename(filepath)

        # As in remote_file_exists, we need to convert unicode strings to str
        if six.PY2:
            if isinstance(name, six.text_type):
                name = name.encode('utf-8')
            if isinstance(filepath, six.text_type):
                filepath = filepath.encode('utf-8')

        if self.remote_file_exists(name, filename, hash):
            self.log.info("File already uploaded: %s", filepath)
            return

        self.log.info("Uploading: %s", filepath)
        post_data = [('name', name),
                     ('%ssum' % self.hashtype, hash),
                     ('branch', self.branch),
                     ('file', (pycurl.FORM_FILE, filepath))]

        with io.BytesIO() as buf:
            c = pycurl.Curl()
            c.setopt(pycurl.URL, self.upload_url)
            c.setopt(pycurl.NOPROGRESS, False)
            c.setopt(pycurl.PROGRESSFUNCTION, self.print_progress)
            c.setopt(pycurl.WRITEFUNCTION, buf.write)
            c.setopt(pycurl.HTTPPOST, post_data)

            if self.client_cert is not None:
                if os.path.exists(self.client_cert):
                    c.setopt(pycurl.SSLCERT, self.client_cert)
                else:
                    self.log.warning("Missing certificate: %s", self.client_cert)

            if self.ca_cert is not None:
                if os.path.exists(self.ca_cert):
                    c.setopt(pycurl.CAINFO, self.ca_cert)
                else:
                    self.log.warning("Missing certificate: %s", self.ca_cert)

            c.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_GSSNEGOTIATE)
            c.setopt(pycurl.USERPWD, ':')

            try:
                c.perform()
                status = c.getinfo(pycurl.RESPONSE_CODE)

            except Exception as e:
                raise UploadError(e)

            finally:
                c.close()

            output = buf.getvalue().strip()

        # Get back a new line, after displaying the download progress
        sys.stdout.write('\n')
        sys.stdout.flush()

        if status != 200:
            self.raise_upload_error(status)

        if output:
            self.log.debug(output)