Move munkiimportlib.py and pkginfolib.py into munkilib/admin

This commit is contained in:
Greg Neagle
2017-11-19 08:39:22 -08:00
parent 810d5d833d
commit dbf0c95b9d
5 changed files with 1587 additions and 3 deletions
+1 -1
View File
@@ -40,8 +40,8 @@ import re
# our libs
from munkilib import info
from munkilib import osutils
from munkilib import pkginfolib
from munkilib import FoundationPlist
from munkilib.admin import pkginfolib
def has_valid_install_critieria(pkginfo):
+2 -2
View File
@@ -38,12 +38,12 @@ from munkilib.cliutils import raw_input_with_default
from munkilib import info
from munkilib import dmgutils
from munkilib import munkihash
from munkilib import munkiimportlib
from munkilib import munkirepo
from munkilib import osutils
from munkilib import pkginfolib
from munkilib import pkgutils
from munkilib import FoundationPlist
from munkilib.admin import munkiimportlib
from munkilib.admin import pkginfolib
def make_dmg(pkgpath):
@@ -0,0 +1,494 @@
# encoding: utf-8
#
# Copyright 2017 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
munkiimportlib
Created by Greg Neagle on 2017-11-18.
Routines used by munkimport to import items into Munki repo
"""
# std lib imports
import os
import sys
# our lib imports
from .. import iconutils
from .. import dmgutils
from .. import munkihash
from .. import munkirepo
from .. import osinstaller
from .. import osutils
from .. import pkgutils
from .. import FoundationPlist
from ..cliutils import pref
class RepoCopyError(Exception):
'''Exception raised when copying a file to the repo fails'''
pass
def list_items_of_kind(repo, kind):
'''Returns a list of items of kind. Relative pathnames are prepended
with kind. (example: ['icons/Bar.png', 'icons/Foo.png'])'''
return [os.path.join(kind, item) for item in repo.itemlist(kind)]
def copy_item_to_repo(repo, itempath, vers, subdirectory=''):
"""Copies an item to the appropriate place in the repo.
If itempath is a path within the repo/pkgs directory, copies nothing.
Renames the item if an item already exists with that name.
Returns the relative path to the item."""
destination_path = os.path.join('pkgs', subdirectory)
item_name = os.path.basename(itempath)
destination_path_name = os.path.join(destination_path, item_name)
name, ext = os.path.splitext(item_name)
if vers:
if not name.endswith(vers):
# add the version number to the end of the filename
item_name = '%s-%s%s' % (name, vers, ext)
destination_path_name = os.path.join(destination_path, item_name)
index = 0
try:
pkgs_list = list_items_of_kind(repo, 'pkgs')
except munkirepo.RepoError, err:
raise RepoCopyError(u'Unable to get list of current pkgs: %s'
% unicode(err))
while destination_path_name in pkgs_list:
print 'File %s already exists...' % destination_path_name
# try appending numbers until we have a unique name
index += 1
item_name = '%s__%s%s' % (name, index, ext)
destination_path_name = os.path.join(destination_path, item_name)
print 'Copying %s to %s...' % (os.path.basename(itempath),
destination_path_name)
try:
repo.put_from_local_file(destination_path_name, itempath)
except munkirepo.RepoError, err:
raise RepoCopyError(u'Unable to copy %s to %s: %s'
% (itempath, destination_path_name, unicode(err)))
else:
return os.path.join(subdirectory, item_name)
def copy_pkginfo_to_repo(repo, pkginfo, subdirectory=''):
"""Saves pkginfo to <munki_repo>/pkgsinfo/subdirectory"""
# less error checking because we copy the installer_item
# first and bail if it fails...
destination_path = os.path.join('pkgsinfo', subdirectory)
pkginfo_ext = pref('pkginfo_extension') or ''
if pkginfo_ext and not pkginfo_ext.startswith('.'):
pkginfo_ext = '.' + pkginfo_ext
pkginfo_name = '%s-%s%s' % (pkginfo['name'], pkginfo['version'],
pkginfo_ext)
pkginfo_path = os.path.join(destination_path, pkginfo_name)
index = 0
try:
pkgsinfo_list = list_items_of_kind(repo, 'pkgsinfo')
except munkirepo.RepoError, err:
raise RepoCopyError(u'Unable to get list of current pkgsinfo: %s'
% unicode(err))
while pkginfo_path in pkgsinfo_list:
index += 1
pkginfo_name = '%s-%s__%s%s' % (pkginfo['name'], pkginfo['version'],
index, pkginfo_ext)
pkginfo_path = os.path.join(destination_path, pkginfo_name)
print 'Saving pkginfo to %s...' % pkginfo_path
try:
pkginfo_str = FoundationPlist.writePlistToString(pkginfo)
except FoundationPlist.NSPropertyListWriteException, errmsg:
raise RepoCopyError(errmsg)
try:
repo.put(pkginfo_path, pkginfo_str)
except munkirepo.RepoError, err:
raise RepoCopyError('Unable to save pkginfo to %s: %s'
% (pkginfo_path, unicode(err)))
class CatalogDBException(Exception):
'''Exception to throw if we can't make a pkginfo DB'''
pass
class CatalogReadException(CatalogDBException):
'''Exception to throw if we can't read the all catalog'''
pass
class CatalogDecodeException(CatalogDBException):
'''Exception to throw if we can't decode the all catalog'''
pass
def make_catalog_db(repo):
"""Returns a dict we can use like a database"""
try:
plist = repo.get('catalogs/all')
except munkirepo.RepoError, err:
raise CatalogReadException(err)
try:
catalogitems = FoundationPlist.readPlistFromString(plist)
except FoundationPlist.NSPropertyListSerializationException, err:
raise CatalogDecodeException(err)
pkgid_table = {}
app_table = {}
installer_item_table = {}
hash_table = {}
profile_table = {}
itemindex = -1
for item in catalogitems:
itemindex = itemindex + 1
name = item.get('name', 'NO NAME')
vers = item.get('version', 'NO VERSION')
if name == 'NO NAME' or vers == 'NO VERSION':
print >> sys.stderr, 'WARNING: Bad pkginfo: %s' % item
# add to hash table
if 'installer_item_hash' in item:
if not item['installer_item_hash'] in hash_table:
hash_table[item['installer_item_hash']] = []
hash_table[item['installer_item_hash']].append(itemindex)
# add to installer item table
if 'installer_item_location' in item:
installer_item_name = os.path.basename(
item['installer_item_location'])
(name, ext) = os.path.splitext(installer_item_name)
if '-' in name:
(name, vers) = pkgutils.nameAndVersion(name)
installer_item_name = name + ext
if not installer_item_name in installer_item_table:
installer_item_table[installer_item_name] = {}
if not vers in installer_item_table[installer_item_name]:
installer_item_table[installer_item_name][vers] = []
installer_item_table[installer_item_name][vers].append(itemindex)
# add to table of receipts
for receipt in item.get('receipts', []):
try:
if 'packageid' in receipt and 'version' in receipt:
pkgid = receipt['packageid']
pkgvers = receipt['version']
if not pkgid in pkgid_table:
pkgid_table[pkgid] = {}
if not pkgvers in pkgid_table[pkgid]:
pkgid_table[pkgid][pkgvers] = []
pkgid_table[pkgid][pkgvers].append(itemindex)
except TypeError:
print >> sys.stderr, (
'Bad receipt data for %s-%s: %s' % (name, vers, receipt))
# add to table of installed applications
for install in item.get('installs', []):
try:
if install.get('type') == 'application':
if 'path' in install:
if not install['path'] in app_table:
app_table[install['path']] = {}
if not vers in app_table[install['path']]:
app_table[install['path']][vers] = []
app_table[install['path']][vers].append(itemindex)
except TypeError:
print >> sys.stderr, (
'Bad install data for %s-%s: %s' % (name, vers, install))
# add to table of PayloadIdentifiers
if 'PayloadIdentifier' in item:
if not item['PayloadIdentifier'] in profile_table:
profile_table[item['PayloadIdentifier']] = {}
if not vers in profile_table[item['PayloadIdentifier']]:
profile_table[item['PayloadIdentifier']][vers] = []
profile_table[item['PayloadIdentifier']][vers].append(itemindex)
pkgdb = {}
pkgdb['hashes'] = hash_table
pkgdb['receipts'] = pkgid_table
pkgdb['applications'] = app_table
pkgdb['installer_items'] = installer_item_table
pkgdb['profiles'] = profile_table
pkgdb['items'] = catalogitems
return pkgdb
def find_matching_pkginfo(repo, pkginfo):
"""Looks through repo catalogs looking for matching pkginfo
Returns a pkginfo dictionary, or an empty dict"""
def compare_version_keys(value_a, value_b):
"""Internal comparison function for use in sorting"""
return cmp(pkgutils.MunkiLooseVersion(value_b),
pkgutils.MunkiLooseVersion(value_a))
try:
catdb = make_catalog_db(repo)
except CatalogReadException, err:
# could not retreive catalogs/all
# do we have any existing pkgsinfo items?
pkgsinfo_items = repo.itemlist('pkgsinfo')
if len(pkgsinfo_items):
# there _are_ existing pkgsinfo items.
# warn about the problem since we can't seem to read catalogs/all
print (u'Could not get a list of existing items from the repo: %s'
% unicode(err))
return {}
except CatalogDBException, err:
# other error while processing catalogs/all
print (u'Could not get a list of existing items from the repo: %s'
% unicode(err))
return {}
if 'installer_item_hash' in pkginfo:
matchingindexes = catdb['hashes'].get(
pkginfo['installer_item_hash'])
if matchingindexes:
return catdb['items'][matchingindexes[0]]
if 'receipts' in pkginfo:
pkgids = [item['packageid']
for item in pkginfo['receipts']
if 'packageid' in item]
if pkgids:
possiblematches = catdb['receipts'].get(pkgids[0])
if possiblematches:
versionlist = possiblematches.keys()
versionlist.sort(compare_version_keys)
# go through possible matches, newest version first
for versionkey in versionlist:
testpkgindexes = possiblematches[versionkey]
for pkgindex in testpkgindexes:
testpkginfo = catdb['items'][pkgindex]
testpkgids = [item['packageid'] for item in
testpkginfo.get('receipts', [])
if 'packageid' in item]
if set(testpkgids) == set(pkgids):
return testpkginfo
if 'installs' in pkginfo:
applist = [item for item in pkginfo['installs']
if item['type'] == 'application'
and 'path' in item]
if applist:
app = applist[0]['path']
possiblematches = catdb['applications'].get(app)
if possiblematches:
versionlist = possiblematches.keys()
versionlist.sort(compare_version_keys)
indexes = catdb['applications'][app][versionlist[0]]
return catdb['items'][indexes[0]]
if 'PayloadIdentifier' in pkginfo:
identifier = pkginfo['PayloadIdentifier']
possiblematches = catdb['profiles'].get(identifier)
if possiblematches:
versionlist = possiblematches.keys()
versionlist.sort(compare_version_keys)
indexes = catdb['profiles'][identifier][versionlist[0]]
return catdb['items'][indexes[0]]
# no matches by receipts or installed applications,
# let's try to match based on installer_item_name
installer_item_name = os.path.basename(
pkginfo.get('installer_item_location', ''))
possiblematches = catdb['installer_items'].get(installer_item_name)
if possiblematches:
versionlist = possiblematches.keys()
versionlist.sort(compare_version_keys)
indexes = catdb['installer_items'][installer_item_name][versionlist[0]]
return catdb['items'][indexes[0]]
# if we get here, we found no matches
return {}
def get_icon_path(pkginfo):
"""Return path for icon"""
icon_name = pkginfo.get('icon_name') or pkginfo['name']
if not os.path.splitext(icon_name)[1]:
icon_name += u'.png'
return os.path.join(u'icons', icon_name)
def icon_exists_in_repo(repo, pkginfo):
"""Returns True if there is an icon for this item in the repo"""
icon_path = get_icon_path(pkginfo)
try:
icon_list = list_items_of_kind(repo, 'icons')
except munkirepo.RepoError, err:
raise RepoCopyError(u'Unable to get list of current icons: %s'
% unicode(err))
if icon_path in icon_list:
return True
return False
def add_icon_hash_to_pkginfo(pkginfo):
"""Adds the icon hash tp pkginfo if the icon exists in repo"""
icon_path = get_icon_path(pkginfo)
if os.path.isfile(icon_path):
pkginfo['icon_hash'] = munkihash.getsha256hash(icon_path)
def generate_png_from_startosinstall_item(repo, dmg_path, pkginfo):
'''Generates a product icon from a startosinstall item
and uploads to the repo'''
mountpoints = dmgutils.mountdmg(dmg_path)
if mountpoints:
mountpoint = mountpoints[0]
app_path = osinstaller.find_install_macos_app(mountpoint)
icon_path = iconutils.findIconForApp(app_path)
if icon_path:
convert_and_install_icon(repo, pkginfo, icon_path)
else:
print 'No application icons found.'
dmgutils.unmountdmg(mountpoint)
def generate_png_from_dmg_item(repo, dmg_path, pkginfo):
'''Generates a product icon from a copy_from_dmg item
and uploads to the repo'''
mountpoints = dmgutils.mountdmg(dmg_path)
if mountpoints:
mountpoint = mountpoints[0]
apps = [item for item in pkginfo.get('items_to_copy', [])
if item.get('source_item', '').endswith('.app')]
if len(apps):
app_path = os.path.join(mountpoint, apps[0]['source_item'])
icon_path = iconutils.findIconForApp(app_path)
if icon_path:
convert_and_install_icon(repo, pkginfo, icon_path)
else:
print 'No application icons found.'
else:
print 'No application icons found.'
dmgutils.unmountdmg(mountpoint)
def generate_pngs_from_pkg(repo, item_path, pkginfo):
'''Generates a product icon (or candidate icons) from an installer pkg
and uploads to the repo'''
icon_paths = []
mountpoint = None
pkg_path = None
if pkgutils.hasValidDiskImageExt(item_path):
dmg_path = item_path
mountpoints = dmgutils.mountdmg(dmg_path)
if mountpoints:
mountpoint = mountpoints[0]
if pkginfo.get('package_path'):
pkg_path = os.path.join(mountpoint, pkginfo['package_path'])
else:
# find first item that appears to be a pkg at the root
for fileitem in osutils.listdir(mountpoints[0]):
if pkgutils.hasValidPackageExt(fileitem):
pkg_path = os.path.join(mountpoint, fileitem)
break
elif pkgutils.hasValidPackageExt(item_path):
pkg_path = item_path
if pkg_path:
if os.path.isdir(pkg_path):
icon_paths = iconutils.extractAppIconsFromBundlePkg(pkg_path)
else:
icon_paths = iconutils.extractAppIconsFromFlatPkg(pkg_path)
if mountpoint:
dmgutils.unmountdmg(mountpoint)
if len(icon_paths) == 1:
convert_and_install_icon(repo, pkginfo, icon_paths[0])
elif len(icon_paths) > 1:
index = 1
for icon_path in icon_paths:
convert_and_install_icon(repo, pkginfo, icon_path, index=index)
index += 1
else:
print 'No application icons found.'
def convert_and_install_icon(repo, pkginfo, icon_path, index=None):
'''Convert icon file to png and save to repo icon path'''
destination_path = 'icons'
if index is not None:
destination_name = pkginfo['name'] + '_' + str(index)
else:
destination_name = pkginfo['name']
png_name = destination_name + u'.png'
repo_png_path = os.path.join(destination_path, png_name)
local_png_tmp = os.path.join(osutils.tmpdir(), png_name)
result = iconutils.convertIconToPNG(icon_path, local_png_tmp)
if result:
try:
repo.put_from_local_file(repo_png_path, local_png_tmp)
print 'Created icon: %s' % repo_png_path
except munkirepo.RepoError, err:
print >> sys.stderr, (u'Error uploading icon to %s: %s'
% (repo_png_path, unicode(err)))
else:
print >> sys.stderr, u'Error converting %s to png.' % icon_path
def copy_icon_to_repo(repo, iconpath):
"""Saves a product icon to the repo"""
destination_path = 'icons'
icon_name = os.path.basename(iconpath)
destination_path_name = os.path.join(destination_path, icon_name)
try:
icon_list = list_items_of_kind(repo, 'icons')
except munkirepo.RepoError, err:
raise RepoCopyError(u'Unable to get list of current icons: %s'
% unicode(err))
if destination_path_name in icon_list:
# remove any existing icon in the repo
try:
repo.delete(destination_path_name)
except munkirepo.RepoError, err:
raise RepoCopyError('Could not remove existing %s: %s'
% (destination_path_name, unicode(err)))
print 'Copying %s to %s...' % (icon_name, destination_path_name)
try:
repo.put_from_local_file(destination_path_name, iconpath)
except munkirepo.RepoError, err:
raise RepoCopyError('Unable to copy %s to %s: %s'
% (iconpath, destination_path_name, unicode(err)))
def extract_and_copy_icon(repo, installer_item, pkginfo):
'''Extracts an icon from an installer item, converts it to a png, and
copies to repo'''
installer_type = pkginfo.get('installer_type')
if installer_type == 'copy_from_dmg':
generate_png_from_dmg_item(repo, installer_item, pkginfo)
elif installer_type == 'startosinstall':
generate_png_from_startosinstall_item(repo, installer_item, pkginfo)
elif installer_type in [None, '']:
generate_pngs_from_pkg(repo, installer_item, pkginfo)
else:
print >> sys.stderr, (
'WARNING: Can\'t generate icons from installer_type: %s.'
% installer_type)
File diff suppressed because it is too large Load Diff