From b3339ef2f8b1091d932e2936fe825ddde31e129d Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Thu, 2 May 2019 06:33:04 -0700 Subject: [PATCH 1/9] Add Python 3 support to installinstallmacos.py --- installinstallmacos.py | 152 +++++++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 67 deletions(-) diff --git a/installinstallmacos.py b/installinstallmacos.py index 3d5793b..4085dc8 100755 --- a/installinstallmacos.py +++ b/installinstallmacos.py @@ -23,17 +23,28 @@ A tool to download the parts for an Install macOS app from Apple's softwareupdate servers and install a functioning Install macOS app onto an empty disk image''' +# Python 2/3 compatibility shims +from __future__ import (absolute_import, + division, + print_function, + unicode_literals) +from builtins import input +# pylint: disable=wrong-import-order,wrong-import-position import argparse import gzip import os import plistlib import subprocess import sys -import urlparse +try: + from urllib.parse import urlsplit +except ImportError: + from urlparse import urlsplit import xattr from xml.dom import minidom from xml.parsers.expat import ExpatError +# pylint: enable=wrong-import-order,wrong-import-position DEFAULT_SUCATALOGS = { @@ -56,7 +67,7 @@ def get_seeding_program(sucatalog_url): '''Returns a seeding program name based on the sucatalog_url''' try: seed_catalogs = plistlib.readPlist(SEED_CATALOGS_PLIST) - for key, value in seed_catalogs.items(): + for key, value in list(seed_catalogs.items()): if sucatalog_url == value: return key return '' @@ -77,7 +88,7 @@ def get_seeding_programs(): '''Returns the list of seeding program names''' try: seed_catalogs = plistlib.readPlist(SEED_CATALOGS_PLIST) - return seed_catalogs.keys() + return list(seed_catalogs.keys()) except (OSError, ExpatError, AttributeError, KeyError): return '' @@ -94,17 +105,17 @@ def make_sparse_image(volume_name, output_path): '-volname', volume_name, '-type', 'SPARSE', '-plist', output_path] try: output = subprocess.check_output(cmd) - except subprocess.CalledProcessError, err: - print >> sys.stderr, err + except subprocess.CalledProcessError as err: + print(err, file=sys.stderr) exit(-1) try: return plistlib.readPlistFromString(output)[0] - except IndexError, err: - print >> sys.stderr, 'Unexpected output from hdiutil: %s' % output + except IndexError as err: + print('Unexpected output from hdiutil: %s' % output, file=sys.stderr) exit(-1) - except ExpatError, err: - print >> sys.stderr, 'Malformed output from hdiutil: %s' % output - print >> sys.stderr, err + except ExpatError as err: + print('Malformed output from hdiutil: %s' % output, file=sys.stderr) + print(err, file=sys.stderr) exit(-1) @@ -112,16 +123,16 @@ def make_compressed_dmg(app_path, diskimagepath): """Returns path to newly-created compressed r/o disk image containing Install macOS.app""" - print ('Making read-only compressed disk image containing %s...' - % os.path.basename(app_path)) + print(('Making read-only compressed disk image containing %s...' + % os.path.basename(app_path))) cmd = ['/usr/bin/hdiutil', 'create', '-fs', 'HFS+', '-srcfolder', app_path, diskimagepath] try: subprocess.check_call(cmd) - except subprocess.CalledProcessError, err: - print >> sys.stderr, err + except subprocess.CalledProcessError as err: + print(err, file=sys.stderr) else: - print 'Disk image created at: %s' % diskimagepath + print('Disk image created at: %s' % diskimagepath) def mountdmg(dmgpath): @@ -137,7 +148,8 @@ def mountdmg(dmgpath): stdout=subprocess.PIPE, stderr=subprocess.PIPE) (pliststr, err) = proc.communicate() if proc.returncode: - print >> sys.stderr, 'Error: "%s" while mounting %s.' % (err, dmgname) + print( + 'Error: "%s" while mounting %s.' % (err, dmgname), file=sys.stderr) return None if pliststr: plist = plistlib.readPlistFromString(pliststr) @@ -157,13 +169,13 @@ def unmountdmg(mountpoint): stderr=subprocess.PIPE) (dummy_output, err) = proc.communicate() if proc.returncode: - print >> sys.stderr, 'Polite unmount failed: %s' % err - print >> sys.stderr, 'Attempting to force unmount %s' % mountpoint + print('Polite unmount failed: %s' % err, file=sys.stderr) + print('Attempting to force unmount %s' % mountpoint, file=sys.stderr) # try forcing the unmount retcode = subprocess.call(['/usr/bin/hdiutil', 'detach', mountpoint, '-force']) if retcode: - print >> sys.stderr, 'Failed to unmount %s' % mountpoint + print('Failed to unmount %s' % mountpoint, file=sys.stderr) def install_product(dist_path, target_vol): @@ -173,8 +185,8 @@ def install_product(dist_path, target_vol): try: subprocess.check_call(cmd) return True - except subprocess.CalledProcessError, err: - print >> sys.stderr, err + except subprocess.CalledProcessError as err: + print(err, file=sys.stderr) return False @@ -191,7 +203,7 @@ def replicate_url(full_url, '''Downloads a URL and stores it in the same relative path on our filesystem. Returns a path to the replicated file.''' - path = urlparse.urlsplit(full_url)[2] + path = urlsplit(full_url)[2] relative_url = path.lstrip('/') relative_url = os.path.normpath(relative_url) local_file_path = os.path.join(root_dir, relative_url) @@ -206,10 +218,10 @@ def replicate_url(full_url, if attempt_resume: curl_cmd.extend(['-C', '-']) curl_cmd.append(full_url) - print "Downloading %s..." % full_url + print("Downloading %s..." % full_url) try: subprocess.check_call(curl_cmd) - except subprocess.CalledProcessError, err: + except subprocess.CalledProcessError as err: raise ReplicationError(err) return local_file_path @@ -222,8 +234,8 @@ def parse_server_metadata(filename): vers = '' try: md_plist = plistlib.readPlist(filename) - except (OSError, IOError, ExpatError), err: - print >> sys.stderr, 'Error reading %s: %s' % (filename, err) + except (OSError, IOError, ExpatError) as err: + print('Error reading %s: %s' % (filename, err), file=sys.stderr) return {} vers = md_plist.get('CFBundleShortVersionString', '') localization = md_plist.get('localization', {}) @@ -246,12 +258,12 @@ def get_server_metadata(catalog, product_key, workdir, ignore_cache=False): smd_path = replicate_url( url, root_dir=workdir, ignore_cache=ignore_cache) return smd_path - except ReplicationError, err: - print >> sys.stderr, ( - 'Could not replicate %s: %s' % (url, err)) + except ReplicationError as err: + print(( + 'Could not replicate %s: %s' % (url, err)), file=sys.stderr) return None except KeyError: - print >> sys.stderr, 'Malformed catalog.' + print('Malformed catalog.', file=sys.stderr) return None @@ -262,10 +274,10 @@ def parse_dist(filename): try: dom = minidom.parse(filename) except ExpatError: - print >> sys.stderr, 'Invalid XML in %s' % filename + print('Invalid XML in %s' % filename, file=sys.stderr) return dist_info - except IOError, err: - print >> sys.stderr, 'Error reading %s: %s' % (filename, err) + except IOError as err: + print('Error reading %s: %s' % (filename, err), file=sys.stderr) return dist_info auxinfos = dom.getElementsByTagName('auxinfo') @@ -299,8 +311,8 @@ def download_and_parse_sucatalog(sucatalog, workdir, ignore_cache=False): try: localcatalogpath = replicate_url( sucatalog, root_dir=workdir, ignore_cache=ignore_cache) - except ReplicationError, err: - print >> sys.stderr, 'Could not replicate %s: %s' % (sucatalog, err) + except ReplicationError as err: + print('Could not replicate %s: %s' % (sucatalog, err), file=sys.stderr) exit(-1) if os.path.splitext(localcatalogpath)[1] == '.gz': with gzip.open(localcatalogpath) as the_file: @@ -308,17 +320,19 @@ def download_and_parse_sucatalog(sucatalog, workdir, ignore_cache=False): try: catalog = plistlib.readPlistFromString(content) return catalog - except ExpatError, err: - print >> sys.stderr, ( - 'Error reading %s: %s' % (localcatalogpath, err)) + except ExpatError as err: + print( + 'Error reading %s: %s' % (localcatalogpath, err), + file=sys.stderr) exit(-1) else: try: catalog = plistlib.readPlist(localcatalogpath) return catalog - except (OSError, IOError, ExpatError), err: - print >> sys.stderr, ( - 'Error reading %s: %s' % (localcatalogpath, err)) + except (OSError, IOError, ExpatError) as err: + print( + 'Error reading %s: %s' % (localcatalogpath, err), + file=sys.stderr) exit(-1) @@ -355,8 +369,9 @@ def os_installer_product_info(catalog, workdir, ignore_cache=False): try: dist_path = replicate_url( dist_url, root_dir=workdir, ignore_cache=ignore_cache) - except ReplicationError, err: - print >> sys.stderr, 'Could not replicate %s: %s' % (dist_url, err) + except ReplicationError as err: + print( + 'Could not replicate %s: %s' % (dist_url, err), file=sys.stderr) dist_info = parse_dist(dist_path) product_info[product_key]['DistributionPath'] = dist_path product_info[product_key].update(dist_info) @@ -377,18 +392,19 @@ def replicate_product(catalog, product_id, workdir, ignore_cache=False): package['URL'], root_dir=workdir, show_progress=True, ignore_cache=ignore_cache, attempt_resume=(not ignore_cache)) - except ReplicationError, err: - print >> sys.stderr, ( - 'Could not replicate %s: %s' % (package['URL'], err)) + except ReplicationError as err: + print( + 'Could not replicate %s: %s' % (package['URL'], err), + file=sys.stderr) exit(-1) if 'MetadataURL' in package: try: replicate_url(package['MetadataURL'], root_dir=workdir, ignore_cache=ignore_cache) - except ReplicationError, err: - print >> sys.stderr, ( + except ReplicationError as err: + print(( 'Could not replicate %s: %s' - % (package['MetadataURL'], err)) + % (package['MetadataURL'], err)), file=sys.stderr) exit(-1) @@ -437,18 +453,19 @@ def main(): elif args.seedprogram: su_catalog_url = get_seed_catalog(args.seedprogram) if not su_catalog_url: - print >> sys.stderr, ( + print(( 'Could not find a catalog url for seed program %s' - % args.seedprogram) - print >> sys.stderr, ( + % args.seedprogram), file=sys.stderr) + print(( 'Valid seeding programs are: %s' - % ', '.join(get_seeding_programs())) + % ', '.join(get_seeding_programs())), file=sys.stderr) exit(-1) else: su_catalog_url = get_default_catalog() if not su_catalog_url: - print >> sys.stderr, ( - 'Could not find a default catalog url for this OS version.') + print( + 'Could not find a default catalog url for this OS version.', + file=sys.stderr) exit(-1) # download sucatalog and look for products that are for macOS installers @@ -458,32 +475,33 @@ def main(): catalog, args.workdir, ignore_cache=args.ignore_cache) if not product_info: - print >> sys.stderr, ( - 'No macOS installer products found in the sucatalog.') + print( + 'No macOS installer products found in the sucatalog.', + file=sys.stderr) exit(-1) # display a menu of choices (some seed catalogs have multiple installers) - print '%2s %12s %10s %8s %11s %s' % ('#', 'ProductID', 'Version', - 'Build', 'Post Date', 'Title') + print('%2s %12s %10s %8s %11s %s' % ('#', 'ProductID', 'Version', + 'Build', 'Post Date', 'Title')) for index, product_id in enumerate(product_info): - print '%2s %12s %10s %8s %11s %s' % ( + print('%2s %12s %10s %8s %11s %s' % ( index + 1, product_id, product_info[product_id]['version'], product_info[product_id]['BUILD'], product_info[product_id]['PostDate'].strftime('%Y-%m-%d'), product_info[product_id]['title'] - ) + )) - answer = raw_input( + answer = input( '\nChoose a product to download (1-%s): ' % len(product_info)) try: index = int(answer) - 1 if index < 0: raise ValueError - product_id = product_info.keys()[index] + product_id = list(product_info.keys())[index] except (ValueError, IndexError): - print 'Exiting.' + print('Exiting.') exit(0) # download all the packages for the selected product @@ -499,7 +517,7 @@ def main(): os.unlink(sparse_diskimage_path) # make an empty sparseimage and mount it - print 'Making empty sparseimage...' + print('Making empty sparseimage...') sparse_diskimage_path = make_sparse_image(volname, sparse_diskimage_path) mountpoint = mountdmg(sparse_diskimage_path) if mountpoint: @@ -508,7 +526,7 @@ def main(): product_info[product_id]['DistributionPath'], mountpoint) if not success: - print >> sys.stderr, 'Product installation failed.' + print('Product installation failed.', file=sys.stderr) unmountdmg(mountpoint) exit(-1) # add the seeding program xattr to the app if applicable @@ -517,7 +535,7 @@ def main(): installer_app = find_installer_app(mountpoint) if installer_app: xattr.setxattr(installer_app, 'SeedProgram', seeding_program) - print 'Product downloaded and installed to %s' % sparse_diskimage_path + print('Product downloaded and installed to %s' % sparse_diskimage_path) if args.raw: unmountdmg(mountpoint) else: From adcff204f460bf5a977798661572947d29637a88 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Thu, 2 May 2019 09:30:05 -0700 Subject: [PATCH 2/9] A few post-2to3 optimizations --- installinstallmacos.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/installinstallmacos.py b/installinstallmacos.py index 4085dc8..345b726 100755 --- a/installinstallmacos.py +++ b/installinstallmacos.py @@ -67,7 +67,7 @@ def get_seeding_program(sucatalog_url): '''Returns a seeding program name based on the sucatalog_url''' try: seed_catalogs = plistlib.readPlist(SEED_CATALOGS_PLIST) - for key, value in list(seed_catalogs.items()): + for key, value in seed_catalogs.items(): if sucatalog_url == value: return key return '' @@ -341,8 +341,7 @@ def find_mac_os_installers(catalog): installers''' mac_os_installer_products = [] if 'Products' in catalog: - product_keys = list(catalog['Products'].keys()) - for product_key in product_keys: + for product_key in catalog['Products'].keys(): product = catalog['Products'][product_key] try: if product['ExtendedMetaInfo'][ From c7c3ab6c317e6b8b185a87592e53c41d453a9a32 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Thu, 2 May 2019 17:19:18 -0700 Subject: [PATCH 3/9] Revert Python 3 chnages since they relied on a non-standard module. This reverts commit adcff204f460bf5a977798661572947d29637a88. --- installinstallmacos.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/installinstallmacos.py b/installinstallmacos.py index 345b726..4085dc8 100755 --- a/installinstallmacos.py +++ b/installinstallmacos.py @@ -67,7 +67,7 @@ def get_seeding_program(sucatalog_url): '''Returns a seeding program name based on the sucatalog_url''' try: seed_catalogs = plistlib.readPlist(SEED_CATALOGS_PLIST) - for key, value in seed_catalogs.items(): + for key, value in list(seed_catalogs.items()): if sucatalog_url == value: return key return '' @@ -341,7 +341,8 @@ def find_mac_os_installers(catalog): installers''' mac_os_installer_products = [] if 'Products' in catalog: - for product_key in catalog['Products'].keys(): + product_keys = list(catalog['Products'].keys()) + for product_key in product_keys: product = catalog['Products'][product_key] try: if product['ExtendedMetaInfo'][ From 1f1615e5a2f877324a737525f8d18d41377912cb Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Thu, 2 May 2019 17:21:20 -0700 Subject: [PATCH 4/9] Revert Python 3 changes as they relied on a non-standard module. This reverts commit b3339ef2f8b1091d932e2936fe825ddde31e129d. --- installinstallmacos.py | 152 ++++++++++++++++++----------------------- 1 file changed, 67 insertions(+), 85 deletions(-) diff --git a/installinstallmacos.py b/installinstallmacos.py index 4085dc8..3d5793b 100755 --- a/installinstallmacos.py +++ b/installinstallmacos.py @@ -23,28 +23,17 @@ A tool to download the parts for an Install macOS app from Apple's softwareupdate servers and install a functioning Install macOS app onto an empty disk image''' -# Python 2/3 compatibility shims -from __future__ import (absolute_import, - division, - print_function, - unicode_literals) -from builtins import input -# pylint: disable=wrong-import-order,wrong-import-position import argparse import gzip import os import plistlib import subprocess import sys -try: - from urllib.parse import urlsplit -except ImportError: - from urlparse import urlsplit +import urlparse import xattr from xml.dom import minidom from xml.parsers.expat import ExpatError -# pylint: enable=wrong-import-order,wrong-import-position DEFAULT_SUCATALOGS = { @@ -67,7 +56,7 @@ def get_seeding_program(sucatalog_url): '''Returns a seeding program name based on the sucatalog_url''' try: seed_catalogs = plistlib.readPlist(SEED_CATALOGS_PLIST) - for key, value in list(seed_catalogs.items()): + for key, value in seed_catalogs.items(): if sucatalog_url == value: return key return '' @@ -88,7 +77,7 @@ def get_seeding_programs(): '''Returns the list of seeding program names''' try: seed_catalogs = plistlib.readPlist(SEED_CATALOGS_PLIST) - return list(seed_catalogs.keys()) + return seed_catalogs.keys() except (OSError, ExpatError, AttributeError, KeyError): return '' @@ -105,17 +94,17 @@ def make_sparse_image(volume_name, output_path): '-volname', volume_name, '-type', 'SPARSE', '-plist', output_path] try: output = subprocess.check_output(cmd) - except subprocess.CalledProcessError as err: - print(err, file=sys.stderr) + except subprocess.CalledProcessError, err: + print >> sys.stderr, err exit(-1) try: return plistlib.readPlistFromString(output)[0] - except IndexError as err: - print('Unexpected output from hdiutil: %s' % output, file=sys.stderr) + except IndexError, err: + print >> sys.stderr, 'Unexpected output from hdiutil: %s' % output exit(-1) - except ExpatError as err: - print('Malformed output from hdiutil: %s' % output, file=sys.stderr) - print(err, file=sys.stderr) + except ExpatError, err: + print >> sys.stderr, 'Malformed output from hdiutil: %s' % output + print >> sys.stderr, err exit(-1) @@ -123,16 +112,16 @@ def make_compressed_dmg(app_path, diskimagepath): """Returns path to newly-created compressed r/o disk image containing Install macOS.app""" - print(('Making read-only compressed disk image containing %s...' - % os.path.basename(app_path))) + print ('Making read-only compressed disk image containing %s...' + % os.path.basename(app_path)) cmd = ['/usr/bin/hdiutil', 'create', '-fs', 'HFS+', '-srcfolder', app_path, diskimagepath] try: subprocess.check_call(cmd) - except subprocess.CalledProcessError as err: - print(err, file=sys.stderr) + except subprocess.CalledProcessError, err: + print >> sys.stderr, err else: - print('Disk image created at: %s' % diskimagepath) + print 'Disk image created at: %s' % diskimagepath def mountdmg(dmgpath): @@ -148,8 +137,7 @@ def mountdmg(dmgpath): stdout=subprocess.PIPE, stderr=subprocess.PIPE) (pliststr, err) = proc.communicate() if proc.returncode: - print( - 'Error: "%s" while mounting %s.' % (err, dmgname), file=sys.stderr) + print >> sys.stderr, 'Error: "%s" while mounting %s.' % (err, dmgname) return None if pliststr: plist = plistlib.readPlistFromString(pliststr) @@ -169,13 +157,13 @@ def unmountdmg(mountpoint): stderr=subprocess.PIPE) (dummy_output, err) = proc.communicate() if proc.returncode: - print('Polite unmount failed: %s' % err, file=sys.stderr) - print('Attempting to force unmount %s' % mountpoint, file=sys.stderr) + print >> sys.stderr, 'Polite unmount failed: %s' % err + print >> sys.stderr, 'Attempting to force unmount %s' % mountpoint # try forcing the unmount retcode = subprocess.call(['/usr/bin/hdiutil', 'detach', mountpoint, '-force']) if retcode: - print('Failed to unmount %s' % mountpoint, file=sys.stderr) + print >> sys.stderr, 'Failed to unmount %s' % mountpoint def install_product(dist_path, target_vol): @@ -185,8 +173,8 @@ def install_product(dist_path, target_vol): try: subprocess.check_call(cmd) return True - except subprocess.CalledProcessError as err: - print(err, file=sys.stderr) + except subprocess.CalledProcessError, err: + print >> sys.stderr, err return False @@ -203,7 +191,7 @@ def replicate_url(full_url, '''Downloads a URL and stores it in the same relative path on our filesystem. Returns a path to the replicated file.''' - path = urlsplit(full_url)[2] + path = urlparse.urlsplit(full_url)[2] relative_url = path.lstrip('/') relative_url = os.path.normpath(relative_url) local_file_path = os.path.join(root_dir, relative_url) @@ -218,10 +206,10 @@ def replicate_url(full_url, if attempt_resume: curl_cmd.extend(['-C', '-']) curl_cmd.append(full_url) - print("Downloading %s..." % full_url) + print "Downloading %s..." % full_url try: subprocess.check_call(curl_cmd) - except subprocess.CalledProcessError as err: + except subprocess.CalledProcessError, err: raise ReplicationError(err) return local_file_path @@ -234,8 +222,8 @@ def parse_server_metadata(filename): vers = '' try: md_plist = plistlib.readPlist(filename) - except (OSError, IOError, ExpatError) as err: - print('Error reading %s: %s' % (filename, err), file=sys.stderr) + except (OSError, IOError, ExpatError), err: + print >> sys.stderr, 'Error reading %s: %s' % (filename, err) return {} vers = md_plist.get('CFBundleShortVersionString', '') localization = md_plist.get('localization', {}) @@ -258,12 +246,12 @@ def get_server_metadata(catalog, product_key, workdir, ignore_cache=False): smd_path = replicate_url( url, root_dir=workdir, ignore_cache=ignore_cache) return smd_path - except ReplicationError as err: - print(( - 'Could not replicate %s: %s' % (url, err)), file=sys.stderr) + except ReplicationError, err: + print >> sys.stderr, ( + 'Could not replicate %s: %s' % (url, err)) return None except KeyError: - print('Malformed catalog.', file=sys.stderr) + print >> sys.stderr, 'Malformed catalog.' return None @@ -274,10 +262,10 @@ def parse_dist(filename): try: dom = minidom.parse(filename) except ExpatError: - print('Invalid XML in %s' % filename, file=sys.stderr) + print >> sys.stderr, 'Invalid XML in %s' % filename return dist_info - except IOError as err: - print('Error reading %s: %s' % (filename, err), file=sys.stderr) + except IOError, err: + print >> sys.stderr, 'Error reading %s: %s' % (filename, err) return dist_info auxinfos = dom.getElementsByTagName('auxinfo') @@ -311,8 +299,8 @@ def download_and_parse_sucatalog(sucatalog, workdir, ignore_cache=False): try: localcatalogpath = replicate_url( sucatalog, root_dir=workdir, ignore_cache=ignore_cache) - except ReplicationError as err: - print('Could not replicate %s: %s' % (sucatalog, err), file=sys.stderr) + except ReplicationError, err: + print >> sys.stderr, 'Could not replicate %s: %s' % (sucatalog, err) exit(-1) if os.path.splitext(localcatalogpath)[1] == '.gz': with gzip.open(localcatalogpath) as the_file: @@ -320,19 +308,17 @@ def download_and_parse_sucatalog(sucatalog, workdir, ignore_cache=False): try: catalog = plistlib.readPlistFromString(content) return catalog - except ExpatError as err: - print( - 'Error reading %s: %s' % (localcatalogpath, err), - file=sys.stderr) + except ExpatError, err: + print >> sys.stderr, ( + 'Error reading %s: %s' % (localcatalogpath, err)) exit(-1) else: try: catalog = plistlib.readPlist(localcatalogpath) return catalog - except (OSError, IOError, ExpatError) as err: - print( - 'Error reading %s: %s' % (localcatalogpath, err), - file=sys.stderr) + except (OSError, IOError, ExpatError), err: + print >> sys.stderr, ( + 'Error reading %s: %s' % (localcatalogpath, err)) exit(-1) @@ -369,9 +355,8 @@ def os_installer_product_info(catalog, workdir, ignore_cache=False): try: dist_path = replicate_url( dist_url, root_dir=workdir, ignore_cache=ignore_cache) - except ReplicationError as err: - print( - 'Could not replicate %s: %s' % (dist_url, err), file=sys.stderr) + except ReplicationError, err: + print >> sys.stderr, 'Could not replicate %s: %s' % (dist_url, err) dist_info = parse_dist(dist_path) product_info[product_key]['DistributionPath'] = dist_path product_info[product_key].update(dist_info) @@ -392,19 +377,18 @@ def replicate_product(catalog, product_id, workdir, ignore_cache=False): package['URL'], root_dir=workdir, show_progress=True, ignore_cache=ignore_cache, attempt_resume=(not ignore_cache)) - except ReplicationError as err: - print( - 'Could not replicate %s: %s' % (package['URL'], err), - file=sys.stderr) + except ReplicationError, err: + print >> sys.stderr, ( + 'Could not replicate %s: %s' % (package['URL'], err)) exit(-1) if 'MetadataURL' in package: try: replicate_url(package['MetadataURL'], root_dir=workdir, ignore_cache=ignore_cache) - except ReplicationError as err: - print(( + except ReplicationError, err: + print >> sys.stderr, ( 'Could not replicate %s: %s' - % (package['MetadataURL'], err)), file=sys.stderr) + % (package['MetadataURL'], err)) exit(-1) @@ -453,19 +437,18 @@ def main(): elif args.seedprogram: su_catalog_url = get_seed_catalog(args.seedprogram) if not su_catalog_url: - print(( + print >> sys.stderr, ( 'Could not find a catalog url for seed program %s' - % args.seedprogram), file=sys.stderr) - print(( + % args.seedprogram) + print >> sys.stderr, ( 'Valid seeding programs are: %s' - % ', '.join(get_seeding_programs())), file=sys.stderr) + % ', '.join(get_seeding_programs())) exit(-1) else: su_catalog_url = get_default_catalog() if not su_catalog_url: - print( - 'Could not find a default catalog url for this OS version.', - file=sys.stderr) + print >> sys.stderr, ( + 'Could not find a default catalog url for this OS version.') exit(-1) # download sucatalog and look for products that are for macOS installers @@ -475,33 +458,32 @@ def main(): catalog, args.workdir, ignore_cache=args.ignore_cache) if not product_info: - print( - 'No macOS installer products found in the sucatalog.', - file=sys.stderr) + print >> sys.stderr, ( + 'No macOS installer products found in the sucatalog.') exit(-1) # display a menu of choices (some seed catalogs have multiple installers) - print('%2s %12s %10s %8s %11s %s' % ('#', 'ProductID', 'Version', - 'Build', 'Post Date', 'Title')) + print '%2s %12s %10s %8s %11s %s' % ('#', 'ProductID', 'Version', + 'Build', 'Post Date', 'Title') for index, product_id in enumerate(product_info): - print('%2s %12s %10s %8s %11s %s' % ( + print '%2s %12s %10s %8s %11s %s' % ( index + 1, product_id, product_info[product_id]['version'], product_info[product_id]['BUILD'], product_info[product_id]['PostDate'].strftime('%Y-%m-%d'), product_info[product_id]['title'] - )) + ) - answer = input( + answer = raw_input( '\nChoose a product to download (1-%s): ' % len(product_info)) try: index = int(answer) - 1 if index < 0: raise ValueError - product_id = list(product_info.keys())[index] + product_id = product_info.keys()[index] except (ValueError, IndexError): - print('Exiting.') + print 'Exiting.' exit(0) # download all the packages for the selected product @@ -517,7 +499,7 @@ def main(): os.unlink(sparse_diskimage_path) # make an empty sparseimage and mount it - print('Making empty sparseimage...') + print 'Making empty sparseimage...' sparse_diskimage_path = make_sparse_image(volname, sparse_diskimage_path) mountpoint = mountdmg(sparse_diskimage_path) if mountpoint: @@ -526,7 +508,7 @@ def main(): product_info[product_id]['DistributionPath'], mountpoint) if not success: - print('Product installation failed.', file=sys.stderr) + print >> sys.stderr, 'Product installation failed.' unmountdmg(mountpoint) exit(-1) # add the seeding program xattr to the app if applicable @@ -535,7 +517,7 @@ def main(): installer_app = find_installer_app(mountpoint) if installer_app: xattr.setxattr(installer_app, 'SeedProgram', seeding_program) - print('Product downloaded and installed to %s' % sparse_diskimage_path) + print 'Product downloaded and installed to %s' % sparse_diskimage_path if args.raw: unmountdmg(mountpoint) else: From c6c91dca06ac983c78c34851a5deef0b6593f15d Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Thu, 2 May 2019 20:26:00 -0700 Subject: [PATCH 5/9] Attempt #2 to add Python 3 compatibility --- installinstallmacos.py | 156 +++++++++++++++++++++++------------------ 1 file changed, 87 insertions(+), 69 deletions(-) diff --git a/installinstallmacos.py b/installinstallmacos.py index 3d5793b..e5e0c38 100755 --- a/installinstallmacos.py +++ b/installinstallmacos.py @@ -23,6 +23,9 @@ A tool to download the parts for an Install macOS app from Apple's softwareupdate servers and install a functioning Install macOS app onto an empty disk image''' +# Python 3 compatibility shims +from __future__ import ( + absolute_import, division, print_function, unicode_literals) import argparse import gzip @@ -30,10 +33,15 @@ import os import plistlib import subprocess import sys -import urlparse -import xattr +try: + # python 2 + from urllib.parse import urlsplit +except ImportError: + # python 3 + from urlparse import urlsplit from xml.dom import minidom from xml.parsers.expat import ExpatError +import xattr DEFAULT_SUCATALOGS = { @@ -52,6 +60,15 @@ SEED_CATALOGS_PLIST = ( ) +def get_input(prompt=None): + '''Python 2 and 3 wrapper for raw_input/input''' + try: + return raw_input(prompt) + except NameError: + # raw_input doesn't exist in Python 3 + return input(prompt) + + def get_seeding_program(sucatalog_url): '''Returns a seeding program name based on the sucatalog_url''' try: @@ -77,7 +94,7 @@ def get_seeding_programs(): '''Returns the list of seeding program names''' try: seed_catalogs = plistlib.readPlist(SEED_CATALOGS_PLIST) - return seed_catalogs.keys() + return list(seed_catalogs.keys()) except (OSError, ExpatError, AttributeError, KeyError): return '' @@ -94,17 +111,17 @@ def make_sparse_image(volume_name, output_path): '-volname', volume_name, '-type', 'SPARSE', '-plist', output_path] try: output = subprocess.check_output(cmd) - except subprocess.CalledProcessError, err: - print >> sys.stderr, err + except subprocess.CalledProcessError as err: + print(err, file=sys.stderr) exit(-1) try: return plistlib.readPlistFromString(output)[0] - except IndexError, err: - print >> sys.stderr, 'Unexpected output from hdiutil: %s' % output + except IndexError as err: + print('Unexpected output from hdiutil: %s' % output, file=sys.stderr) exit(-1) - except ExpatError, err: - print >> sys.stderr, 'Malformed output from hdiutil: %s' % output - print >> sys.stderr, err + except ExpatError as err: + print('Malformed output from hdiutil: %s' % output, file=sys.stderr) + print(err, file=sys.stderr) exit(-1) @@ -112,16 +129,16 @@ def make_compressed_dmg(app_path, diskimagepath): """Returns path to newly-created compressed r/o disk image containing Install macOS.app""" - print ('Making read-only compressed disk image containing %s...' - % os.path.basename(app_path)) + print(('Making read-only compressed disk image containing %s...' + % os.path.basename(app_path))) cmd = ['/usr/bin/hdiutil', 'create', '-fs', 'HFS+', '-srcfolder', app_path, diskimagepath] try: subprocess.check_call(cmd) - except subprocess.CalledProcessError, err: - print >> sys.stderr, err + except subprocess.CalledProcessError as err: + print(err, file=sys.stderr) else: - print 'Disk image created at: %s' % diskimagepath + print('Disk image created at: %s' % diskimagepath) def mountdmg(dmgpath): @@ -137,7 +154,8 @@ def mountdmg(dmgpath): stdout=subprocess.PIPE, stderr=subprocess.PIPE) (pliststr, err) = proc.communicate() if proc.returncode: - print >> sys.stderr, 'Error: "%s" while mounting %s.' % (err, dmgname) + print('Error: "%s" while mounting %s.' % (err, dmgname), + file=sys.stderr) return None if pliststr: plist = plistlib.readPlistFromString(pliststr) @@ -157,13 +175,13 @@ def unmountdmg(mountpoint): stderr=subprocess.PIPE) (dummy_output, err) = proc.communicate() if proc.returncode: - print >> sys.stderr, 'Polite unmount failed: %s' % err - print >> sys.stderr, 'Attempting to force unmount %s' % mountpoint + print('Polite unmount failed: %s' % err, file=sys.stderr) + print('Attempting to force unmount %s' % mountpoint, file=sys.stderr) # try forcing the unmount retcode = subprocess.call(['/usr/bin/hdiutil', 'detach', mountpoint, '-force']) if retcode: - print >> sys.stderr, 'Failed to unmount %s' % mountpoint + print('Failed to unmount %s' % mountpoint, file=sys.stderr) def install_product(dist_path, target_vol): @@ -173,8 +191,8 @@ def install_product(dist_path, target_vol): try: subprocess.check_call(cmd) return True - except subprocess.CalledProcessError, err: - print >> sys.stderr, err + except subprocess.CalledProcessError as err: + print(err, file=sys.stderr) return False @@ -191,7 +209,7 @@ def replicate_url(full_url, '''Downloads a URL and stores it in the same relative path on our filesystem. Returns a path to the replicated file.''' - path = urlparse.urlsplit(full_url)[2] + path = urlsplit(full_url)[2] relative_url = path.lstrip('/') relative_url = os.path.normpath(relative_url) local_file_path = os.path.join(root_dir, relative_url) @@ -206,10 +224,10 @@ def replicate_url(full_url, if attempt_resume: curl_cmd.extend(['-C', '-']) curl_cmd.append(full_url) - print "Downloading %s..." % full_url + print("Downloading %s..." % full_url) try: subprocess.check_call(curl_cmd) - except subprocess.CalledProcessError, err: + except subprocess.CalledProcessError as err: raise ReplicationError(err) return local_file_path @@ -222,8 +240,8 @@ def parse_server_metadata(filename): vers = '' try: md_plist = plistlib.readPlist(filename) - except (OSError, IOError, ExpatError), err: - print >> sys.stderr, 'Error reading %s: %s' % (filename, err) + except (OSError, IOError, ExpatError) as err: + print('Error reading %s: %s' % (filename, err), file=sys.stderr) return {} vers = md_plist.get('CFBundleShortVersionString', '') localization = md_plist.get('localization', {}) @@ -246,12 +264,12 @@ def get_server_metadata(catalog, product_key, workdir, ignore_cache=False): smd_path = replicate_url( url, root_dir=workdir, ignore_cache=ignore_cache) return smd_path - except ReplicationError, err: - print >> sys.stderr, ( - 'Could not replicate %s: %s' % (url, err)) + except ReplicationError as err: + print(( + 'Could not replicate %s: %s' % (url, err)), file=sys.stderr) return None except KeyError: - print >> sys.stderr, 'Malformed catalog.' + print('Malformed catalog.', file=sys.stderr) return None @@ -262,10 +280,10 @@ def parse_dist(filename): try: dom = minidom.parse(filename) except ExpatError: - print >> sys.stderr, 'Invalid XML in %s' % filename + print('Invalid XML in %s' % filename, file=sys.stderr) return dist_info - except IOError, err: - print >> sys.stderr, 'Error reading %s: %s' % (filename, err) + except IOError as err: + print('Error reading %s: %s' % (filename, err), file=sys.stderr) return dist_info auxinfos = dom.getElementsByTagName('auxinfo') @@ -299,8 +317,8 @@ def download_and_parse_sucatalog(sucatalog, workdir, ignore_cache=False): try: localcatalogpath = replicate_url( sucatalog, root_dir=workdir, ignore_cache=ignore_cache) - except ReplicationError, err: - print >> sys.stderr, 'Could not replicate %s: %s' % (sucatalog, err) + except ReplicationError as err: + print('Could not replicate %s: %s' % (sucatalog, err), file=sys.stderr) exit(-1) if os.path.splitext(localcatalogpath)[1] == '.gz': with gzip.open(localcatalogpath) as the_file: @@ -308,17 +326,17 @@ def download_and_parse_sucatalog(sucatalog, workdir, ignore_cache=False): try: catalog = plistlib.readPlistFromString(content) return catalog - except ExpatError, err: - print >> sys.stderr, ( - 'Error reading %s: %s' % (localcatalogpath, err)) + except ExpatError as err: + print('Error reading %s: %s' % (localcatalogpath, err), + file=sys.stderr) exit(-1) else: try: catalog = plistlib.readPlist(localcatalogpath) return catalog - except (OSError, IOError, ExpatError), err: - print >> sys.stderr, ( - 'Error reading %s: %s' % (localcatalogpath, err)) + except (OSError, IOError, ExpatError) as err: + print('Error reading %s: %s' % (localcatalogpath, err), + file=sys.stderr) exit(-1) @@ -327,8 +345,7 @@ def find_mac_os_installers(catalog): installers''' mac_os_installer_products = [] if 'Products' in catalog: - product_keys = list(catalog['Products'].keys()) - for product_key in product_keys: + for product_key in catalog['Products'].keys(): product = catalog['Products'][product_key] try: if product['ExtendedMetaInfo'][ @@ -355,8 +372,9 @@ def os_installer_product_info(catalog, workdir, ignore_cache=False): try: dist_path = replicate_url( dist_url, root_dir=workdir, ignore_cache=ignore_cache) - except ReplicationError, err: - print >> sys.stderr, 'Could not replicate %s: %s' % (dist_url, err) + except ReplicationError as err: + print('Could not replicate %s: %s' % (dist_url, err), + file=sys.stderr) dist_info = parse_dist(dist_path) product_info[product_key]['DistributionPath'] = dist_path product_info[product_key].update(dist_info) @@ -377,18 +395,18 @@ def replicate_product(catalog, product_id, workdir, ignore_cache=False): package['URL'], root_dir=workdir, show_progress=True, ignore_cache=ignore_cache, attempt_resume=(not ignore_cache)) - except ReplicationError, err: - print >> sys.stderr, ( - 'Could not replicate %s: %s' % (package['URL'], err)) + except ReplicationError as err: + print('Could not replicate %s: %s' % (package['URL'], err), + file=sys.stderr) exit(-1) if 'MetadataURL' in package: try: replicate_url(package['MetadataURL'], root_dir=workdir, ignore_cache=ignore_cache) - except ReplicationError, err: - print >> sys.stderr, ( + except ReplicationError as err: + print(( 'Could not replicate %s: %s' - % (package['MetadataURL'], err)) + % (package['MetadataURL'], err)), file=sys.stderr) exit(-1) @@ -437,18 +455,18 @@ def main(): elif args.seedprogram: su_catalog_url = get_seed_catalog(args.seedprogram) if not su_catalog_url: - print >> sys.stderr, ( + print(( 'Could not find a catalog url for seed program %s' - % args.seedprogram) - print >> sys.stderr, ( + % args.seedprogram), file=sys.stderr) + print(( 'Valid seeding programs are: %s' - % ', '.join(get_seeding_programs())) + % ', '.join(get_seeding_programs())), file=sys.stderr) exit(-1) else: su_catalog_url = get_default_catalog() if not su_catalog_url: - print >> sys.stderr, ( - 'Could not find a default catalog url for this OS version.') + print('Could not find a default catalog url for this OS version.', + file=sys.stderr) exit(-1) # download sucatalog and look for products that are for macOS installers @@ -458,32 +476,32 @@ def main(): catalog, args.workdir, ignore_cache=args.ignore_cache) if not product_info: - print >> sys.stderr, ( - 'No macOS installer products found in the sucatalog.') + print('No macOS installer products found in the sucatalog.', + file=sys.stderr) exit(-1) # display a menu of choices (some seed catalogs have multiple installers) - print '%2s %12s %10s %8s %11s %s' % ('#', 'ProductID', 'Version', - 'Build', 'Post Date', 'Title') + print('%2s %12s %10s %8s %11s %s' + % ('#', 'ProductID', 'Version', 'Build', 'Post Date', 'Title')) for index, product_id in enumerate(product_info): - print '%2s %12s %10s %8s %11s %s' % ( + print('%2s %12s %10s %8s %11s %s' % ( index + 1, product_id, product_info[product_id]['version'], product_info[product_id]['BUILD'], product_info[product_id]['PostDate'].strftime('%Y-%m-%d'), product_info[product_id]['title'] - ) + )) - answer = raw_input( + answer = get_input( '\nChoose a product to download (1-%s): ' % len(product_info)) try: index = int(answer) - 1 if index < 0: raise ValueError - product_id = product_info.keys()[index] + product_id = list(product_info.keys())[index] except (ValueError, IndexError): - print 'Exiting.' + print('Exiting.') exit(0) # download all the packages for the selected product @@ -499,7 +517,7 @@ def main(): os.unlink(sparse_diskimage_path) # make an empty sparseimage and mount it - print 'Making empty sparseimage...' + print('Making empty sparseimage...') sparse_diskimage_path = make_sparse_image(volname, sparse_diskimage_path) mountpoint = mountdmg(sparse_diskimage_path) if mountpoint: @@ -508,7 +526,7 @@ def main(): product_info[product_id]['DistributionPath'], mountpoint) if not success: - print >> sys.stderr, 'Product installation failed.' + print('Product installation failed.', file=sys.stderr) unmountdmg(mountpoint) exit(-1) # add the seeding program xattr to the app if applicable @@ -517,7 +535,7 @@ def main(): installer_app = find_installer_app(mountpoint) if installer_app: xattr.setxattr(installer_app, 'SeedProgram', seeding_program) - print 'Product downloaded and installed to %s' % sparse_diskimage_path + print('Product downloaded and installed to %s' % sparse_diskimage_path) if args.raw: unmountdmg(mountpoint) else: From 22c91ee3c84e9dc1f5f7354fadd1a094bdc4a149 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Thu, 2 May 2019 20:34:37 -0700 Subject: [PATCH 6/9] Some post-2to3-conversion cleanup --- installinstallmacos.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/installinstallmacos.py b/installinstallmacos.py index e5e0c38..9357792 100755 --- a/installinstallmacos.py +++ b/installinstallmacos.py @@ -129,8 +129,8 @@ def make_compressed_dmg(app_path, diskimagepath): """Returns path to newly-created compressed r/o disk image containing Install macOS.app""" - print(('Making read-only compressed disk image containing %s...' - % os.path.basename(app_path))) + print('Making read-only compressed disk image containing %s...' + % os.path.basename(app_path)) cmd = ['/usr/bin/hdiutil', 'create', '-fs', 'HFS+', '-srcfolder', app_path, diskimagepath] try: @@ -265,8 +265,7 @@ def get_server_metadata(catalog, product_key, workdir, ignore_cache=False): url, root_dir=workdir, ignore_cache=ignore_cache) return smd_path except ReplicationError as err: - print(( - 'Could not replicate %s: %s' % (url, err)), file=sys.stderr) + print('Could not replicate %s: %s' % (url, err), file=sys.stderr) return None except KeyError: print('Malformed catalog.', file=sys.stderr) @@ -404,9 +403,8 @@ def replicate_product(catalog, product_id, workdir, ignore_cache=False): replicate_url(package['MetadataURL'], root_dir=workdir, ignore_cache=ignore_cache) except ReplicationError as err: - print(( - 'Could not replicate %s: %s' - % (package['MetadataURL'], err)), file=sys.stderr) + print('Could not replicate %s: %s' + % (package['MetadataURL'], err), file=sys.stderr) exit(-1) @@ -455,12 +453,10 @@ def main(): elif args.seedprogram: su_catalog_url = get_seed_catalog(args.seedprogram) if not su_catalog_url: - print(( - 'Could not find a catalog url for seed program %s' - % args.seedprogram), file=sys.stderr) - print(( - 'Valid seeding programs are: %s' - % ', '.join(get_seeding_programs())), file=sys.stderr) + print('Could not find a catalog url for seed program %s' + % args.seedprogram, file=sys.stderr) + print('Valid seeding programs are: %s' + % ', '.join(get_seeding_programs()), file=sys.stderr) exit(-1) else: su_catalog_url = get_default_catalog() From 7f27257d961aa13544d2a1da514e35da3280e57b Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Sun, 12 May 2019 12:11:20 -0700 Subject: [PATCH 7/9] Add plistlib wrapper functions to avoid DeprecationWarnings under Python 3 --- installinstallmacos.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/installinstallmacos.py b/installinstallmacos.py index 9357792..07f42d2 100755 --- a/installinstallmacos.py +++ b/installinstallmacos.py @@ -69,10 +69,29 @@ def get_input(prompt=None): return input(prompt) +def readPlist(filepath): + '''Wrapper for the differences between Python 2 and Python 3's plistlib''' + try: + with open(filepath, "rb") as fileobj: + return plistlib.load(fileobj) + except AttributeError: + # plistlib module doesn't have a load function (as in Python 2) + return plistlib.readPlist(filepath) + + +def readPlistFromString(bytestring): + '''Wrapper for the differences between Python 2 and Python 3's plistlib''' + try: + return plistlib.loads(bytestring) + except AttributeError: + # plistlib module doesn't have a load function (as in Python 2) + return plistlib.readPlistFromString(bytestring) + + def get_seeding_program(sucatalog_url): '''Returns a seeding program name based on the sucatalog_url''' try: - seed_catalogs = plistlib.readPlist(SEED_CATALOGS_PLIST) + seed_catalogs = readPlist(SEED_CATALOGS_PLIST) for key, value in seed_catalogs.items(): if sucatalog_url == value: return key @@ -84,7 +103,7 @@ def get_seeding_program(sucatalog_url): def get_seed_catalog(seedname='DeveloperSeed'): '''Returns the developer seed sucatalog''' try: - seed_catalogs = plistlib.readPlist(SEED_CATALOGS_PLIST) + seed_catalogs = readPlist(SEED_CATALOGS_PLIST) return seed_catalogs.get(seedname) except (OSError, ExpatError, AttributeError, KeyError): return '' @@ -93,7 +112,7 @@ def get_seed_catalog(seedname='DeveloperSeed'): def get_seeding_programs(): '''Returns the list of seeding program names''' try: - seed_catalogs = plistlib.readPlist(SEED_CATALOGS_PLIST) + seed_catalogs = readPlist(SEED_CATALOGS_PLIST) return list(seed_catalogs.keys()) except (OSError, ExpatError, AttributeError, KeyError): return '' @@ -115,7 +134,7 @@ def make_sparse_image(volume_name, output_path): print(err, file=sys.stderr) exit(-1) try: - return plistlib.readPlistFromString(output)[0] + return readPlistFromString(output)[0] except IndexError as err: print('Unexpected output from hdiutil: %s' % output, file=sys.stderr) exit(-1) @@ -158,7 +177,7 @@ def mountdmg(dmgpath): file=sys.stderr) return None if pliststr: - plist = plistlib.readPlistFromString(pliststr) + plist = readPlistFromString(pliststr) for entity in plist['system-entities']: if 'mount-point' in entity: mountpoints.append(entity['mount-point']) @@ -239,7 +258,7 @@ def parse_server_metadata(filename): title = '' vers = '' try: - md_plist = plistlib.readPlist(filename) + md_plist = readPlist(filename) except (OSError, IOError, ExpatError) as err: print('Error reading %s: %s' % (filename, err), file=sys.stderr) return {} @@ -323,7 +342,7 @@ def download_and_parse_sucatalog(sucatalog, workdir, ignore_cache=False): with gzip.open(localcatalogpath) as the_file: content = the_file.read() try: - catalog = plistlib.readPlistFromString(content) + catalog = readPlistFromString(content) return catalog except ExpatError as err: print('Error reading %s: %s' % (localcatalogpath, err), @@ -331,7 +350,7 @@ def download_and_parse_sucatalog(sucatalog, workdir, ignore_cache=False): exit(-1) else: try: - catalog = plistlib.readPlist(localcatalogpath) + catalog = readPlist(localcatalogpath) return catalog except (OSError, IOError, ExpatError) as err: print('Error reading %s: %s' % (localcatalogpath, err), From 9d9bb51ab11ca01fd1ac9f51b129dd8b1b32591a Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Tue, 28 May 2019 14:53:27 -0700 Subject: [PATCH 8/9] Don't attempt to parse a dist we could not replicate! Addresses #19. --- installinstallmacos.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/installinstallmacos.py b/installinstallmacos.py index 07f42d2..05bad4e 100755 --- a/installinstallmacos.py +++ b/installinstallmacos.py @@ -393,9 +393,10 @@ def os_installer_product_info(catalog, workdir, ignore_cache=False): except ReplicationError as err: print('Could not replicate %s: %s' % (dist_url, err), file=sys.stderr) - dist_info = parse_dist(dist_path) - product_info[product_key]['DistributionPath'] = dist_path - product_info[product_key].update(dist_info) + else: + dist_info = parse_dist(dist_path) + product_info[product_key]['DistributionPath'] = dist_path + product_info[product_key].update(dist_info) return product_info From ebd27a06c2789d2d4e7b687cd19f561ff06a33e8 Mon Sep 17 00:00:00 2001 From: Greg Neagle Date: Wed, 3 Jul 2019 10:02:31 -0700 Subject: [PATCH 9/9] Fix to add SeedProgram extended attribute to the install application if --seedprogram option is used --- installinstallmacos.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/installinstallmacos.py b/installinstallmacos.py index 05bad4e..9d72b41 100755 --- a/installinstallmacos.py +++ b/installinstallmacos.py @@ -69,7 +69,7 @@ def get_input(prompt=None): return input(prompt) -def readPlist(filepath): +def read_plist(filepath): '''Wrapper for the differences between Python 2 and Python 3's plistlib''' try: with open(filepath, "rb") as fileobj: @@ -79,10 +79,10 @@ def readPlist(filepath): return plistlib.readPlist(filepath) -def readPlistFromString(bytestring): +def read_plist_from_string(bytestring): '''Wrapper for the differences between Python 2 and Python 3's plistlib''' try: - return plistlib.loads(bytestring) + return plistlib.loads(bytestring) except AttributeError: # plistlib module doesn't have a load function (as in Python 2) return plistlib.readPlistFromString(bytestring) @@ -91,7 +91,7 @@ def readPlistFromString(bytestring): def get_seeding_program(sucatalog_url): '''Returns a seeding program name based on the sucatalog_url''' try: - seed_catalogs = readPlist(SEED_CATALOGS_PLIST) + seed_catalogs = read_plist(SEED_CATALOGS_PLIST) for key, value in seed_catalogs.items(): if sucatalog_url == value: return key @@ -103,7 +103,7 @@ def get_seeding_program(sucatalog_url): def get_seed_catalog(seedname='DeveloperSeed'): '''Returns the developer seed sucatalog''' try: - seed_catalogs = readPlist(SEED_CATALOGS_PLIST) + seed_catalogs = read_plist(SEED_CATALOGS_PLIST) return seed_catalogs.get(seedname) except (OSError, ExpatError, AttributeError, KeyError): return '' @@ -112,7 +112,7 @@ def get_seed_catalog(seedname='DeveloperSeed'): def get_seeding_programs(): '''Returns the list of seeding program names''' try: - seed_catalogs = readPlist(SEED_CATALOGS_PLIST) + seed_catalogs = read_plist(SEED_CATALOGS_PLIST) return list(seed_catalogs.keys()) except (OSError, ExpatError, AttributeError, KeyError): return '' @@ -134,7 +134,7 @@ def make_sparse_image(volume_name, output_path): print(err, file=sys.stderr) exit(-1) try: - return readPlistFromString(output)[0] + return read_plist_from_string(output)[0] except IndexError as err: print('Unexpected output from hdiutil: %s' % output, file=sys.stderr) exit(-1) @@ -177,7 +177,7 @@ def mountdmg(dmgpath): file=sys.stderr) return None if pliststr: - plist = readPlistFromString(pliststr) + plist = read_plist_from_string(pliststr) for entity in plist['system-entities']: if 'mount-point' in entity: mountpoints.append(entity['mount-point']) @@ -258,7 +258,7 @@ def parse_server_metadata(filename): title = '' vers = '' try: - md_plist = readPlist(filename) + md_plist = read_plist(filename) except (OSError, IOError, ExpatError) as err: print('Error reading %s: %s' % (filename, err), file=sys.stderr) return {} @@ -342,7 +342,7 @@ def download_and_parse_sucatalog(sucatalog, workdir, ignore_cache=False): with gzip.open(localcatalogpath) as the_file: content = the_file.read() try: - catalog = readPlistFromString(content) + catalog = read_plist_from_string(content) return catalog except ExpatError as err: print('Error reading %s: %s' % (localcatalogpath, err), @@ -350,7 +350,7 @@ def download_and_parse_sucatalog(sucatalog, workdir, ignore_cache=False): exit(-1) else: try: - catalog = readPlist(localcatalogpath) + catalog = read_plist(localcatalogpath) return catalog except (OSError, IOError, ExpatError) as err: print('Error reading %s: %s' % (localcatalogpath, err), @@ -546,10 +546,12 @@ def main(): unmountdmg(mountpoint) exit(-1) # add the seeding program xattr to the app if applicable - seeding_program = get_seeding_program(args.catalogurl) + seeding_program = get_seeding_program(su_catalog_url) if seeding_program: installer_app = find_installer_app(mountpoint) if installer_app: + print("Adding seeding program %s extended attribute to app" + % seeding_program) xattr.setxattr(installer_app, 'SeedProgram', seeding_program) print('Product downloaded and installed to %s' % sparse_diskimage_path) if args.raw: