~quobyte/charms/trusty/quobyte-client/trunk

« back to all changes in this revision

Viewing changes to hooks/charmhelpers/fetch/archiveurl.py

  • Committer: Bruno Ranieri
  • Date: 2016-07-13 14:39:00 UTC
  • Revision ID: bruno@quobyte.com-20160713143900-538ecurz18j2z4fq
Initial charm

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2014-2015 Canonical Limited.
 
2
#
 
3
# Licensed under the Apache License, Version 2.0 (the "License");
 
4
# you may not use this file except in compliance with the License.
 
5
# You may obtain a copy of the License at
 
6
#
 
7
#  http://www.apache.org/licenses/LICENSE-2.0
 
8
#
 
9
# Unless required by applicable law or agreed to in writing, software
 
10
# distributed under the License is distributed on an "AS IS" BASIS,
 
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
12
# See the License for the specific language governing permissions and
 
13
# limitations under the License.
 
14
 
 
15
import os
 
16
import hashlib
 
17
import re
 
18
 
 
19
from charmhelpers.fetch import (
 
20
    BaseFetchHandler,
 
21
    UnhandledSource
 
22
)
 
23
from charmhelpers.payload.archive import (
 
24
    get_archive_handler,
 
25
    extract,
 
26
)
 
27
from charmhelpers.core.host import mkdir, check_hash
 
28
 
 
29
import six
 
30
if six.PY3:
 
31
    from urllib.request import (
 
32
        build_opener, install_opener, urlopen, urlretrieve,
 
33
        HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler,
 
34
    )
 
35
    from urllib.parse import urlparse, urlunparse, parse_qs
 
36
    from urllib.error import URLError
 
37
else:
 
38
    from urllib import urlretrieve
 
39
    from urllib2 import (
 
40
        build_opener, install_opener, urlopen,
 
41
        HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler,
 
42
        URLError
 
43
    )
 
44
    from urlparse import urlparse, urlunparse, parse_qs
 
45
 
 
46
 
 
47
def splituser(host):
 
48
    '''urllib.splituser(), but six's support of this seems broken'''
 
49
    _userprog = re.compile('^(.*)@(.*)$')
 
50
    match = _userprog.match(host)
 
51
    if match:
 
52
        return match.group(1, 2)
 
53
    return None, host
 
54
 
 
55
 
 
56
def splitpasswd(user):
 
57
    '''urllib.splitpasswd(), but six's support of this is missing'''
 
58
    _passwdprog = re.compile('^([^:]*):(.*)$', re.S)
 
59
    match = _passwdprog.match(user)
 
60
    if match:
 
61
        return match.group(1, 2)
 
62
    return user, None
 
63
 
 
64
 
 
65
class ArchiveUrlFetchHandler(BaseFetchHandler):
 
66
    """
 
67
    Handler to download archive files from arbitrary URLs.
 
68
 
 
69
    Can fetch from http, https, ftp, and file URLs.
 
70
 
 
71
    Can install either tarballs (.tar, .tgz, .tbz2, etc) or zip files.
 
72
 
 
73
    Installs the contents of the archive in $CHARM_DIR/fetched/.
 
74
    """
 
75
    def can_handle(self, source):
 
76
        url_parts = self.parse_url(source)
 
77
        if url_parts.scheme not in ('http', 'https', 'ftp', 'file'):
 
78
            # XXX: Why is this returning a boolean and a string? It's
 
79
            # doomed to fail since "bool(can_handle('foo://'))"  will be True.
 
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, 'wb') 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
                if len(value) != 1:
 
159
                    raise TypeError(
 
160
                        "Expected 1 hash value, not %d" % len(value))
 
161
                expected = value[0]
 
162
                check_hash(dld_file, expected, key)
 
163
        if checksum:
 
164
            check_hash(dld_file, checksum, hash_type)
 
165
        return extract(dld_file, dest)