2013-03-17 17:06:52 -04:00
#
2013-10-27 21:59:47 +01:00
# Copyright 2006-2007, 2013 Red Hat, Inc.
2013-03-17 17:06:52 -04:00
#
2018-04-04 14:35:41 +01:00
# This work is licensed under the GNU GPLv2 or later.
2018-03-20 15:00:02 -04:00
# See the COPYING file in the top-level directory.
2013-03-17 17:06:52 -04:00
2013-08-08 20:14:10 -04:00
import ftplib
2017-10-11 12:35:50 +01:00
import io
2013-03-17 17:06:52 -04:00
import logging
import os
2013-08-08 20:14:10 -04:00
import subprocess
import tempfile
2018-04-03 11:38:37 -04:00
import urllib
2013-08-08 20:14:10 -04:00
2016-04-18 16:42:12 -04:00
import requests
2013-04-13 14:34:52 -04:00
2018-06-12 13:49:25 -04:00
###########################################################################
# Backends for the various URL types we support (http, https, ftp, local) #
###########################################################################
2013-04-13 14:34:52 -04:00
2015-09-18 16:49:18 -04:00
class _URLFetcher ( object ) :
2013-08-08 20:14:10 -04:00
"""
This is a generic base class for fetching / extracting files from
2018-06-12 13:49:25 -04:00
a media source , such as CD ISO , or HTTP / HTTPS / FTP server
2013-08-08 20:14:10 -04:00
"""
2015-09-18 18:31:56 -04:00
_block_size = 16384
2018-10-09 15:52:23 +02:00
_is_iso = False
2015-09-18 18:31:56 -04:00
2013-09-26 13:04:28 -04:00
def __init__ ( self , location , scratchdir , meter ) :
2013-08-08 20:14:10 -04:00
self . location = location
self . scratchdir = scratchdir
2013-09-26 13:04:28 -04:00
self . meter = meter
2013-08-08 20:14:10 -04:00
2015-09-18 16:49:18 -04:00
logging . debug ( " Using scratchdir= %s " , scratchdir )
2019-01-29 17:00:30 -05:00
self . _prepare ( )
2014-02-14 11:02:35 -05:00
2015-09-18 18:31:56 -04:00
2015-09-18 16:49:18 -04:00
####################
# Internal helpers #
####################
def _make_full_url ( self , filename ) :
"""
Generate a full fetchable URL from the passed filename , which
is relative to the self . location
"""
2019-01-29 15:11:03 -05:00
if self . _is_iso :
return os . path . join ( " / " , filename )
2015-09-18 16:49:18 -04:00
if not filename :
2019-01-29 15:11:03 -05:00
return self . location
return os . path . join ( self . location , filename )
2015-09-18 16:49:18 -04:00
2015-09-18 18:31:56 -04:00
def _grabURL ( self , filename , fileobj ) :
2015-09-18 16:49:18 -04:00
"""
2015-09-18 18:31:56 -04:00
Download the filename from self . location , and write contents to
fileobj
2015-09-18 16:49:18 -04:00
"""
2015-09-18 18:31:56 -04:00
url = self . _make_full_url ( filename )
try :
urlobj , size = self . _grabber ( url )
2017-05-05 12:47:21 -04:00
except Exception as e :
2015-09-18 18:31:56 -04:00
raise ValueError ( _ ( " Couldn ' t acquire file %s : %s " ) %
( url , str ( e ) ) )
logging . debug ( " Fetching URI: %s " , url )
self . meter . start (
text = _ ( " Retrieving file %s ... " ) % os . path . basename ( filename ) ,
size = size )
total = self . _write ( urlobj , fileobj )
self . meter . end ( total )
def _write ( self , urlobj , fileobj ) :
"""
Write the contents of urlobj to python file like object fileobj
"""
total = 0
2015-09-18 17:11:37 -04:00
while 1 :
2015-09-18 18:31:56 -04:00
buff = urlobj . read ( self . _block_size )
2015-09-18 17:11:37 -04:00
if not buff :
break
fileobj . write ( buff )
2015-09-18 18:31:56 -04:00
total + = len ( buff )
self . meter . update ( total )
return total
2013-08-08 20:14:10 -04:00
2015-09-18 18:31:56 -04:00
def _grabber ( self , url ) :
2015-09-18 18:01:57 -04:00
"""
2015-09-18 18:31:56 -04:00
Returns the urlobj , size for the passed URL . urlobj is whatever
data needs to be passed to self . _write
2015-09-18 18:01:57 -04:00
"""
2015-09-18 18:31:56 -04:00
raise NotImplementedError ( " must be implemented in subclass " )
2015-09-18 18:01:57 -04:00
2015-09-18 16:49:18 -04:00
##############
# Public API #
##############
2018-10-09 15:52:23 +02:00
def is_iso ( self ) :
"""
If this is a fetcher for local CDROM / ISO
"""
return self . _is_iso
2019-01-29 17:00:30 -05:00
def _prepare ( self ) :
2015-09-18 16:49:18 -04:00
"""
Perform any necessary setup
"""
2013-08-08 20:14:10 -04:00
2019-01-29 17:00:30 -05:00
def _cleanup ( self ) :
2015-09-18 16:49:18 -04:00
"""
Perform any necessary cleanup
"""
2013-08-08 20:14:10 -04:00
2019-01-29 17:00:30 -05:00
def __del__ ( self ) :
self . _cleanup ( )
2018-03-29 19:22:28 -04:00
def can_access ( self ) :
"""
Return True if the location URL seems to be valid
"""
return True
2016-03-24 14:51:36 -04:00
def _hasFile ( self , url ) :
raise NotImplementedError ( " Must be implemented in subclass " )
2015-09-18 16:49:18 -04:00
def hasFile ( self , filename ) :
"""
Return True if self . location has the passed filename
"""
2016-03-24 14:51:36 -04:00
url = self . _make_full_url ( filename )
ret = self . _hasFile ( url )
logging . debug ( " hasFile( %s ) returning %s " , url , ret )
return ret
2015-09-18 16:49:18 -04:00
2013-09-26 13:04:28 -04:00
def acquireFile ( self , filename ) :
2015-09-18 16:49:18 -04:00
"""
Grab the passed filename from self . location and save it to
a temporary file , returning the temp filename
"""
2015-09-18 18:01:57 -04:00
prefix = " virtinst- " + os . path . basename ( filename ) + " . "
2013-08-08 20:14:10 -04:00
2016-04-18 16:42:12 -04:00
# pylint: disable=redefined-variable-type
2015-09-18 18:01:57 -04:00
if " VIRTINST_TEST_SUITE " in os . environ :
2015-09-18 18:31:56 -04:00
fn = os . path . join ( " /tmp " , prefix )
2017-10-21 19:33:30 -04:00
fileobj = open ( fn , " wb " )
2015-09-18 18:01:57 -04:00
else :
fileobj = tempfile . NamedTemporaryFile (
dir = self . scratchdir , prefix = prefix , delete = False )
2015-09-18 18:31:56 -04:00
fn = fileobj . name
2013-08-08 20:14:10 -04:00
2015-09-18 18:31:56 -04:00
self . _grabURL ( filename , fileobj )
2018-03-02 08:01:23 +00:00
logging . debug ( " Saved file to %s " , fn )
2015-09-18 18:31:56 -04:00
return fn
2015-09-18 18:01:57 -04:00
def acquireFileContent ( self , filename ) :
"""
Grab the passed filename from self . location and return it as a string
"""
2017-11-08 01:23:28 -05:00
fileobj = io . BytesIO ( )
2015-09-18 18:31:56 -04:00
self . _grabURL ( filename , fileobj )
2018-01-27 15:23:22 -05:00
return fileobj . getvalue ( ) . decode ( " utf-8 " )
2013-08-08 20:14:10 -04:00
2015-09-18 16:49:18 -04:00
class _HTTPURLFetcher ( _URLFetcher ) :
2018-03-29 17:56:40 -04:00
_session = None
2019-01-29 17:00:30 -05:00
def _prepare ( self ) :
2018-03-29 17:56:40 -04:00
self . _session = requests . Session ( )
2019-01-29 17:00:30 -05:00
def _cleanup ( self ) :
2018-03-29 17:56:40 -04:00
if self . _session :
try :
self . _session . close ( )
except Exception :
logging . debug ( " Error closing requests.session " , exc_info = True )
self . _session = None
2018-03-29 19:22:28 -04:00
def can_access ( self ) :
return self . hasFile ( " " )
2016-03-24 14:51:36 -04:00
def _hasFile ( self , url ) :
2015-09-18 18:31:56 -04:00
"""
We just do a HEAD request to see if the file exists
"""
2013-08-08 20:14:10 -04:00
try :
2018-03-29 17:56:40 -04:00
response = self . _session . head ( url , allow_redirects = True )
2015-09-18 18:31:56 -04:00
response . raise_for_status ( )
2017-05-05 12:47:21 -04:00
except Exception as e :
2016-03-24 14:51:36 -04:00
logging . debug ( " HTTP hasFile request failed: %s " , str ( e ) )
2013-08-08 20:14:10 -04:00
return False
return True
2015-09-18 18:31:56 -04:00
def _grabber ( self , url ) :
"""
Use requests for this
"""
2018-03-29 17:56:40 -04:00
response = self . _session . get ( url , stream = True )
2015-09-18 18:31:56 -04:00
response . raise_for_status ( )
2016-01-12 12:45:02 -05:00
try :
size = int ( response . headers . get ( ' content-length ' ) )
2017-07-24 09:26:48 +01:00
except Exception :
2016-01-12 12:45:02 -05:00
size = None
return response , size
2015-09-18 18:31:56 -04:00
def _write ( self , urlobj , fileobj ) :
"""
The requests object doesn ' t have a file-like read() option, so
2018-05-21 20:42:50 +01:00
we need to implement it ourselves
2015-09-18 18:31:56 -04:00
"""
total = 0
for data in urlobj . iter_content ( chunk_size = self . _block_size ) :
fileobj . write ( data )
total + = len ( data )
self . meter . update ( total )
return total
2013-08-08 20:14:10 -04:00
2015-09-18 16:49:18 -04:00
class _FTPURLFetcher ( _URLFetcher ) :
_ftp = None
2013-08-08 20:14:10 -04:00
2019-01-29 17:00:30 -05:00
def _prepare ( self ) :
2015-09-18 16:49:18 -04:00
if self . _ftp :
2014-09-07 14:22:56 -04:00
return
virtinst: early detect ftp connection errors
It fixes two problems:
i) "ftp://" was accepted as valid URL but then it causes this
exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib64/python2.7/ftplib.py", line 387, in login
resp = self.sendcmd('USER ' + user)
File "/usr/lib64/python2.7/ftplib.py", line 243, in sendcmd
self.putcmd(cmd)
File "/usr/lib64/python2.7/ftplib.py", line 178, in putcmd
self.putline(line)
File "/usr/lib64/python2.7/ftplib.py", line 173, in putline
self.sock.sendall(line)
AttributeError: 'NoneType' object has no attribute 'sendall'
ii) only a cryptic error message "Unable to complete install: '[Errno
-2] Name or service not known'" was showed to users when the DNS
lookup failed. The exception is now intercepted and decorated with
more information.
Closes: https://bugzilla.redhat.com/show_bug.cgi?id=1086554
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2014-04-14 14:49:21 +02:00
try :
2018-01-27 14:19:12 -05:00
parsed = urllib . parse . urlparse ( self . location )
2016-03-24 16:15:21 -04:00
self . _ftp = ftplib . FTP ( )
2018-04-18 14:23:33 +01:00
username = urllib . parse . unquote ( parsed . username or ' ' )
password = urllib . parse . unquote ( parsed . password or ' ' )
2018-01-27 15:23:22 -05:00
self . _ftp . connect ( parsed . hostname , parsed . port or 0 )
2018-04-18 14:23:33 +01:00
self . _ftp . login ( username , password )
2017-08-17 16:10:46 -04:00
# Force binary mode
self . _ftp . voidcmd ( " TYPE I " )
2017-05-05 12:47:21 -04:00
except Exception as e :
virtinst: early detect ftp connection errors
It fixes two problems:
i) "ftp://" was accepted as valid URL but then it causes this
exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib64/python2.7/ftplib.py", line 387, in login
resp = self.sendcmd('USER ' + user)
File "/usr/lib64/python2.7/ftplib.py", line 243, in sendcmd
self.putcmd(cmd)
File "/usr/lib64/python2.7/ftplib.py", line 178, in putcmd
self.putline(line)
File "/usr/lib64/python2.7/ftplib.py", line 173, in putline
self.sock.sendall(line)
AttributeError: 'NoneType' object has no attribute 'sendall'
ii) only a cryptic error message "Unable to complete install: '[Errno
-2] Name or service not known'" was showed to users when the DNS
lookup failed. The exception is now intercepted and decorated with
more information.
Closes: https://bugzilla.redhat.com/show_bug.cgi?id=1086554
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
2014-04-14 14:49:21 +02:00
raise ValueError ( _ ( " Opening URL %s failed: %s . " ) %
( self . location , str ( e ) ) )
2015-09-18 18:31:56 -04:00
def _grabber ( self , url ) :
"""
2017-10-11 12:35:49 +01:00
Use urllib and ftplib to grab the file
2015-09-18 18:31:56 -04:00
"""
2018-01-27 14:19:12 -05:00
request = urllib . request . Request ( url )
urlobj = urllib . request . urlopen ( request )
size = self . _ftp . size ( urllib . parse . urlparse ( url ) [ 2 ] )
2015-09-18 18:31:56 -04:00
return urlobj , size
2019-01-29 17:00:30 -05:00
def _cleanup ( self ) :
2015-09-18 16:49:18 -04:00
if not self . _ftp :
2014-09-07 14:22:56 -04:00
return
try :
2015-09-18 16:49:18 -04:00
self . _ftp . quit ( )
2017-07-24 09:26:48 +01:00
except Exception :
2014-09-07 14:22:56 -04:00
logging . debug ( " Error quitting ftp connection " , exc_info = True )
2015-09-18 16:49:18 -04:00
self . _ftp = None
2013-08-08 20:14:10 -04:00
2016-03-24 14:51:36 -04:00
def _hasFile ( self , url ) :
2018-01-27 14:19:12 -05:00
path = urllib . parse . urlparse ( url ) [ 2 ]
2013-08-08 20:14:10 -04:00
try :
try :
# If it's a file
2015-09-18 18:31:56 -04:00
self . _ftp . size ( path )
2013-08-08 20:14:10 -04:00
except ftplib . all_errors :
# If it's a dir
2015-09-18 18:31:56 -04:00
self . _ftp . cwd ( path )
2017-05-05 12:47:21 -04:00
except ftplib . all_errors as e :
2013-08-08 20:14:10 -04:00
logging . debug ( " FTP hasFile: couldn ' t access %s : %s " ,
2015-09-18 16:49:18 -04:00
url , str ( e ) )
2013-08-08 20:14:10 -04:00
return False
return True
2015-09-18 16:49:18 -04:00
class _LocalURLFetcher ( _URLFetcher ) :
"""
For grabbing files from a local directory
"""
2016-03-24 14:51:36 -04:00
def _hasFile ( self , url ) :
return os . path . exists ( url )
2013-08-08 20:14:10 -04:00
2015-09-18 18:31:56 -04:00
def _grabber ( self , url ) :
2018-01-27 14:08:46 -05:00
urlobj = open ( url , " rb " )
2015-09-18 18:31:56 -04:00
size = os . path . getsize ( url )
return urlobj , size
2013-08-08 20:14:10 -04:00
2017-11-24 00:11:14 -05:00
class _ISOURLFetcher ( _URLFetcher ) :
_cache_file_list = None
2018-10-09 15:52:23 +02:00
_is_iso = True
2017-11-24 00:11:14 -05:00
def _grabber ( self , url ) :
"""
Use isoinfo to grab the file
"""
2018-03-28 14:02:23 -04:00
if not self . _hasFile ( url ) :
raise RuntimeError ( " isoinfo didn ' t find file= %s " % url )
2017-11-24 00:11:14 -05:00
cmd = [ " isoinfo " , " -J " , " -i " , self . location , " -x " , url ]
logging . debug ( " Running isoinfo: %s " , cmd )
output = subprocess . check_output ( cmd )
return io . BytesIO ( output ) , len ( output )
def _hasFile ( self , url ) :
"""
Use isoinfo to list and search for the file
"""
if not self . _cache_file_list :
cmd = [ " isoinfo " , " -J " , " -i " , self . location , " -f " ]
logging . debug ( " Running isoinfo: %s " , cmd )
2019-01-31 18:14:37 -05:00
output = subprocess . check_output ( cmd , stderr = subprocess . DEVNULL )
2017-11-24 00:11:14 -05:00
self . _cache_file_list = output . splitlines ( False )
2018-01-27 15:23:22 -05:00
return url . encode ( " ascii " ) in self . _cache_file_list
2017-11-24 00:11:14 -05:00
2013-09-26 13:04:28 -04:00
def fetcherForURI ( uri , * args , * * kwargs ) :
2013-09-26 10:24:28 -04:00
if uri . startswith ( " http:// " ) or uri . startswith ( " https:// " ) :
2015-09-18 16:49:18 -04:00
fclass = _HTTPURLFetcher
2013-03-17 17:06:52 -04:00
elif uri . startswith ( " ftp:// " ) :
2015-09-18 16:49:18 -04:00
fclass = _FTPURLFetcher
elif os . path . isdir ( uri ) :
# Pointing to a local tree
fclass = _LocalURLFetcher
2013-03-17 17:06:52 -04:00
else :
2017-11-24 00:11:14 -05:00
# Pointing to a path (e.g. iso), or a block device (e.g. /dev/cdrom)
fclass = _ISOURLFetcher
2013-09-26 13:04:28 -04:00
return fclass ( uri , * args , * * kwargs )