def getMCXData(ds_object): '''Returns a dictionary representation of dsAttrTypeStandard:MCXSettings from the given DirectoryServices object''' ds_object_parts = ds_object.split('/') ds_node = '/'.join(ds_object_parts[0:3]) ds_object_path = '/' + '/'.join(ds_object_parts[3:]) cmd = ['/usr/bin/dscl', '-plist', ds_node, 'read', ds_object_path, 'dsAttrTypeStandard:MCXSettings'] proc = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (pliststr, err) = proc.communicate() if proc.returncode: errorAndExit("dscl error: %s" % err) # decode plist string returned by dscl try: mcx_dict = FoundationPlist.readPlistFromString(pliststr) except FoundationPlist.FoundationPlistException: errorAndExit( "Could not decode plist data from dscl:\n" % pliststr) # mcx_settings is a plist encoded inside the plist! try: mcx_data_plist = mcx_dict['dsAttrTypeStandard:MCXSettings'][0] except KeyError: errorAndExit("No mcx_settings in %s:\n%s" % (ds_object, pliststr)) except IndexError: errorAndExit( "Unexpected mcx_settings format in %s:\n%s" % (ds_object, pliststr)) # decode the embedded plist mcx_data = FoundationPlist.readPlistFromString(str(mcx_data_plist)) return mcx_data['mcx_application_data']
def createInstallsItems(self): """Calls makepkginfo to create an installs array.""" faux_root = "" if self.env.get("faux_root"): faux_root = self.env["faux_root"].rstrip("/") args = ["/usr/local/munki/makepkginfo"] for item in self.env["installs_item_paths"]: args.extend(["-f", faux_root + item]) # Call makepkginfo. try: proc = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err) = proc.communicate() except OSError as err: raise ProcessorError( "makepkginfo execution failed with error code %d: %s" % (err.errno, err.strerror)) if proc.returncode != 0: raise ProcessorError( "creating pkginfo failed: %s" % err) # Get pkginfo from output plist. pkginfo = FoundationPlist.readPlistFromString(out) installs_array = pkginfo.get("installs", []) if faux_root: for item in installs_array: if item["path"].startswith(faux_root): item["path"] = item["path"][len(faux_root):] self.output("Created installs item for %s" % item["path"]) if not "additional_pkginfo" in self.env: self.env["additional_pkginfo"] = {} self.env["additional_pkginfo"]["installs"] = installs_array
def loadData(self): pool = NSAutoreleasePool.alloc().init() self.volumes = macdisk.MountedVolumes() theURL = Utils.getServerURL() if theURL: plistData = Utils.downloadFile(theURL) if plistData: try: converted_plist = FoundationPlist.readPlistFromString(plistData) except: self.errorMessage = "Configuration plist couldn't be read." try: self.passwordHash = converted_plist['password'] except: self.errorMessage = "Password wasn't set." try: self.workflows = converted_plist['workflows'] except: self.errorMessage = "No workflows found in the configuration plist." else: self.errorMessage = "Couldn't get configuration plist from server." else: self.errorMessage = "Configuration URL wasn't set." self.performSelectorOnMainThread_withObject_waitUntilDone_( self.loadDataComplete, None, YES) del pool
def read_signed_profile(profile_path): '''Attempts to read a (presumably) signed profile.''' # filed for future reference: # openssl smime -inform DER -verify -in Signed.mobileconfig # -noverify -out Unsigned.mobileconfig # will strip the signing from a signed profile # this might be a better approach # from: https://apple.stackexchange.com/questions/105981/ # how-do-i-view-or-verify-signed-mobileconfig-files-using-terminal # but... we're going to use an Apple-provided tool instead. cmd = ['/usr/bin/security', 'cms', '-D', '-i', profile_path] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = proc.communicate() if proc.returncode: # security cms -D couldn't decode the file munkicommon.display_error('Error reading profile %s: %s' % (profile_path, stderr)) return {} try: return FoundationPlist.readPlistFromString(stdout) except FoundationPlist.NSPropertyListSerializationException, err: # not a valid plist munkicommon.display_error('Error reading profile %s: %s' % (profile_path, err)) return {}
def dmg_has_sla(self, dmgpath): '''Returns true if dmg has a Software License Agreement. These dmgs normally cannot be attached without user intervention''' has_sla = False proc = subprocess.Popen( ['/usr/bin/hdiutil', 'imageinfo', dmgpath, '-plist'], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = proc.communicate() if stderr: # some error with hdiutil. # Output but return False so we can attempt to continue self.output('hdiutil imageinfo error %s with image %s.' % (stderr, dmgpath)) return False (pliststr, stdout) = self.get_first_plist(stdout) if pliststr: try: plist = FoundationPlist.readPlistFromString(pliststr) properties = plist.get('Properties') if properties: has_sla = properties.get( 'Software License Agreement', False) except FoundationPlist.NSPropertyListSerializationException: pass return has_sla
def dmg_has_sla(self, dmgpath): '''Returns true if dmg has a Software License Agreement. These dmgs normally cannot be attached without user intervention''' has_sla = False proc = subprocess.Popen( ['/usr/bin/hdiutil', 'imageinfo', dmgpath, '-plist'], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdout, stderr) = proc.communicate() if stderr: # some error with hdiutil. # Output but return False so we can attempt to continue self.output('hdiutil imageinfo error %s with image %s.' % (stderr, dmgpath)) return False (pliststr, stdout) = self.get_first_plist(stdout) if pliststr: try: plist = FoundationPlist.readPlistFromString(pliststr) properties = plist.get('Properties') if properties: has_sla = properties.get('Software License Agreement', False) except FoundationPlist.NSPropertyListSerializationException: pass return has_sla
def DMGhasSLA(self, dmgpath): """Returns true if dmg has a Software License Agreement. These dmgs normally cannot be attached without user intervention""" hasSLA = False proc = subprocess.Popen( ["/usr/bin/hdiutil", "imageinfo", dmgpath, "-plist"], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) (out, err) = proc.communicate() if err: # some error with hdiutil. # Output but return False so we can attempt to continue self.output("hdiutil imageinfo error %s with image %s." % (err, dmgpath)) return False (pliststr, out) = self.getFirstPlist(out) if pliststr: try: plist = FoundationPlist.readPlistFromString(pliststr) properties = plist.get("Properties") if properties: hasSLA = properties.get("Software License Agreement", False) except FoundationPlist.NSPropertyListSerializationException: pass return hasSLA
def create_installs_items(self): """Calls makepkginfo to create an installs array.""" faux_root = "" if self.env.get("faux_root"): faux_root = self.env["faux_root"].rstrip("/") args = ["/usr/local/munki/makepkginfo"] for item in self.env["installs_item_paths"]: args.extend(["-f", faux_root + item]) # Call makepkginfo. try: proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err) = proc.communicate() except OSError as err: raise ProcessorError( "makepkginfo execution failed with error code %d: %s" % (err.errno, err.strerror)) if proc.returncode != 0: raise ProcessorError("creating pkginfo failed: %s" % err) # Get pkginfo from output plist. pkginfo = FoundationPlist.readPlistFromString(out) installs_array = pkginfo.get("installs", []) if faux_root: for item in installs_array: if item["path"].startswith(faux_root): item["path"] = item["path"][len(faux_root):] self.output("Created installs item for %s" % item["path"]) if "version_comparison_key" in self.env: for item in installs_array: cmp_key = None # If it's a string, set it for all installs items if isinstance(self.env["version_comparison_key"], basestring): cmp_key = self.env["version_comparison_key"] # It it's a dict, find if there's a key that matches a path elif isinstance(self.env["version_comparison_key"], NSDictionary): for path, key in self.env["version_comparison_key"].items( ): if path == item["path"]: cmp_key = key if cmp_key: # Check that we really have this key available to compare if cmp_key in item: item["version_comparison_key"] = cmp_key else: raise ProcessorError( "version_comparison_key '%s' could not be found in " "the installs item for path '%s'" % (cmp_key, item["path"])) if not "additional_pkginfo" in self.env: self.env["additional_pkginfo"] = {} self.env["additional_pkginfo"]["installs"] = installs_array
def read_signed_profile(profile_path): '''Attempts to read a (presumably) signed profile.''' # filed for future reference: # openssl smime -inform DER -verify -in Signed.mobileconfig # -noverify -out Unsigned.mobileconfig # will strip the signing from a signed profile # this might be a better approach # from: http://apple.stackexchange.com/questions/105981/ # how-do-i-view-or-verify-signed-mobileconfig-files-using-terminal # but... we're going to use an Apple-provided tool instead. cmd = ['/usr/bin/security', 'cms', '-D', '-i', profile_path] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = proc.communicate() if proc.returncode: # security cms -D couldn't decode the file munkicommon.display_error( 'Error reading profile %s: %s' % (profile_path, stderr)) return {} try: return FoundationPlist.readPlistFromString(stdout) except FoundationPlist.NSPropertyListSerializationException, err: # not a valid plist munkicommon.display_error( 'Error reading profile %s: %s' % (profile_path, err)) return {}
def main(self): # Wrap in a try/finally so the temp_path is always removed. temp_path = None try: # Check munki version. if os.path.exists("/usr/local/munki/munkilib/version.plist"): # Assume 0.7.0 or higher. munkiopts = ("displayname", "description", "catalog") else: # Assume 0.6.0 munkiopts = ("catalog",) # Copy pkg to a temporary local directory, as installer -query # (which is called by makepkginfo) doesn't work on network drives. if self.env["pkg_path"].endswith("pkg"): # Create temporary directory. temp_path = tempfile.mkdtemp(prefix="autopkg", dir="/private/tmp") # Copy the pkg there pkg_for_makepkginfo = os.path.join(temp_path, os.path.basename(self.env["pkg_path"])) shutil.copyfile(self.env["pkg_path"], pkg_for_makepkginfo) else: pkg_for_makepkginfo = self.env["pkg_path"] # Generate arguments for makepkginfo. args = ["/usr/local/munki/makepkginfo"] for option in munkiopts: if option in self.env: args.append("--%s=%s" % (option, self.env[option])) args.append(pkg_for_makepkginfo) # Call makepkginfo. try: p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err) = p.communicate() except OSError as e: raise ProcessorError("makepkginfo execution failed with error code %d: %s" % ( e.errno, e.strerror)) if p.returncode != 0: raise ProcessorError("creating pkginfo for %s failed: %s" % (self.env['pkg_path'], err)) # makepkginfo cleanup. finally: if temp_path is not None: shutil.rmtree(temp_path) # Read output plist. output = FoundationPlist.readPlistFromString(out) # Set version and name. if "version" in self.env: output["version"] = self.env["version"] if "name" in self.env: output["name"] = self.env["name"] # Save info. self.env["munki_info"] = output if "info_path" in self.env: FoundationPlist.writePlist(output, self.env["info_path"])
def create_installs_items(self): """Calls makepkginfo to create an installs array.""" faux_root = "" if self.env.get("faux_root"): faux_root = self.env["faux_root"].rstrip("/") args = ["/usr/local/munki/makepkginfo"] for item in self.env["installs_item_paths"]: args.extend(["-f", faux_root + item]) # Call makepkginfo. try: proc = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) (out, err) = proc.communicate() except OSError as err: raise ProcessorError( "makepkginfo execution failed with error code %d: %s" % (err.errno, err.strerror) ) if proc.returncode != 0: raise ProcessorError("creating pkginfo failed: %s" % err) # Get pkginfo from output plist. pkginfo = FoundationPlist.readPlistFromString(out) installs_array = pkginfo.get("installs", []) if faux_root: for item in installs_array: if item["path"].startswith(faux_root): item["path"] = item["path"][len(faux_root) :] self.output("Created installs item for %s" % item["path"]) if "version_comparison_key" in self.env: for item in installs_array: cmp_key = None # If it's a string, set it for all installs items if isinstance(self.env["version_comparison_key"], basestring): cmp_key = self.env["version_comparison_key"] # It it's a dict, find if there's a key that matches a path elif isinstance(self.env["version_comparison_key"], NSDictionary): for path, key in self.env["version_comparison_key"].items(): if path == item["path"]: cmp_key = key if cmp_key: # Check that we really have this key available to compare if cmp_key in item: item["version_comparison_key"] = cmp_key else: raise ProcessorError( "version_comparison_key '%s' could not be found in " "the installs item for path '%s'" % (cmp_key, item["path"]) ) if "additional_pkginfo" not in self.env: self.env["additional_pkginfo"] = {} self.env["additional_pkginfo"]["installs"] = installs_array
def create_munkipkginfo(self): # Set pkginfo plist path self.env["pkginfo_path"] = ("%s/%s.plist") % (self.env.get("RECIPE_CACHE_DIR"), self.env.get("NAME")) # Generate arguments for makepkginfo. args = ["/usr/local/munki/makepkginfo", self.env["pkg_path"]] if self.env.get("munkiimport_pkgname"): args.extend(["--pkgname", self.env["munkiimport_pkgname"]]) if self.env.get("munkiimport_appname"): args.extend(["--appname", self.env["munkiimport_appname"]]) if self.env.get("additional_makepkginfo_options"): args.extend(self.env["additional_makepkginfo_options"]) if self.env.get("munkiimport_name"): args.extend(["--displayname", self.env["munkiimport_name"]]) # Call makepkginfo. try: proc = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err_out) = proc.communicate() except OSError as err: raise ProcessorError( "makepkginfo execution failed with error code %d: %s" % (err.errno, err.strerror)) if proc.returncode != 0: raise ProcessorError( "creating pkginfo for %s failed: %s" % (self.env["pkg_path"], err_out)) # Get pkginfo from output plist. pkginfo = FoundationPlist.readPlistFromString(out) # copy any keys from pkginfo in self.env if "pkginfo" in self.env: for key in self.env["pkginfo"]: pkginfo[key] = self.env["pkginfo"][key] # set an alternate version_comparison_key # if pkginfo has an installs item if "installs" in pkginfo and self.env.get("version_comparison_key"): for item in pkginfo["installs"]: if not self.env["version_comparison_key"] in item: raise ProcessorError( ("version_comparison_key '%s' could not be found in " "the installs item for path '%s'") % (self.env["version_comparison_key"], item["path"])) item["version_comparison_key"] = ( self.env["version_comparison_key"]) try: pkginfo_path = self.env["pkginfo_path"] FoundationPlist.writePlist(pkginfo, pkginfo_path) except OSError, err: raise ProcessorError("Could not write pkginfo %s: %s" % (pkginfo_path, err.strerror))
def mount(self, pathname): """Mount image with hdiutil.""" # Make sure we don't try to mount something twice. if pathname in self.mounts: raise ProcessorError("%s is already mounted" % pathname) stdin = "" if self.dmg_has_sla(pathname): stdin = "Y\n" # Call hdiutil. try: proc = subprocess.Popen( ( "/usr/bin/hdiutil", "attach", "-plist", "-mountrandom", "/private/tmp", "-nobrowse", pathname, ), stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, ) (stdout, stderr) = proc.communicate(stdin) except OSError as err: raise ProcessorError( "hdiutil execution failed with error code %d: %s" % (err.errno, err.strerror) ) if proc.returncode != 0: raise ProcessorError("mounting %s failed: %s" % (pathname, stderr)) # Read output plist. (pliststr, stdout) = self.get_first_plist(stdout) try: output = FoundationPlist.readPlistFromString(pliststr) except FoundationPlist.NSPropertyListSerializationException: raise ProcessorError( "mounting %s failed: unexpected output from hdiutil" % pathname ) # Find mount point. for part in output.get("system-entities", []): if "mount-point" in part: # Add to mount list. self.mounts[pathname] = part["mount-point"] self.output("Mounted disk image %s" % pathname) return self.mounts[pathname] raise ProcessorError( "mounting %s failed: unexpected output from hdiutil" % pathname )
def read_input_plist(self): """Read environment from input plist.""" try: indata = self.infile.read() if indata: self.env = FoundationPlist.readPlistFromString(indata) else: self.env = dict() except BaseException as err: raise ProcessorError(err)
def mount(self, pathname): """Mount image with hdiutil.""" # Make sure we don't try to mount something twice. if pathname in self.mounts: raise ProcessorError("%s is already mounted" % pathname) stdin = "" if self.dmg_has_sla(pathname): stdin = "Y\n" # Call hdiutil. try: proc = subprocess.Popen( ( "/usr/bin/hdiutil", "attach", "-plist", "-mountrandom", "/private/tmp", "-nobrowse", pathname, ), stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, ) (stdout, stderr) = proc.communicate(stdin) except OSError as err: raise ProcessorError( "hdiutil execution failed with error code %d: %s" % (err.errno, err.strerror)) if proc.returncode != 0: raise ProcessorError("mounting %s failed: %s" % (pathname, stderr)) # Read output plist. (pliststr, stdout) = self.get_first_plist(stdout) try: output = FoundationPlist.readPlistFromString(pliststr) except FoundationPlist.NSPropertyListSerializationException: raise ProcessorError( "mounting %s failed: unexpected output from hdiutil" % pathname) # Find mount point. for part in output.get("system-entities", []): if "mount-point" in part: # Add to mount list. self.mounts[pathname] = part["mount-point"] self.output("Mounted disk image %s" % pathname) return self.mounts[pathname] raise ProcessorError( "mounting %s failed: unexpected output from hdiutil" % pathname)
def verify_dmg(self, src_dmg): '''Check that dmg is good.''' try: p = subprocess.Popen(("/usr/bin/hdiutil", "verify", "-plist", src_dmg), stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err) = p.communicate() info = FoundationPlist.readPlistFromString(out) except (OSError, IOError), err: raise ProcessorError( "Couldn't verify dmg file")
def get_share_credentials(self): if 'credential_file' not in self.env: return '' else: try: cfile = open(self.env['credential_file']) data = cfile.read() cfile.close() plist = FoundationPlist.readPlistFromString(data) username = plist.get('Username') password = plist.get('Password') except BaseException as err: raise ProcessorError(err) return "%s:%s@" % (username, password)
def get_share_credentials(self): if 'credential_file' not in self.env: return '' else: try: cfile = open(self.env['credential_file']) data = cfile.read() cfile.close() plist = FoundationPlist.readPlistFromString(data) username = plist.get('Username') password = plist.get('Password') except Exception as err: raise ProcessorError(err) return "%s:%s@" % (username, password)
def get_hardware_info(): '''Uses system profiler to get hardware info for this machine''' cmd = ['/usr/sbin/system_profiler', 'SPHardwareDataType', '-xml'] proc = subprocess.Popen(cmd, shell=False, bufsize=-1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (output, unused_error) = proc.communicate() try: plist = FoundationPlist.readPlistFromString(output) # system_profiler xml is an array sp_dict = plist[0] items = sp_dict['_items'] sp_hardware_dict = items[0] return sp_hardware_dict except Exception: return {}
def loadData(self): pool = NSAutoreleasePool.alloc().init() self.volumes = macdisk.MountedVolumes() theURL = Utils.getServerURL() if theURL: plistData = Utils.downloadFile(theURL) converted_plist = FoundationPlist.readPlistFromString(plistData) self.passwordHash = converted_plist['password'] self.workflows = converted_plist['workflows'] else: self.passwordHash = False self.performSelectorOnMainThread_withObject_waitUntilDone_( self.loadDataComplete, None, YES) del pool
def convert_to_dmg(self, src_cdr, out_dmg): '''Convert cdr to dmg''' try: p = subprocess.Popen(("/usr/bin/hdiutil", "convert", "-plist", "-format", "UDZO", "-o", out_dmg, src_cdr), stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err) = p.communicate() info = FoundationPlist.readPlistFromString(out) dmg_path = info[0] except (OSError, IOError), err: raise ProcessorError( "Couldn't convert cdr file")
def getPlistData(data): # Try the user's homedir try: # NSLog("Trying Home Location") homedir = os.path.expanduser("~") plist = FoundationPlist.readPlist( os.path.join(homedir, "Library", "Preferences", "com.grahamgilbert.Imagr.plist")) return plist[data] except: pass # Try the main prefs try: # NSLog("Trying System Location") plist = FoundationPlist.readPlist( os.path.join("/Library", "Preferences", "com.grahamgilbert.Imagr.plist")) return plist[data] except: pass # Hopefully we're in a netboot set, try in /System/Installation/Packages try: # NSLog("Trying NetBoot Location") plist = FoundationPlist.readPlist( os.path.join("/System", "Installation", "Packages", "com.grahamgilbert.Imagr.plist")) return plist[data] except: pass # last chance; look for a file next to the app appPath = NSBundle.mainBundle().bundlePath() appDirPath = os.path.dirname(appPath) try: plistData = open( os.path.join(appDirPath, "com.grahamgilbert.Imagr.plist")).read() plistData = plistData.replace("{{current_volume_path}}", currentVolumePath()).encode("utf8") plist = FoundationPlist.readPlistFromString(plistData) return plist[data] except: pass
def mountAdobeDmg(dmgpath): """ Attempts to mount the dmg at dmgpath and returns a list of mountpoints """ mountpoints = [] dmgname = os.path.basename(dmgpath) proc = subprocess.Popen(['/usr/bin/hdiutil', 'attach', dmgpath, '-nobrowse', '-noverify', '-plist'], bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (pliststr, err) = proc.communicate() if err: munkicommon.display_error("Error %s mounting %s." % (err, dmgname)) if pliststr: plist = FoundationPlist.readPlistFromString(pliststr) for entity in plist['system-entities']: if 'mount-point' in entity: mountpoints.append(entity['mount-point']) return mountpoints
def DMGhasSLA(self, dmgpath): '''Returns true if dmg has a Software License Agreement. These dmgs normally cannot be attached without user intervention''' hasSLA = False proc = subprocess.Popen( ['/usr/bin/hdiutil', 'imageinfo', dmgpath, '-plist'], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err) = proc.communicate() if err: print >> sys.stderr, ( 'hdiutil error %s with image %s.' % (err, dmgpath)) (pliststr, out) = self.getFirstPlist(out) if pliststr: try: plist = FoundationPlist.readPlistFromString(pliststr) properties = plist.get('Properties') if properties: hasSLA = properties.get('Software License Agreement', False) except FoundationPlist.NSPropertyListSerializationException: pass return hasSLA
def process_hd_installer(self): ''' Process HD installer - app_json: Path to the Application JSON from within the PKG ''' #pylint: disable=too-many-locals, too-many-statements self.output('Processing HD installer') with open(self.env['app_json']) as json_file: load_json = json.load(json_file) # AppLaunch is not always in the same format, but is splittable if 'AppLaunch' in load_json: # Bridge CC is HD but does not have AppLaunch app_launch = load_json['AppLaunch'] self.output('app_launch: %s' % app_launch) app_details = list(re.split('/', app_launch)) if app_details[2].endswith('.app'): app_bundle = app_details[2] app_path = app_details[1] else: app_bundle = app_details[1] app_path = list(re.split('/', (load_json['InstallDir']['value'])))[1] self.output('app_bundle: %s' % app_bundle) self.output('app_path: %s' % app_path) installed_path = os.path.join('/Applications', app_path, app_bundle) self.output('installed_path: %s' % installed_path) if not app_path.endswith('CC') and not app_path.endswith('2020'): self.env['display_name'] = app_path + ' 2020' elif app_path.endswith('CC') and not app_path.endswith('2020'): self.env['display_name'] = app_path + ' 2020' else: self.env['display_name'] = app_path self.output('display_name: %s' % self.env['display_name']) zip_file = load_json['Packages']['Package'][0]['PackageName'] self.output('zip_file: %s' % zip_file) zip_path = os.path.join(self.env['PKG'], 'Contents/Resources/HD', \ self.env['target_folder'], zip_file + '.zip') self.output('zip_path: %s' % zip_path) with zipfile.ZipFile(zip_path, mode='r') as myzip: with myzip.open(zip_file + '.pimx') as mytxt: txt = mytxt.read() tree = ElementTree.fromstring(txt) # Loop through .pmx's Assets, look for target=[INSTALLDIR], # then grab Assets Source. # Break when found .app/Contents/Info.plist for elem in tree.findall('Assets'): for i in elem.getchildren(): # Below special tweak for the non-Classic Lightroom bundle if i.attrib['target'].upper().startswith('[INSTALLDIR]') and not i.attrib['target'].endswith('Icons'): bundle_location = i.attrib['source'] self.output('bundle_location: %s' % bundle_location) else: continue if not bundle_location.startswith('[StagingFolder]'): continue elif bundle_location.endswith('Icons') or \ bundle_location.endswith('AMT'): continue else: bundle_location = bundle_location[16:] if bundle_location.endswith('.app'): zip_bundle = os.path.join('1', bundle_location, \ 'Contents/Info.plist') else: zip_bundle = os.path.join('1', bundle_location, \ app_bundle, 'Contents/Info.plist') try: with myzip.open(zip_bundle) as myplist: plist = myplist.read() data = FoundationPlist.readPlistFromString(plist) # If the App is Lightroom (Classic or non-Classic) we need to compare a different value in Info.plist if self.env['sap_code'] == 'LTRM' or self.env['sap_code'] == 'LRCC': self.env['vers_compare_key'] = 'CFBundleVersion' else: self.env['vers_compare_key'] = \ 'CFBundleShortVersionString' self.output('vers_compare_key: %s' % \ self.env['vers_compare_key']) app_version = data[self.env['vers_compare_key']] self.output('staging_folder: %s' % bundle_location) self.output('staging_folder_path: %s' % zip_bundle) self.output('app_version: %s' % app_version) self.output('app_bundle: %s' % app_bundle) break except zipfile.BadZipfile: continue # Now we have the deets, let's use them self.create_pkginfo(app_bundle, app_version, installed_path)
def getManifest(manifest): '''Returns a plist dictionary of manifest data''' req = urllib2.Request(MANIFESTS_URL + '/' + urllib2.quote(manifest)) response = urllib2.urlopen(req) manifestData = response.read() return plistlib.readPlistFromString(manifestData)
def ImportFromPkgutil(pkgname, curs): """ Imports package data from pkgutil into our internal package database. """ timestamp = 0 owner = 0 pkgid = pkgname vers = "1.0" ppath = "" #get metadata from applepkgdb proc = subprocess.Popen(["/usr/sbin/pkgutil", "--pkg-info-plist", pkgid], bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (pliststr, unused_err) = proc.communicate() if pliststr: plist = FoundationPlist.readPlistFromString(pliststr) if "pkg-version" in plist: vers = plist["pkg-version"] if "install-time" in plist: timestamp = plist["install-time"] if "install-location" in plist: ppath = plist["install-location"] ppath = ppath.lstrip('./').rstrip('/') else: # there _should_ be an install-location. If there's not, let's # check the old /Library/Receipts. # (Workaround for QuarkXPress 8.1 packages) receiptpath = findBundleReceiptFromID(pkgid) if receiptpath: infopath = os.path.join(receiptpath, 'Contents/Info.plist') if os.path.exists(infopath): infopl = FoundationPlist.readPlist(infopath) if "IFPkgRelocatedPath" in infopl: ppath = infopl["IFPkgRelocatedPath"] ppath = ppath.lstrip('./').rstrip('/') values_t = (timestamp, owner, pkgid, vers, ppath, pkgname) curs.execute( '''INSERT INTO pkgs (timestamp, owner, pkgid, vers, ppath, pkgname) values (?, ?, ?, ?, ?, ?)''', values_t) pkgkey = curs.lastrowid cmd = ["/usr/sbin/pkgutil", "--files", pkgid] proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: line = proc.stdout.readline().decode('UTF-8') if not line and (proc.poll() != None): break path = line.rstrip("\n") # pkgutil --files pkgid only gives us path info. We don't # really need perms, uid and gid, so we'll just fake them. # if we needed them, we'd have to call # pkgutil --export-plist pkgid and iterate through the # plist. That would be slower, so we'll do things this way... perms = "0000" uid = "0" gid = "0" if path != ".": # special case for MS Office 2008 installers # /tmp/com.microsoft.updater/office_location if ppath == "tmp/com.microsoft.updater/office_location": ppath = "Applications" #prepend the ppath so the paths match the actual install locations path = path.lstrip("./") if ppath: path = ppath + "/" + path values_t = (path, ) row = curs.execute( 'SELECT path_key from paths where path = ?', values_t).fetchone() if not row: curs.execute( 'INSERT INTO paths (path) values (?)', values_t) pathkey = curs.lastrowid else: pathkey = row[0] values_t = (pkgkey, pathkey, uid, gid, perms) curs.execute( '''INSERT INTO pkgs_paths (pkg_key, path_key, uid, gid, perms) values (?, ?, ?, ?, ?)''', values_t)
def ImportBom(bompath, curs): """ Imports package data into our internal package database using a combination of the bom file and data in Apple's package database into our internal package database. """ # If we completely trusted the accuracy of Apple's database, we wouldn't # need the bom files, but in my enviroment at least, the bom files are # a better indicator of what flat packages have actually been installed # on the current machine. # We still need to consult Apple's package database # because the bom files are missing metadata about the package. #applepkgdb = "/Library/Receipts/db/a.receiptdb" pkgname = os.path.basename(bompath) timestamp = os.stat(bompath).st_mtime owner = 0 pkgid = os.path.splitext(pkgname)[0] vers = "1.0" ppath = "" #try to get metadata from applepkgdb proc = subprocess.Popen(["/usr/sbin/pkgutil", "--pkg-info-plist", pkgid], bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (pliststr, unused_err) = proc.communicate() if pliststr: plist = FoundationPlist.readPlistFromString(pliststr) if "install-location" in plist: ppath = plist["install-location"] ppath = ppath.lstrip('./').rstrip('/') if "pkg-version" in plist: vers = plist["pkg-version"] if "install-time" in plist: timestamp = plist["install-time"] values_t = (timestamp, owner, pkgid, vers, ppath, pkgname) curs.execute( '''INSERT INTO pkgs (timestamp, owner, pkgid, vers, ppath, pkgname) values (?, ?, ?, ?, ?, ?)''', values_t) pkgkey = curs.lastrowid cmd = ["/usr/bin/lsbom", bompath] proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: line = proc.stdout.readline().decode('UTF-8') if not line and (proc.poll() != None): break try: item = line.rstrip("\n").split("\t") path = item[0] perms = item[1] uidgid = item[2].split("/") uid = uidgid[0] gid = uidgid[1] except IndexError: # we really only care about the path perms = "0000" uid = "0" gid = "0" if path != ".": # special case for MS Office 2008 installers if ppath == "tmp/com.microsoft.updater/office_location": ppath = "Applications" #prepend the ppath so the paths match the actual install locations path = path.lstrip("./") if ppath: path = ppath + "/" + path values_t = (path, ) row = curs.execute( 'SELECT path_key from paths where path = ?', values_t).fetchone() if not row: curs.execute( 'INSERT INTO paths (path) values (?)', values_t) pathkey = curs.lastrowid else: pathkey = row[0] values_t = (pkgkey, pathkey, uid, gid, perms) curs.execute( '''INSERT INTO pkgs_paths (pkg_key, path_key, uid, gid, perms) values (?, ?, ?, ?, ?)''', values_t)
def ImportBom(bompath, curs): """ Imports package data into our internal package database using a combination of the bom file and data in Apple's package database into our internal package database. """ # If we completely trusted the accuracy of Apple's database, we wouldn't # need the bom files, but in my enviroment at least, the bom files are # a better indicator of what flat packages have actually been installed # on the current machine. # We still need to consult Apple's package database # because the bom files are missing metadata about the package. pkgname = os.path.basename(bompath) timestamp = os.stat(bompath).st_mtime owner = 0 pkgid = os.path.splitext(pkgname)[0] vers = "1.0" ppath = "" # try to get metadata from applepkgdb proc = subprocess.Popen(["/usr/sbin/pkgutil", "--pkg-info-plist", pkgid], bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (pliststr, dummy_err) = proc.communicate() if pliststr: plist = FoundationPlist.readPlistFromString(pliststr) if "install-location" in plist: ppath = plist["install-location"] ppath = ppath.lstrip('./').rstrip('/') if "pkg-version" in plist: vers = plist["pkg-version"] if "install-time" in plist: timestamp = plist["install-time"] values_t = (timestamp, owner, pkgid, vers, ppath, pkgname) curs.execute( '''INSERT INTO pkgs (timestamp, owner, pkgid, vers, ppath, pkgname) values (?, ?, ?, ?, ?, ?)''', values_t) pkgkey = curs.lastrowid cmd = ["/usr/bin/lsbom", bompath] proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: line = proc.stdout.readline().decode('UTF-8') if not line and (proc.poll() != None): break try: item = line.rstrip("\n").split("\t") path = item[0] perms = item[1] uidgid = item[2].split("/") uid = uidgid[0] gid = uidgid[1] except IndexError: # we really only care about the path perms = "0000" uid = "0" gid = "0" if path != ".": # special case for MS Office 2008 installers if ppath == "tmp/com.microsoft.updater/office_location": ppath = "Applications" #prepend the ppath so the paths match the actual install locations path = path.lstrip("./") if ppath: path = ppath + "/" + path values_t = (path, ) row = curs.execute('SELECT path_key from paths where path = ?', values_t).fetchone() if not row: curs.execute('INSERT INTO paths (path) values (?)', values_t) pathkey = curs.lastrowid else: pathkey = row[0] values_t = (pkgkey, pathkey, uid, gid, perms) curs.execute( 'INSERT INTO pkgs_paths (pkg_key, path_key, uid, gid, perms) ' 'values (?, ?, ?, ?, ?)', values_t)
def process_hd_installer(self): """Process HD installer Inputs: app_json: Path to the Application JSON that was extracted from the feed. """ self.output("Processing HD installer") with open(self.env["app_json"]) as json_file: load_json = json.load(json_file) # AppLaunch is not always in the same format, but is splittable if 'AppLaunch' in load_json: # Bridge CC is HD but does not have AppLaunch app_launch = load_json["AppLaunch"] self.output("app_launch: %s" % app_launch) app_details = list(re.split("/", app_launch)) if app_details[2].endswith(".app"): app_bundle = app_details[2] app_path = app_details[1] else: app_bundle = app_details[1] app_path = list(re.split("/", (load_json["InstallDir"]["value"])))[1] self.output("app_bundle: %s" % app_bundle) self.output("app_path: %s" % app_path) installed_path = os.path.join("/Applications", app_path, app_bundle) self.output("installed_path: %s" % installed_path) zip_file = load_json["Packages"]["Package"][0]["PackageName"] self.output("zip_file: %s" % zip_file) zip_path = os.path.join(self.env["pkg_path"], "Contents/Resources/HD", self.env["sapCode"] + self.env["ccpVersion"], zip_file + ".zip") self.output("zip_path: %s" % zip_path) with zipfile.ZipFile(zip_path, mode="r") as myzip: with myzip.open(zip_file + ".pimx") as mytxt: txt = mytxt.read() tree = ElementTree.fromstring(txt) # Loop through .pmx's Assets, look for target=[INSTALLDIR], then grab Assets Source. # Break when found .app/Contents/Info.plist for elem in tree.findall("Assets"): for i in elem.getchildren(): if i.attrib["target"].upper().startswith("[INSTALLDIR]"): bundle_location = i.attrib["source"] else: continue if not bundle_location.startswith("[StagingFolder]"): continue else: bundle_location = bundle_location[16:] if bundle_location.endswith(".app"): zip_bundle = os.path.join("1", bundle_location, "Contents/Info.plist") else: zip_bundle = os.path.join("1", bundle_location, app_bundle, "Contents/Info.plist") try: with myzip.open(zip_bundle) as myplist: plist = myplist.read() data = FoundationPlist.readPlistFromString(plist) app_version = data["CFBundleShortVersionString"] #app_identifier = data["CFBundleIdentifier"] self.output("staging_folder: %s" % bundle_location) self.output("staging_folder_path: %s" % zip_bundle) self.output("app_version: %s" % app_version) self.output("app_bundle: %s" % app_bundle) #self.output("app_identifier: %s" % app_identifier) break except: continue # Now we have the deets, let's use them self.create_pkginfo(app_bundle, app_version, installed_path)
def main(self): # clear any pre-exising summary result if 'munki_importer_summary_result' in self.env: del self.env['munki_importer_summary_result'] # Generate arguments for makepkginfo. args = ["/usr/local/munki/makepkginfo", self.env["pkg_path"]] if self.env.get("munkiimport_pkgname"): args.extend(["--pkgname", self.env["munkiimport_pkgname"]]) if self.env.get("munkiimport_appname"): args.extend(["--appname", self.env["munkiimport_appname"]]) # uninstaller pkg will be copied later, this is just to suppress # makepkginfo stderr warning output if self.env.get("uninstaller_pkg_path"): args.extend(["--uninstallpkg", self.env["uninstaller_pkg_path"]]) if self.env.get("additional_makepkginfo_options"): args.extend(self.env["additional_makepkginfo_options"]) # Call makepkginfo. try: proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err_out) = proc.communicate() except OSError as err: raise ProcessorError( "makepkginfo execution failed with error code %d: %s" % (err.errno, err.strerror)) if err_out: for err_line in err_out.splitlines(): self.output(err_line) if proc.returncode != 0: raise ProcessorError("creating pkginfo for %s failed: %s" % (self.env["pkg_path"], err_out)) # Get pkginfo from output plist. pkginfo = FoundationPlist.readPlistFromString(out) # copy any keys from pkginfo in self.env if "pkginfo" in self.env: for key in self.env["pkginfo"]: pkginfo[key] = self.env["pkginfo"][key] # set an alternate version_comparison_key # if pkginfo has an installs item if "installs" in pkginfo and self.env.get("version_comparison_key"): for item in pkginfo["installs"]: if not self.env["version_comparison_key"] in item: raise ProcessorError( ("version_comparison_key '%s' could not be found in " "the installs item for path '%s'") % (self.env["version_comparison_key"], item["path"])) item["version_comparison_key"] = ( self.env["version_comparison_key"]) # check to see if this item is already in the repo matchingitem = self.find_matching_item_in_repo(pkginfo) if matchingitem: self.env["pkginfo_repo_path"] = "" # set env["pkg_repo_path"] to the path of the matching item self.env["pkg_repo_path"] = os.path.join( self.env["MUNKI_REPO"], "pkgs", matchingitem['installer_item_location']) self.env["munki_info"] = {} if not "munki_repo_changed" in self.env: self.env["munki_repo_changed"] = False self.output("Item %s already exists in the munki repo as %s." % (os.path.basename(self.env["pkg_path"]), "pkgs/" + matchingitem['installer_item_location'])) return # copy pkg/dmg to repo relative_path = self.copy_item_to_repo(pkginfo) # adjust the installer_item_location to match the actual location # and name pkginfo["installer_item_location"] = relative_path if self.env.get("uninstaller_pkg_path"): relative_uninstall_path = self.copy_item_to_repo( pkginfo, uninstaller_pkg=True) pkginfo["uninstaller_item_location"] = relative_uninstall_path pkginfo["uninstallable"] = True # set output variables self.env["pkginfo_repo_path"] = self.copy_pkginfo_to_repo(pkginfo) self.env["pkg_repo_path"] = os.path.join(self.env["MUNKI_REPO"], "pkgs", relative_path) # update env["pkg_path"] to match env["pkg_repo_path"] # this allows subsequent recipe steps to reuse the uploaded # pkg/dmg instead of re-uploading # This won't affect most recipes, since most have a single # MunkiImporter step (and it's usually the last step) self.env["pkg_path"] = self.env["pkg_repo_path"] self.env["munki_info"] = pkginfo self.env["munki_repo_changed"] = True self.env["munki_importer_summary_result"] = { 'summary_text': 'The following new items were imported into Munki:', 'report_fields': ['name', 'version', 'catalogs', 'pkginfo_path', 'pkg_repo_path'], 'data': { 'name': pkginfo['name'], 'version': pkginfo['version'], 'catalogs': ','.join(pkginfo['catalogs']), 'pkginfo_path': self.env['pkginfo_repo_path'].partition('pkgsinfo/')[2], 'pkg_repo_path': self.env['pkg_repo_path'].partition('pkgs/')[2] } } self.output("Copied pkginfo to %s" % self.env["pkginfo_repo_path"]) self.output("Copied pkg to %s" % self.env["pkg_repo_path"])
def main(self): # Generate arguments for makepkginfo. args = ["/usr/local/munki/makepkginfo", self.env["pkg_path"]] if self.env.get("munkiimport_pkgname"): args.extend(["--pkgname", self.env["munkiimport_pkgname"]]) if self.env.get("munkiimport_appname"): args.extend(["--appname", self.env["munkiimport_appname"]]) if self.env.get("additional_makepkginfo_options"): args.extend(self.env["additional_makepkginfo_options"]) # Call makepkginfo. try: proc = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err_out) = proc.communicate() except OSError as err: raise ProcessorError( "makepkginfo execution failed with error code %d: %s" % (err.errno, err.strerror)) if proc.returncode != 0: raise ProcessorError( "creating pkginfo for %s failed: %s" % (self.env["pkg_path"], err_out)) # Get pkginfo from output plist. pkginfo = FoundationPlist.readPlistFromString(out) # copy any keys from pkginfo in self.env if "pkginfo" in self.env: for key in self.env["pkginfo"]: pkginfo[key] = self.env["pkginfo"][key] # check to see if this item is already in the repo matchingitem = self.findMatchingItemInRepo(pkginfo) if matchingitem: self.env["pkginfo_repo_path"] = "" # set env["pkg_repo_path"] to the path of the matching item self.env["pkg_repo_path"] = os.path.join( self.env["MUNKI_REPO"], "pkgs", matchingitem['installer_item_location']) self.env["munki_info"] = {} if not "munki_repo_changed" in self.env: self.env["munki_repo_changed"] = False self.output("Item %s already exists in the munki repo as %s." % (os.path.basename(self.env["pkg_path"]), "pkgs/" + matchingitem['installer_item_location'])) return # copy pkg/dmg to repo relative_path = self.copyItemToRepo(pkginfo) # adjust the installer_item_location to match the actual location # and name pkginfo["installer_item_location"] = relative_path # set output variables self.env["pkginfo_repo_path"] = self.copyPkginfoToRepo(pkginfo) self.env["pkg_repo_path"] = os.path.join( self.env["MUNKI_REPO"], "pkgs", relative_path) # update env["pkg_path"] to match env["pkg_repo_path"] # this allows subsequent recipe steps to reuse the uploaded # pkg/dmg instead of re-uploading # This won't affect most recipes, since most have a single # MunkiImporter step (and it's usually the last step) self.env["pkg_path"] = self.env["pkg_repo_path"] self.env["munki_info"] = pkginfo self.env["munki_repo_changed"] = True self.output("Copied pkginfo to %s" % self.env["pkginfo_repo_path"]) self.output("Copied pkg to %s" % self.env["pkg_repo_path"])
def process_hd_installer(self): """Process HD installer Inputs: app_json: Path to the Application JSON that was extracted from the feed. """ self.output("Processing HD installer") with open(self.env["app_json"]) as json_file: load_json = json.load(json_file) # AppLaunch is not always in the same format, but is splittable if 'AppLaunch' in load_json: # Bridge CC is HD but does not have AppLaunch app_launch = load_json["AppLaunch"] self.output("app_launch: %s" % app_launch) app_details = list(re.split("/", app_launch)) if app_details[2].endswith(".app"): app_bundle = app_details[2] app_path = app_details[1] else: app_bundle = app_details[1] app_path = list( re.split("/", (load_json["InstallDir"]["value"])))[1] self.output("app_bundle: %s" % app_bundle) self.output("app_path: %s" % app_path) installed_path = os.path.join("/Applications", app_path, app_bundle) self.output("installed_path: %s" % installed_path) zip_file = load_json["Packages"]["Package"][0]["PackageName"] self.output("zip_file: %s" % zip_file) zip_path = os.path.join( self.env["pkg_path"], "Contents/Resources/HD", self.env["sapCode"] + self.env["ccpVersion"], zip_file + ".zip") self.output("zip_path: %s" % zip_path) with zipfile.ZipFile(zip_path, mode="r") as myzip: with myzip.open(zip_file + ".pimx") as mytxt: txt = mytxt.read() tree = ElementTree.fromstring(txt) # Loop through .pmx's Assets, look for target=[INSTALLDIR], then grab Assets Source. # Break when found .app/Contents/Info.plist for elem in tree.findall("Assets"): for i in elem.getchildren(): if i.attrib["target"].upper().startswith( "[INSTALLDIR]"): bundle_location = i.attrib["source"] else: continue if not bundle_location.startswith( "[StagingFolder]"): continue else: bundle_location = bundle_location[16:] if bundle_location.endswith(".app"): zip_bundle = os.path.join( "1", bundle_location, "Contents/Info.plist") else: zip_bundle = os.path.join( "1", bundle_location, app_bundle, "Contents/Info.plist") try: with myzip.open(zip_bundle) as myplist: plist = myplist.read() data = FoundationPlist.readPlistFromString( plist) app_version = data[ "CFBundleShortVersionString"] #app_identifier = data["CFBundleIdentifier"] self.output("staging_folder: %s" % bundle_location) self.output( "staging_folder_path: %s" % zip_bundle) self.output("app_version: %s" % app_version) self.output("app_bundle: %s" % app_bundle) #self.output("app_identifier: %s" % app_identifier) break except: continue # Now we have the deets, let's use them self.create_pkginfo(app_bundle, app_version, installed_path)
def getCatalog(catalog): '''Takes a catalog name and returns the whole catalog''' req = urllib2.Request(CATALOG_URL + '/' + urllib2.quote(catalog)) response = urllib2.urlopen(req) catalogData = response.read() return plistlib.readPlistFromString(catalogData)
def ImportFromPkgutil(pkgname, curs): """ Imports package data from pkgutil into our internal package database. """ timestamp = 0 owner = 0 pkgid = pkgname vers = "1.0" ppath = "" #get metadata from applepkgdb proc = subprocess.Popen(["/usr/sbin/pkgutil", "--pkg-info-plist", pkgid], bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (pliststr, dummy_err) = proc.communicate() if pliststr: plist = FoundationPlist.readPlistFromString(pliststr) if "pkg-version" in plist: vers = plist["pkg-version"] if "install-time" in plist: timestamp = plist["install-time"] if "install-location" in plist: ppath = plist["install-location"] ppath = ppath.lstrip('./').rstrip('/') else: # there _should_ be an install-location. If there's not, let's # check the old /Library/Receipts. # (Workaround for QuarkXPress 8.1 packages) receiptpath = findBundleReceiptFromID(pkgid) if receiptpath: infopath = os.path.join(receiptpath, 'Contents/Info.plist') if os.path.exists(infopath): infopl = FoundationPlist.readPlist(infopath) if "IFPkgRelocatedPath" in infopl: ppath = infopl["IFPkgRelocatedPath"] ppath = ppath.lstrip('./').rstrip('/') values_t = (timestamp, owner, pkgid, vers, ppath, pkgname) curs.execute( '''INSERT INTO pkgs (timestamp, owner, pkgid, vers, ppath, pkgname) values (?, ?, ?, ?, ?, ?)''', values_t) pkgkey = curs.lastrowid cmd = ["/usr/sbin/pkgutil", "--files", pkgid] proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while True: line = proc.stdout.readline().decode('UTF-8') if not line and (proc.poll() != None): break path = line.rstrip("\n") # pkgutil --files pkgid only gives us path info. We don't # really need perms, uid and gid, so we'll just fake them. # if we needed them, we'd have to call # pkgutil --export-plist pkgid and iterate through the # plist. That would be slower, so we'll do things this way... perms = "0000" uid = "0" gid = "0" if path != ".": # special case for MS Office 2008 installers # /tmp/com.microsoft.updater/office_location if ppath == "tmp/com.microsoft.updater/office_location": ppath = "Applications" # another special case for Office 2011 updaters if ppath.startswith( 'tmp/com.microsoft.office.updater/com.microsoft.office.'): ppath = "" #prepend the ppath so the paths match the actual install locations path = path.lstrip("./") if ppath: path = ppath + "/" + path values_t = (path, ) row = curs.execute('SELECT path_key from paths where path = ?', values_t).fetchone() if not row: curs.execute('INSERT INTO paths (path) values (?)', values_t) pathkey = curs.lastrowid else: pathkey = row[0] values_t = (pkgkey, pathkey, uid, gid, perms) curs.execute( 'INSERT INTO pkgs_paths (pkg_key, path_key, uid, gid, perms) ' 'values (?, ?, ?, ?, ?)', values_t)
def main(self): # clear any pre-exising summary result if 'munki_importer_summary_result' in self.env: del self.env['munki_importer_summary_result'] # Generate arguments for makepkginfo. args = ["/usr/local/munki/makepkginfo", self.env["pkg_path"]] if self.env.get("munkiimport_pkgname"): args.extend(["--pkgname", self.env["munkiimport_pkgname"]]) if self.env.get("munkiimport_appname"): args.extend(["--appname", self.env["munkiimport_appname"]]) if self.env.get("additional_makepkginfo_options"): args.extend(self.env["additional_makepkginfo_options"]) # Call makepkginfo. try: proc = subprocess.Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (out, err_out) = proc.communicate() except OSError as err: raise ProcessorError( "makepkginfo execution failed with error code %d: %s" % (err.errno, err.strerror)) if err_out: for err_line in err_out.splitlines(): self.output(err_line) if proc.returncode != 0: raise ProcessorError( "creating pkginfo for %s failed: %s" % (self.env["pkg_path"], err_out)) # Get pkginfo from output plist. pkginfo = FoundationPlist.readPlistFromString(out) # copy any keys from pkginfo in self.env if "pkginfo" in self.env: for key in self.env["pkginfo"]: pkginfo[key] = self.env["pkginfo"][key] # set an alternate version_comparison_key # if pkginfo has an installs item if "installs" in pkginfo and self.env.get("version_comparison_key"): for item in pkginfo["installs"]: if not self.env["version_comparison_key"] in item: raise ProcessorError( ("version_comparison_key '%s' could not be found in " "the installs item for path '%s'") % (self.env["version_comparison_key"], item["path"])) item["version_comparison_key"] = ( self.env["version_comparison_key"]) # check to see if this item is already in the repo matchingitem = self.find_matching_item_in_repo(pkginfo) if matchingitem: self.env["pkginfo_repo_path"] = "" # set env["pkg_repo_path"] to the path of the matching item self.env["pkg_repo_path"] = os.path.join( self.env["MUNKI_REPO"], "pkgs", matchingitem['installer_item_location']) self.env["munki_info"] = {} if not "munki_repo_changed" in self.env: self.env["munki_repo_changed"] = False self.output("Item %s already exists in the munki repo as %s." % (os.path.basename(self.env["pkg_path"]), "pkgs/" + matchingitem['installer_item_location'])) return # copy pkg/dmg to repo relative_path = self.copy_item_to_repo(pkginfo) # adjust the installer_item_location to match the actual location # and name pkginfo["installer_item_location"] = relative_path # set output variables self.env["pkginfo_repo_path"] = self.copy_pkginfo_to_repo(pkginfo) self.env["pkg_repo_path"] = os.path.join( self.env["MUNKI_REPO"], "pkgs", relative_path) # update env["pkg_path"] to match env["pkg_repo_path"] # this allows subsequent recipe steps to reuse the uploaded # pkg/dmg instead of re-uploading # This won't affect most recipes, since most have a single # MunkiImporter step (and it's usually the last step) self.env["pkg_path"] = self.env["pkg_repo_path"] self.env["munki_info"] = pkginfo self.env["munki_repo_changed"] = True self.env["munki_importer_summary_result"] = { 'summary_text': 'The following new items were imported into Munki:', 'report_fields': ['name', 'version', 'catalogs', 'pkginfo_path', 'pkg_repo_path'], 'data': { 'name': pkginfo['name'], 'version': pkginfo['version'], 'catalogs': ','. join(pkginfo['catalogs']), 'pkginfo_path': self.env['pkginfo_repo_path'].partition('pkgsinfo/')[2], 'pkg_repo_path': self.env['pkg_repo_path'].partition('pkgs/')[2] } } self.output("Copied pkginfo to %s" % self.env["pkginfo_repo_path"]) self.output("Copied pkg to %s" % self.env["pkg_repo_path"]) self.report["items"] = { "Name": self.env["munki_info"]["name"], "Version": self.env["munki_info"]["version"], "Catalogs" : ", ".join(self.env["munki_info"]["catalogs"]), "Pkginfo Path" : self.env["pkginfo_repo_path"]\ .partition("pkgsinfo/")[2], }
def main(): dirpath = os.path.dirname(os.path.realpath(sys.argv[0])) opt = optparse.OptionParser() opts, args = opt.parse_args() # pprint.pprint(args[0]) tree = ET.parse(args[0]) output_location = tree.find(".//OutputLocation").text package_name = tree.find(".//PackageName").text root = tree.getroot() adobe_codes = {} target_names = {} product_names = {} media_list = tree.findall(".//Media") for media in media_list: if 'adobeCode' in media.find(".//DeploymentInstall//Payload").keys(): product_name = media.find(".//prodName").text # Remove the () around the year in the product name: (2015) -> 2015 # product_name = re.sub('[()]', '', product_name) # Remove the spaces and year from the name. # This should help match Munki item names product_name = re.sub('[()\s\d]', '', product_name) target_folder = media.find("TargetFolderName").text # adobe_code = media.find(".//mediaSignature").text adobe_code = media.find(".//DeploymentUninstall//Payload").attrib[ 'adobeCode'] # print target_folder, adobe_code target_name = media.find(".//IncludedPayloads//Payload/[AdobeCode='{0}']" .format(adobe_code)).findtext('TargetName') print "{0},{1},{2}".format(product_name, target_folder, adobe_code) adobe_codes[target_folder] = adobe_code target_names[target_folder] = target_name product_names[target_folder] = product_name for target_folder, adobe_code in adobe_codes.iteritems(): media_db = "{0}/{1}/Build/{2}_Install.pkg/Contents/Resources/Setup/{3}"\ "/payloads/{4}/Media_db.db".format( output_location, package_name, package_name, target_folder, target_names[target_folder]) install_db = "{0}/{1}/Build/{2}_Install.pkg/Contents/Resources/Setup/{3}"\ "/payloads/{4}/Install.db".format( output_location, package_name, package_name, target_folder, target_names[target_folder]) zip_file = "{0}/{1}/Build/{2}_Install.pkg/Contents/Resources/Setup/{3}"\ "/payloads/{4}/{4}.zip".format( output_location, package_name, package_name, target_folder, target_names[target_folder]) payloadinfo = ET.fromstring(get_payloadinfo(media_db, adobe_code)) applaunch_path = payloadinfo.find(".//AppLaunch").attrib['path'] app_contents = "{0}Contents".format( applaunch_path.split('Contents')[0]) app_info = "{0}/Info.plist".format(app_contents) source = get_source(install_db, app_info) if source is not None: head = source.split('/')[0].strip("[]").split('_')[1].title() tail = source.split('/', 1)[1] info_plist = "{0}/{1}".format(head, tail) with zipfile.ZipFile(zip_file) as zip: plist = FoundationPlist.readPlistFromString(zip.read(info_plist)) icon_file = plist.get('CFBundleIconFile') icon_path = "{0}/Resources/{1}".format(app_contents, icon_file) if not icon_path.endswith('.icns'): icon_path = icon_path + '.icns' icon_source = get_source(install_db, icon_path) if icon_source is not None: head = icon_source.split('/')[0].strip("[]").split('_')[1].title() tail = icon_source.split('/', 1)[1] icon_path = "{0}/{1}".format(head, tail) icon_icns = "{0}.icns".format(product_names[target_folder]) icon_png = "{0}.png".format(product_names[target_folder]) icns_dest = os.path.join(dirpath, 'icons', icon_icns) png_dest = os.path.join(dirpath, 'icons', icon_png) with zipfile.ZipFile(zip_file) as zip: with open(icns_dest, 'wb') as f: f.write(zip.read(icon_path)) convertIconToPNG(icns_dest, png_dest)