Files
munki/code/client/munkilib/updatecheck/manifestutils.py
2017-01-17 20:14:31 -08:00

274 lines
8.8 KiB
Python

# encoding: utf-8
#
# Copyright 2009-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.
"""
updatecheck.manifestutils
Created by Greg Neagle on 2016-12-16.
Functions for working with manifest files
"""
import os
import urllib2
from .. import display
from .. import fetch
from .. import info
from .. import keychain
from .. import prefs
from .. import reports
from .. import FoundationPlist
PRIMARY_MANIFEST_TAG = '_primary_manifest_'
class ManifestException(Exception):
"""Lets us raise an exception when we can't get a manifest."""
pass
class ManifestInvalidException(ManifestException):
"""Lets us raise an exception when we get an invalid manifest."""
pass
class ManifestNotRetrievedException(ManifestException):
"""Lets us raise an exception when manifest is not retrieved."""
pass
class ManifestServerConnectionException(ManifestException):
"""Exception for connection error."""
pass
def manifests():
'''Returns our internal _MANIFESTS dict'''
return _MANIFESTS
def set_manifest(name, path):
'''Stores path under name in our internal _MANIFESTS dict'''
_MANIFESTS[name] = path
def get_manifest(manifest_name, suppress_errors=False):
"""Gets a manifest from the server.
Returns:
string local path to the downloaded manifest
Raises:
fetch.ConnectionError if we can't connect to the server
ManifestException if we can't get the manifest
"""
if manifest_name in _MANIFESTS:
return _MANIFESTS[manifest_name]
manifestbaseurl = (prefs.pref('ManifestURL') or
prefs.pref('SoftwareRepoURL') + '/manifests/')
if (not manifestbaseurl.endswith('?') and
not manifestbaseurl.endswith('/')):
manifestbaseurl = manifestbaseurl + '/'
manifest_dir = os.path.join(prefs.pref('ManagedInstallDir'),
'manifests')
manifesturl = (
manifestbaseurl + urllib2.quote(manifest_name.encode('UTF-8')))
display.display_debug2('Manifest base URL is: %s', manifestbaseurl)
display.display_detail('Getting manifest %s...', manifest_name)
manifestpath = os.path.join(manifest_dir, manifest_name)
# Create the folder the manifest shall be stored in
destinationdir = os.path.dirname(manifestpath)
try:
os.makedirs(destinationdir)
except OSError, err:
# OSError will be raised if destinationdir exists, ignore this case
if not os.path.isdir(destinationdir):
if not suppress_errors:
display.display_error(
'Could not create folder to store manifest %s: %s',
manifest_name, err
)
raise ManifestException(err)
message = 'Retrieving list of software for this machine...'
try:
dummy_value = fetch.munki_resource(
manifesturl, manifestpath, message=message)
except fetch.ConnectionError, err:
raise ManifestServerConnectionException(err)
except fetch.Error, err:
if not suppress_errors:
display.display_error(
'Could not retrieve manifest %s from the server: %s',
manifest_name, err)
raise ManifestNotRetrievedException(err)
try:
# read plist to see if it is valid
dummy_data = FoundationPlist.readPlist(manifestpath)
except FoundationPlist.NSPropertyListSerializationException:
errormsg = 'manifest returned for %s is invalid.' % manifest_name
display.display_error(errormsg)
try:
os.unlink(manifestpath)
except (OSError, IOError):
pass
raise ManifestInvalidException(errormsg)
else:
# plist is valid
_MANIFESTS[manifest_name] = manifestpath
return manifestpath
def get_primary_manifest(alternate_id=''):
"""Gets the primary client manifest from the server."""
manifest = ""
if alternate_id:
clientidentifier = alternate_id
elif (prefs.pref('UseClientCertificate') and
prefs.pref('UseClientCertificateCNAsClientIdentifier')):
# we're to use the client cert CN as the clientidentifier
clientidentifier = keychain.get_client_cert_common_name()
else:
# get the ClientIdentifier from Munki's preferences
clientidentifier = prefs.pref('ClientIdentifier')
if clientidentifier:
manifest = get_manifest(clientidentifier)
else:
# no client identifier specified, so try the hostname
hostname = os.uname()[1]
clientidentifier = hostname
display.display_detail(
'No client id specified. Requesting %s...', clientidentifier)
try:
manifest = get_manifest(clientidentifier, suppress_errors=True)
except ManifestNotRetrievedException:
pass
if not manifest:
# try the short hostname
clientidentifier = hostname.split('.')[0]
display.display_detail(
'Request failed. Trying %s...', clientidentifier)
try:
manifest = get_manifest(
clientidentifier, suppress_errors=True)
except ManifestNotRetrievedException:
pass
if not manifest:
# try the machine serial number
clientidentifier = info.getMachineFacts()['serial_number']
if clientidentifier != 'UNKNOWN':
display.display_detail(
'Request failed. Trying %s...', clientidentifier)
try:
manifest = get_manifest(
clientidentifier, suppress_errors=True)
except ManifestNotRetrievedException:
pass
if not manifest:
# last resort - try for the site_default manifest
clientidentifier = 'site_default'
display.display_detail(
'Request failed. Trying %s...', clientidentifier)
manifest = get_manifest(clientidentifier, suppress_errors=True)
# record this info for later
# primary manifest is tagged as PRIMARY_MANIFEST_TAG
_MANIFESTS[PRIMARY_MANIFEST_TAG] = manifest
reports.report['ManifestName'] = clientidentifier
display.display_detail('Using manifest: %s', clientidentifier)
return manifest
def clean_up_manifests():
"""Removes any manifest files that are no longer in use by this client"""
manifest_dir = os.path.join(
prefs.pref('ManagedInstallDir'), 'manifests')
exceptions = [
"SelfServeManifest"
]
for (dirpath, dummy_dirnames, filenames) in os.walk(
manifest_dir, topdown=False):
for name in filenames:
if name in exceptions:
continue
abs_path = os.path.join(dirpath, name)
rel_path = abs_path[len(manifest_dir):].lstrip("/")
if rel_path not in _MANIFESTS:
os.unlink(abs_path)
# If the directory isn't the main manifest dir and is empty, try to
# remove it
if dirpath != manifest_dir and not len(os.listdir(dirpath)):
try:
os.rmdir(dirpath)
except OSError:
pass
def get_manifest_data(manifestpath):
'''Reads a manifest file, returns a dictionary-like object.'''
plist = {}
try:
plist = FoundationPlist.readPlist(manifestpath)
except FoundationPlist.NSPropertyListSerializationException:
display.display_error('Could not read plist: %s', manifestpath)
if os.path.exists(manifestpath):
try:
os.unlink(manifestpath)
except OSError, err:
display.display_error(
'Failed to delete plist: %s', unicode(err))
else:
display.display_error('plist does not exist.')
return plist
def get_manifest_value_for_key(manifestpath, keyname):
"""Returns a value for keyname in manifestpath"""
plist = get_manifest_data(manifestpath)
try:
return plist.get(keyname, None)
except AttributeError, err:
display.display_error(
'Failed to get manifest value for key: %s (%s)',
manifestpath, keyname)
display.display_error(
'Manifest is likely corrupt: %s', unicode(err))
return None
# module globals
_MANIFESTS = {}
if __name__ == '__main__':
print 'This is a library of support tools for the Munki Suite.'