# 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)