Bug 1048446 - [mozinstall] Add ability to download and extract installer from a url, r?whimboo draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Thu, 09 Mar 2017 12:20:03 -0500
changeset 591737 4fddf897fe5ca6807b6c8f4ec65abeac6451efa9
parent 591677 1742b1bdadd13a02df95ca690bea9cc42ff40c91
child 591738 43fe16dae1a02e0b64c154a4ecfb032f2951f92e
push id63159
push userahalberstadt@mozilla.com
push dateFri, 09 Jun 2017 15:00:30 +0000
reviewerswhimboo
bugs1048446
milestone55.0a1
Bug 1048446 - [mozinstall] Add ability to download and extract installer from a url, r?whimboo This is a minor convenience for downloading the installer from a url. It uses requests (which should be available everywhere in-tree). MozReview-Commit-ID: 8IfiVkYNr06
testing/mozbase/mozinstall/mozinstall/mozinstall.py
testing/mozbase/mozinstall/setup.py
testing/mozbase/mozinstall/tests/test.py
--- a/testing/mozbase/mozinstall/mozinstall/mozinstall.py
+++ b/testing/mozbase/mozinstall/mozinstall/mozinstall.py
@@ -3,19 +3,22 @@
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from optparse import OptionParser
 import os
 import shutil
 import subprocess
 import sys
 import tarfile
+import tempfile
 import time
 import zipfile
 
+import requests
+
 import mozfile
 import mozinfo
 
 try:
     import pefile
     has_pefile = True
 except ImportError:
     has_pefile = False
@@ -91,22 +94,31 @@ def get_binary(path, app_name):
 def install(src, dest):
     """Install a zip, exe, tar.gz, tar.bz2 or dmg file, and return the path of
     the installation folder.
 
     :param src: Path to the install file
     :param dest: Path to install to (to ensure we do not overwrite any existent
                  files the folder should not exist yet)
     """
+
+    if not is_installer(src):
+        msg = "{} is not a valid installer file".format(src)
+        if '://' in src:
+            try:
+                return _install_url(src, dest)
+            except:
+                exc, val, tb = sys.exc_info()
+                msg = "{} ({})".format(msg, val)
+                raise InvalidSource, msg, tb
+        raise InvalidSource(msg)
+
     src = os.path.realpath(src)
     dest = os.path.realpath(dest)
 
-    if not is_installer(src):
-        raise InvalidSource(src + ' is not valid installer file.')
-
     did_we_create = False
     if not os.path.exists(dest):
         did_we_create = True
         os.makedirs(dest)
 
     trbk = None
     try:
         install_dir = None
@@ -232,16 +244,36 @@ def uninstall(install_folder):
                 # http://docs.python.org/library/sys.html#sys.exc_info
                 del trbk
 
     # Ensure that we remove any trace of the installation. Even the uninstaller
     # on Windows leaves files behind we have to explicitely remove.
     mozfile.remove(install_folder)
 
 
+def _install_url(url, dest):
+    """Saves a url to a temporary file, and passes that through to the
+    install function.
+
+    :param url: Url to the install file
+    :param dest: Path to install to (to ensure we do not overwrite any existent
+                 files the folder should not exist yet)
+    """
+    r = requests.get(url, stream=True)
+    name = tempfile.mkstemp()[1]
+    try:
+        with open(name, 'w+b') as fh:
+            for chunk in r.iter_content(chunk_size=16*1024):
+                fh.write(chunk)
+        result = install(name, dest)
+    finally:
+        mozfile.remove(name)
+    return result
+
+
 def _install_dmg(src, dest):
     """Extract a dmg file into the destination folder and return the
     application folder.
 
     src -- DMG image which has to be extracted
     dest -- the path to extract to
 
     """
--- a/testing/mozbase/mozinstall/setup.py
+++ b/testing/mozbase/mozinstall/setup.py
@@ -6,20 +6,21 @@ import os
 from setuptools import setup
 
 try:
     here = os.path.dirname(os.path.abspath(__file__))
     description = file(os.path.join(here, 'README.md')).read()
 except IOError:
     description = None
 
-PACKAGE_VERSION = '1.12'
+PACKAGE_VERSION = '1.13'
 
 deps = ['mozinfo >= 0.7',
         'mozfile >= 1.0',
+        'requests',
         ]
 
 setup(name='mozInstall',
       version=PACKAGE_VERSION,
       description="package for installing and uninstalling Mozilla applications",
       long_description="see http://mozbase.readthedocs.org/",
       # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
       classifiers=['Environment :: Console',
--- a/testing/mozbase/mozinstall/tests/test.py
+++ b/testing/mozbase/mozinstall/tests/test.py
@@ -108,16 +108,20 @@ class TestMozInstall(unittest.TestCase):
         elif mozinfo.isWin:
             self.assertRaises(mozinstall.InvalidSource, mozinstall.install,
                               self.bz2, 'firefox')
 
         elif mozinfo.isMac:
             self.assertRaises(mozinstall.InvalidSource, mozinstall.install,
                               self.bz2, 'firefox')
 
+        # Test an invalid url handler
+        self.assertRaises(mozinstall.InvalidSource, mozinstall.install,
+                          'file://foo.bar', 'firefox')
+
     @unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe "
                      "for mozinstall 1.12 and higher.")
     def test_install(self):
         """ Test mozinstall's install capability """
 
         if mozinfo.isLinux:
             installdir = mozinstall.install(self.bz2, self.tempdir)
             self.assertEqual(os.path.join(self.tempdir, 'firefox'), installdir)
@@ -162,10 +166,11 @@ class TestMozInstall(unittest.TestCase):
             mozinstall.uninstall(installdir_zip)
             self.assertFalse(os.path.exists(installdir_zip))
 
         elif mozinfo.isMac:
             installdir = mozinstall.install(self.dmg, self.tempdir)
             mozinstall.uninstall(installdir)
             self.assertFalse(os.path.exists(installdir))
 
+
 if __name__ == '__main__':
     mozunit.main()