github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/acceptancetests/repository/trusty/haproxy/hooks/charmhelpers/fetch/archiveurl.py (about) 1 # Copyright 2014-2015 Canonical Limited. 2 # 3 # This file is part of charm-helpers. 4 # 5 # charm-helpers is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Lesser General Public License version 3 as 7 # published by the Free Software Foundation. 8 # 9 # charm-helpers is distributed in the hope that it will be useful, 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # GNU Lesser General Public License for more details. 13 # 14 # You should have received a copy of the GNU Lesser General Public License 15 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. 16 17 import os 18 import hashlib 19 import re 20 21 from charmhelpers.fetch import ( 22 BaseFetchHandler, 23 UnhandledSource 24 ) 25 from charmhelpers.payload.archive import ( 26 get_archive_handler, 27 extract, 28 ) 29 from charmhelpers.core.host import mkdir, check_hash 30 31 import six 32 if six.PY3: 33 from urllib.request import ( 34 build_opener, install_opener, urlopen, urlretrieve, 35 HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, 36 ) 37 from urllib.parse import urlparse, urlunparse, parse_qs 38 from urllib.error import URLError 39 else: 40 from urllib import urlretrieve 41 from urllib2 import ( 42 build_opener, install_opener, urlopen, 43 HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, 44 URLError 45 ) 46 from urlparse import urlparse, urlunparse, parse_qs 47 48 49 def splituser(host): 50 '''urllib.splituser(), but six's support of this seems broken''' 51 _userprog = re.compile('^(.*)@(.*)$') 52 match = _userprog.match(host) 53 if match: 54 return match.group(1, 2) 55 return None, host 56 57 58 def splitpasswd(user): 59 '''urllib.splitpasswd(), but six's support of this is missing''' 60 _passwdprog = re.compile('^([^:]*):(.*)$', re.S) 61 match = _passwdprog.match(user) 62 if match: 63 return match.group(1, 2) 64 return user, None 65 66 67 class ArchiveUrlFetchHandler(BaseFetchHandler): 68 """ 69 Handler to download archive files from arbitrary URLs. 70 71 Can fetch from http, https, ftp, and file URLs. 72 73 Can install either tarballs (.tar, .tgz, .tbz2, etc) or zip files. 74 75 Installs the contents of the archive in $CHARM_DIR/fetched/. 76 """ 77 def can_handle(self, source): 78 url_parts = self.parse_url(source) 79 if url_parts.scheme not in ('http', 'https', 'ftp', 'file'): 80 return "Wrong source type" 81 if get_archive_handler(self.base_url(source)): 82 return True 83 return False 84 85 def download(self, source, dest): 86 """ 87 Download an archive file. 88 89 :param str source: URL pointing to an archive file. 90 :param str dest: Local path location to download archive file to. 91 """ 92 # propogate all exceptions 93 # URLError, OSError, etc 94 proto, netloc, path, params, query, fragment = urlparse(source) 95 if proto in ('http', 'https'): 96 auth, barehost = splituser(netloc) 97 if auth is not None: 98 source = urlunparse((proto, barehost, path, params, query, fragment)) 99 username, password = splitpasswd(auth) 100 passman = HTTPPasswordMgrWithDefaultRealm() 101 # Realm is set to None in add_password to force the username and password 102 # to be used whatever the realm 103 passman.add_password(None, source, username, password) 104 authhandler = HTTPBasicAuthHandler(passman) 105 opener = build_opener(authhandler) 106 install_opener(opener) 107 response = urlopen(source) 108 try: 109 with open(dest, 'w') as dest_file: 110 dest_file.write(response.read()) 111 except Exception as e: 112 if os.path.isfile(dest): 113 os.unlink(dest) 114 raise e 115 116 # Mandatory file validation via Sha1 or MD5 hashing. 117 def download_and_validate(self, url, hashsum, validate="sha1"): 118 tempfile, headers = urlretrieve(url) 119 check_hash(tempfile, hashsum, validate) 120 return tempfile 121 122 def install(self, source, dest=None, checksum=None, hash_type='sha1'): 123 """ 124 Download and install an archive file, with optional checksum validation. 125 126 The checksum can also be given on the `source` URL's fragment. 127 For example:: 128 129 handler.install('http://example.com/file.tgz#sha1=deadbeef') 130 131 :param str source: URL pointing to an archive file. 132 :param str dest: Local destination path to install to. If not given, 133 installs to `$CHARM_DIR/archives/archive_file_name`. 134 :param str checksum: If given, validate the archive file after download. 135 :param str hash_type: Algorithm used to generate `checksum`. 136 Can be any hash alrgorithm supported by :mod:`hashlib`, 137 such as md5, sha1, sha256, sha512, etc. 138 139 """ 140 url_parts = self.parse_url(source) 141 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched') 142 if not os.path.exists(dest_dir): 143 mkdir(dest_dir, perms=0o755) 144 dld_file = os.path.join(dest_dir, os.path.basename(url_parts.path)) 145 try: 146 self.download(source, dld_file) 147 except URLError as e: 148 raise UnhandledSource(e.reason) 149 except OSError as e: 150 raise UnhandledSource(e.strerror) 151 options = parse_qs(url_parts.fragment) 152 for key, value in options.items(): 153 if not six.PY3: 154 algorithms = hashlib.algorithms 155 else: 156 algorithms = hashlib.algorithms_available 157 if key in algorithms: 158 check_hash(dld_file, value, key) 159 if checksum: 160 check_hash(dld_file, checksum, hash_type) 161 return extract(dld_file, dest)