mirror of
https://github.com/munki/munki.git
synced 2026-01-26 08:59:17 -06:00
munkicommon __init.py__ almost free of functions...
This commit is contained in:
@@ -22,55 +22,27 @@ Created by Greg Neagle on 2008-11-18.
|
||||
Common functions used by the munki tools.
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import fcntl
|
||||
import hashlib
|
||||
import os
|
||||
import logging
|
||||
import logging.handlers
|
||||
import platform
|
||||
import re
|
||||
import select
|
||||
import shutil
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import urllib2
|
||||
import warnings
|
||||
from distutils import version
|
||||
from types import StringType
|
||||
from xml.dom import minidom
|
||||
|
||||
from .. import munkistatus
|
||||
from .. import FoundationPlist
|
||||
|
||||
# We wildcard-import from submodules for backwards compatibility; functions
|
||||
# that were previously available from this module
|
||||
# pylint: disable=wildcard-import
|
||||
from .authrestart import *
|
||||
from .constants import *
|
||||
from .display import *
|
||||
from .dmgutils import *
|
||||
from .hash import *
|
||||
from .info import *
|
||||
from .munkilog import *
|
||||
from .osutils import *
|
||||
from .output import *
|
||||
from .pkgutils import *
|
||||
from .prefs import *
|
||||
from .processes import *
|
||||
from .reports import *
|
||||
from .scriptutils import *
|
||||
# pylint: enable=wildcard-import
|
||||
|
||||
import LaunchServices
|
||||
|
||||
# PyLint cannot properly find names inside Cocoa libraries, so issues bogus
|
||||
# No name 'Foo' in module 'Bar' warnings. Disable them.
|
||||
# pylint: disable=E0611
|
||||
from Foundation import NSDate, NSMetadataQuery, NSPredicate, NSRunLoop
|
||||
# pylint: enable=E0611
|
||||
|
||||
# we use lots of camelCase-style names. Deal with it.
|
||||
# we use camelCase-style names. Deal with it.
|
||||
# pylint: disable=C0103
|
||||
|
||||
|
||||
@@ -82,249 +54,22 @@ def stopRequested():
|
||||
global _stop_requested
|
||||
if _stop_requested:
|
||||
return True
|
||||
STOP_REQUEST_FLAG = (
|
||||
stop_request_flag = (
|
||||
'/private/tmp/'
|
||||
'com.googlecode.munki.managedsoftwareupdate.stop_requested')
|
||||
if munkistatusoutput:
|
||||
if os.path.exists(STOP_REQUEST_FLAG):
|
||||
if os.path.exists(stop_request_flag):
|
||||
# store this so it's persistent until this session is over
|
||||
_stop_requested = True
|
||||
log('### User stopped session ###')
|
||||
try:
|
||||
os.unlink(STOP_REQUEST_FLAG)
|
||||
os.unlink(stop_request_flag)
|
||||
except OSError, err:
|
||||
display_error(
|
||||
'Could not remove %s: %s', STOP_REQUEST_FLAG, err)
|
||||
'Could not remove %s: %s', stop_request_flag, err)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def gethash(filename, hash_function):
|
||||
"""
|
||||
Calculates the hashvalue of the given file with the given hash_function.
|
||||
|
||||
Args:
|
||||
filename: The file name to calculate the hash value of.
|
||||
hash_function: The hash function object to use, which was instanciated
|
||||
before calling this function, e.g. hashlib.md5().
|
||||
|
||||
Returns:
|
||||
The hashvalue of the given file as hex string.
|
||||
"""
|
||||
if not os.path.isfile(filename):
|
||||
return 'NOT A FILE'
|
||||
|
||||
f = open(filename, 'rb')
|
||||
while 1:
|
||||
chunk = f.read(2**16)
|
||||
if not chunk:
|
||||
break
|
||||
hash_function.update(chunk)
|
||||
f.close()
|
||||
return hash_function.hexdigest()
|
||||
|
||||
|
||||
def getmd5hash(filename):
|
||||
"""
|
||||
Returns hex of MD5 checksum of a file
|
||||
"""
|
||||
hash_function = hashlib.md5()
|
||||
return gethash(filename, hash_function)
|
||||
|
||||
|
||||
def getsha256hash(filename):
|
||||
"""
|
||||
Returns the SHA-256 hash value of a file as a hex string.
|
||||
"""
|
||||
hash_function = hashlib.sha256()
|
||||
return gethash(filename, hash_function)
|
||||
|
||||
|
||||
def isApplication(pathname):
|
||||
"""Returns true if path appears to be an OS X application"""
|
||||
# No symlinks, please
|
||||
if os.path.islink(pathname):
|
||||
return False
|
||||
if pathname.endswith('.app'):
|
||||
return True
|
||||
if os.path.isdir(pathname):
|
||||
# look for app bundle structure
|
||||
# use Info.plist to determine the name of the executable
|
||||
infoplist = os.path.join(pathname, 'Contents', 'Info.plist')
|
||||
if os.path.exists(infoplist):
|
||||
plist = FoundationPlist.readPlist(infoplist)
|
||||
if 'CFBundlePackageType' in plist:
|
||||
if plist['CFBundlePackageType'] != 'APPL':
|
||||
return False
|
||||
# get CFBundleExecutable,
|
||||
# falling back to bundle name if it's missing
|
||||
bundleexecutable = plist.get(
|
||||
'CFBundleExecutable', os.path.basename(pathname))
|
||||
bundleexecutablepath = os.path.join(
|
||||
pathname, 'Contents', 'MacOS', bundleexecutable)
|
||||
if os.path.exists(bundleexecutablepath):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# utility functions for running scripts from pkginfo files
|
||||
# used by updatecheck.py and installer.py
|
||||
|
||||
def writefile(stringdata, path):
|
||||
'''Writes string data to path.
|
||||
Returns the path on success, empty string on failure.'''
|
||||
try:
|
||||
fileobject = open(path, mode='w', buffering=1)
|
||||
# write line-by-line to ensure proper UNIX line-endings
|
||||
for line in stringdata.splitlines():
|
||||
print >> fileobject, line.encode('UTF-8')
|
||||
fileobject.close()
|
||||
return path
|
||||
except (OSError, IOError):
|
||||
display_error("Couldn't write %s" % stringdata)
|
||||
return ""
|
||||
|
||||
|
||||
def runEmbeddedScript(scriptname, pkginfo_item, suppress_error=False):
|
||||
'''Runs a script embedded in the pkginfo.
|
||||
Returns the result code.'''
|
||||
|
||||
# get the script text from the pkginfo
|
||||
script_text = pkginfo_item.get(scriptname)
|
||||
itemname = pkginfo_item.get('name')
|
||||
if not script_text:
|
||||
display_error(
|
||||
'Missing script %s for %s' % (scriptname, itemname))
|
||||
return -1
|
||||
|
||||
# write the script to a temp file
|
||||
scriptpath = os.path.join(tmpdir(), scriptname)
|
||||
if writefile(script_text, scriptpath):
|
||||
cmd = ['/bin/chmod', '-R', 'o+x', scriptpath]
|
||||
retcode = subprocess.call(cmd)
|
||||
if retcode:
|
||||
display_error(
|
||||
'Error setting script mode in %s for %s'
|
||||
% (scriptname, itemname))
|
||||
return -1
|
||||
else:
|
||||
display_error(
|
||||
'Cannot write script %s for %s' % (scriptname, itemname))
|
||||
return -1
|
||||
|
||||
# now run the script
|
||||
return runScript(
|
||||
itemname, scriptpath, scriptname, suppress_error=suppress_error)
|
||||
|
||||
|
||||
def runScript(itemname, path, scriptname, suppress_error=False):
|
||||
'''Runs a script, Returns return code.'''
|
||||
if suppress_error:
|
||||
display_detail(
|
||||
'Running %s for %s ' % (scriptname, itemname))
|
||||
else:
|
||||
display_status_minor(
|
||||
'Running %s for %s ' % (scriptname, itemname))
|
||||
if munkistatusoutput:
|
||||
# set indeterminate progress bar
|
||||
munkistatus.percent(-1)
|
||||
|
||||
scriptoutput = []
|
||||
try:
|
||||
proc = subprocess.Popen(path, shell=False, bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
except OSError, e:
|
||||
display_error(
|
||||
'Error executing script %s: %s' % (scriptname, str(e)))
|
||||
return -1
|
||||
|
||||
while True:
|
||||
msg = proc.stdout.readline().decode('UTF-8')
|
||||
if not msg and (proc.poll() != None):
|
||||
break
|
||||
# save all script output in case there is
|
||||
# an error so we can dump it to the log
|
||||
scriptoutput.append(msg)
|
||||
msg = msg.rstrip("\n")
|
||||
display_info(msg)
|
||||
|
||||
retcode = proc.poll()
|
||||
if retcode and not suppress_error:
|
||||
display_error(
|
||||
'Running %s for %s failed.' % (scriptname, itemname))
|
||||
display_error("-"*78)
|
||||
for line in scriptoutput:
|
||||
display_error("\t%s" % line.rstrip("\n"))
|
||||
display_error("-"*78)
|
||||
elif not suppress_error:
|
||||
log('Running %s for %s was successful.' % (scriptname, itemname))
|
||||
|
||||
if munkistatusoutput:
|
||||
# clear indeterminate progress bar
|
||||
munkistatus.percent(0)
|
||||
|
||||
return retcode
|
||||
|
||||
|
||||
def forceLogoutNow():
|
||||
"""Force the logout of interactive GUI users and spawn MSU."""
|
||||
try:
|
||||
procs = findProcesses(exe=LOGINWINDOW)
|
||||
users = {}
|
||||
for pid in procs:
|
||||
users[procs[pid]['user']] = pid
|
||||
|
||||
if 'root' in users:
|
||||
del users['root']
|
||||
|
||||
# force MSU GUI to raise
|
||||
f = open('/private/tmp/com.googlecode.munki.installatlogout', 'w')
|
||||
f.close()
|
||||
|
||||
# kill loginwindows to cause logout of current users, whether
|
||||
# active or switched away via fast user switching.
|
||||
for user in users:
|
||||
try:
|
||||
os.kill(users[user], signal.SIGKILL)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
except BaseException, err:
|
||||
display_error('Exception in forceLogoutNow(): %s' % str(err))
|
||||
|
||||
|
||||
def blockingApplicationsRunning(pkginfoitem):
|
||||
"""Returns true if any application in the blocking_applications list
|
||||
is running or, if there is no blocking_applications list, if any
|
||||
application in the installs list is running."""
|
||||
|
||||
if 'blocking_applications' in pkginfoitem:
|
||||
appnames = pkginfoitem['blocking_applications']
|
||||
else:
|
||||
# if no blocking_applications specified, get appnames
|
||||
# from 'installs' list if it exists
|
||||
appnames = [os.path.basename(item.get('path'))
|
||||
for item in pkginfoitem.get('installs', [])
|
||||
if item['type'] == 'application']
|
||||
|
||||
display_debug1("Checking for %s" % appnames)
|
||||
running_apps = [appname for appname in appnames
|
||||
if isAppRunning(appname)]
|
||||
if running_apps:
|
||||
display_detail(
|
||||
"Blocking apps for %s are running:" % pkginfoitem['name'])
|
||||
display_detail(" %s" % running_apps)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Placeholder"""
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
@@ -25,9 +25,9 @@ Functions supporting FileVault authrestart.
|
||||
|
||||
import subprocess
|
||||
|
||||
from .osutils import getOsVersion
|
||||
from .output import display_debug1, display_error, display_warning, log
|
||||
from .prefs import pref
|
||||
from . import display
|
||||
from . import osutils
|
||||
from . import prefs
|
||||
from .. import FoundationPlist
|
||||
|
||||
|
||||
@@ -36,22 +36,22 @@ def supports_auth_restart():
|
||||
if an Authorized Restart is supported, returns True
|
||||
or False accordingly.
|
||||
"""
|
||||
display_debug1('Checking if FileVault is Enabled...')
|
||||
display.display_debug1('Checking if FileVault is Enabled...')
|
||||
active_cmd = ['/usr/bin/fdesetup', 'isactive']
|
||||
try:
|
||||
is_active = subprocess.check_output(
|
||||
active_cmd, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
if exc.output and 'false' in exc.output:
|
||||
display_warning('FileVault appears to be Disabled...')
|
||||
display.display_warning('FileVault appears to be Disabled...')
|
||||
return False
|
||||
if not exc.output:
|
||||
display_warning(
|
||||
display.display_warning(
|
||||
'Encountered problem determining FileVault Status...')
|
||||
return False
|
||||
display_warning(exc.output)
|
||||
display.display_warning(exc.output)
|
||||
return False
|
||||
display_debug1(
|
||||
display.display_debug1(
|
||||
'Checking if FileVault can perform an AuthRestart...')
|
||||
support_cmd = ['/usr/bin/fdesetup', 'supportsauthrestart']
|
||||
try:
|
||||
@@ -59,17 +59,17 @@ def supports_auth_restart():
|
||||
support_cmd, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
if not exc.output:
|
||||
display_warning(
|
||||
display.display_warning(
|
||||
'Encountered problem determining AuthRestart Status...')
|
||||
return False
|
||||
display_warning(exc.output)
|
||||
display.display_warning(exc.output)
|
||||
return False
|
||||
if 'true' in is_active and 'true' in is_supported:
|
||||
display_debug1(
|
||||
display.display_debug1(
|
||||
'FileVault is On and Supports an AuthRestart...')
|
||||
return True
|
||||
else:
|
||||
display_warning(
|
||||
display.display_warning(
|
||||
'FileVault is Disabled or does not support an AuthRestart...')
|
||||
return False
|
||||
|
||||
@@ -78,12 +78,12 @@ def get_auth_restart_key():
|
||||
"""Returns recovery key as a string... If we failed
|
||||
to get the proper information, returns an empty string"""
|
||||
# checks to see if recovery key preference is set
|
||||
recoverykeyplist = pref('RecoveryKeyFile')
|
||||
recoverykeyplist = prefs.pref('RecoveryKeyFile')
|
||||
if not recoverykeyplist:
|
||||
display_warning(
|
||||
display.display_warning(
|
||||
"RecoveryKeyFile preference is not set")
|
||||
return ''
|
||||
display_debug1(
|
||||
display.display_debug1(
|
||||
'RecoveryKeyFile preference is set to {0}...'.format(recoverykeyplist))
|
||||
# try to get the recovery key from the defined location
|
||||
try:
|
||||
@@ -91,11 +91,11 @@ def get_auth_restart_key():
|
||||
recovery_key = keyplist['RecoveryKey'].strip()
|
||||
return recovery_key
|
||||
except FoundationPlist.NSPropertyListSerializationException:
|
||||
display_error(
|
||||
display.display_error(
|
||||
'We had trouble getting info from {0}...'.format(recoverykeyplist))
|
||||
return ''
|
||||
except KeyError:
|
||||
display_error(
|
||||
display.display_error(
|
||||
'Problem with Key: RecoveryKey in {0}...'.format(recoverykeyplist))
|
||||
return ''
|
||||
|
||||
@@ -105,38 +105,33 @@ def perform_auth_restart():
|
||||
to perform an authorized restart it checks to see if the machine supports
|
||||
the feature. If supported it will then look for the defined plist containing
|
||||
a key called RecoveryKey. It will use that value to perform the restart"""
|
||||
display_debug1(
|
||||
display.display_debug1(
|
||||
'Checking if performing an Auth Restart is fully supported...')
|
||||
if not supports_auth_restart():
|
||||
display_warning("Machine doesn't support Authorized Restarts...")
|
||||
display.display_warning("Machine doesn't support Authorized Restarts...")
|
||||
return False
|
||||
display_debug1('Machine Supports Authorized Restarts...')
|
||||
display.display_debug1('Machine supports Authorized Restarts...')
|
||||
recovery_key = get_auth_restart_key()
|
||||
if not recovery_key:
|
||||
return False
|
||||
key = {'Password': recovery_key}
|
||||
inputplist = FoundationPlist.writePlistToString(key)
|
||||
log('Attempting an Authorized Restart Now...')
|
||||
display.display_info('Attempting an Authorized Restart now...')
|
||||
cmd = subprocess.Popen(
|
||||
['/usr/bin/fdesetup', 'authrestart', '-inputplist'],
|
||||
stdout=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(dummy_out, err) = cmd.communicate(input=inputplist)
|
||||
os_version_tuple = getOsVersion(as_tuple=True)
|
||||
os_version_tuple = osutils.getOsVersion(as_tuple=True)
|
||||
if os_version_tuple >= (10, 12) and 'System is being restarted' in err:
|
||||
return True
|
||||
if err:
|
||||
display_error(err)
|
||||
display.display_error(err)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""Placeholder"""
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
3
code/client/munkilib/munkicommon/constants.py
Executable file → Normal file
3
code/client/munkilib/munkicommon/constants.py
Executable file → Normal file
@@ -46,3 +46,6 @@ ADDITIONAL_HTTP_HEADERS_KEY = 'AdditionalHttpHeaders'
|
||||
|
||||
LOGINWINDOW = (
|
||||
"/System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow")
|
||||
|
||||
if __name__ == '__main__':
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
251
code/client/munkilib/munkicommon/display.py
Executable file
251
code/client/munkilib/munkicommon/display.py
Executable file
@@ -0,0 +1,251 @@
|
||||
#!/usr/bin/python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Copyright 2009-2016 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.
|
||||
"""
|
||||
display.py
|
||||
|
||||
Created by Greg Neagle on 2016-12-13.
|
||||
|
||||
Common output functions
|
||||
"""
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from . import munkilog
|
||||
from . import prefs
|
||||
from . import reports
|
||||
from .. import munkistatus
|
||||
|
||||
|
||||
def getsteps(num_of_steps, limit):
|
||||
"""
|
||||
Helper function for display_percent_done
|
||||
"""
|
||||
steps = []
|
||||
current = 0.0
|
||||
for i in range(0, num_of_steps):
|
||||
if i == num_of_steps-1:
|
||||
steps.append(int(round(limit)))
|
||||
else:
|
||||
steps.append(int(round(current)))
|
||||
current += float(limit)/float(num_of_steps-1)
|
||||
return steps
|
||||
|
||||
|
||||
def display_percent_done(current, maximum):
|
||||
"""
|
||||
Mimics the command-line progress meter seen in some
|
||||
of Apple's tools (like softwareupdate), or tells
|
||||
MunkiStatus to display percent done via progress bar.
|
||||
"""
|
||||
if munkistatusoutput:
|
||||
step = getsteps(21, maximum)
|
||||
if current in step:
|
||||
if current == maximum:
|
||||
percentdone = 100
|
||||
else:
|
||||
percentdone = int(float(current)/float(maximum)*100)
|
||||
munkistatus.percent(str(percentdone))
|
||||
elif verbose > 0:
|
||||
step = getsteps(16, maximum)
|
||||
output = ''
|
||||
indicator = ['\t0', '.', '.', '20', '.', '.', '40', '.', '.',
|
||||
'60', '.', '.', '80', '.', '.', '100\n']
|
||||
for i in range(0, 16):
|
||||
if current >= step[i]:
|
||||
output += indicator[i]
|
||||
if output:
|
||||
sys.stdout.write('\r' + output)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def str_to_ascii(a_string):
|
||||
"""Given str (unicode, latin-1, or not) return ascii.
|
||||
|
||||
Args:
|
||||
s: str, likely in Unicode-16BE, UTF-8, or Latin-1 charset
|
||||
Returns:
|
||||
str, ascii form, no >7bit chars
|
||||
"""
|
||||
try:
|
||||
return unicode(a_string).encode('ascii', 'ignore')
|
||||
except UnicodeDecodeError:
|
||||
return a_string.decode('ascii', 'ignore')
|
||||
|
||||
|
||||
def to_unicode(obj, encoding='UTF-8'):
|
||||
"""Coerces basestring obj to unicode"""
|
||||
if isinstance(obj, basestring):
|
||||
if not isinstance(obj, unicode):
|
||||
obj = unicode(obj, encoding)
|
||||
return obj
|
||||
|
||||
|
||||
def concat_message(msg, *args):
|
||||
"""Concatenates a string with any additional arguments,
|
||||
making sure everything is unicode"""
|
||||
# coerce msg to unicode if it's not already
|
||||
msg = to_unicode(msg)
|
||||
if args:
|
||||
# coerce all args to unicode as well
|
||||
args = [to_unicode(arg) for arg in args]
|
||||
try:
|
||||
msg = msg % tuple(args)
|
||||
except TypeError, dummy_err:
|
||||
warnings.warn(
|
||||
'String format does not match concat args: %s'
|
||||
% (str(sys.exc_info())))
|
||||
return msg.rstrip()
|
||||
|
||||
|
||||
def display_status_major(msg, *args):
|
||||
"""
|
||||
Displays major status messages, formatting as needed
|
||||
for verbose/non-verbose and munkistatus-style output.
|
||||
"""
|
||||
msg = concat_message(msg, *args)
|
||||
munkilog.log(msg)
|
||||
if munkistatusoutput:
|
||||
munkistatus.message(msg)
|
||||
munkistatus.detail('')
|
||||
munkistatus.percent(-1)
|
||||
elif verbose > 0:
|
||||
if msg.endswith('.') or msg.endswith(u'…'):
|
||||
print '%s' % msg.encode('UTF-8')
|
||||
else:
|
||||
print '%s...' % msg.encode('UTF-8')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def display_status_minor(msg, *args):
|
||||
"""
|
||||
Displays minor status messages, formatting as needed
|
||||
for verbose/non-verbose and munkistatus-style output.
|
||||
"""
|
||||
msg = concat_message(msg, *args)
|
||||
munkilog.log(u' ' + msg)
|
||||
if munkistatusoutput:
|
||||
munkistatus.detail(msg)
|
||||
elif verbose > 0:
|
||||
if msg.endswith('.') or msg.endswith(u'…'):
|
||||
print ' %s' % msg.encode('UTF-8')
|
||||
else:
|
||||
print ' %s...' % msg.encode('UTF-8')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def display_info(msg, *args):
|
||||
"""
|
||||
Displays info messages.
|
||||
Not displayed in MunkiStatus.
|
||||
"""
|
||||
msg = concat_message(msg, *args)
|
||||
munkilog.log(u' ' + msg)
|
||||
if munkistatusoutput:
|
||||
pass
|
||||
elif verbose > 0:
|
||||
print ' %s' % msg.encode('UTF-8')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def display_detail(msg, *args):
|
||||
"""
|
||||
Displays minor info messages.
|
||||
Not displayed in MunkiStatus.
|
||||
These are usually logged only, but can be printed to
|
||||
stdout if verbose is set greater than 1
|
||||
"""
|
||||
msg = concat_message(msg, *args)
|
||||
if munkistatusoutput:
|
||||
pass
|
||||
elif verbose > 1:
|
||||
print ' %s' % msg.encode('UTF-8')
|
||||
sys.stdout.flush()
|
||||
if prefs.pref('LoggingLevel') > 0:
|
||||
munkilog.log(u' ' + msg)
|
||||
|
||||
|
||||
def display_debug1(msg, *args):
|
||||
"""
|
||||
Displays debug messages, formatting as needed
|
||||
for verbose/non-verbose and munkistatus-style output.
|
||||
"""
|
||||
msg = concat_message(msg, *args)
|
||||
if munkistatusoutput:
|
||||
pass
|
||||
elif verbose > 2:
|
||||
print ' %s' % msg.encode('UTF-8')
|
||||
sys.stdout.flush()
|
||||
if prefs.pref('LoggingLevel') > 1:
|
||||
munkilog.log('DEBUG1: %s' % msg)
|
||||
|
||||
|
||||
def display_debug2(msg, *args):
|
||||
"""
|
||||
Displays debug messages, formatting as needed
|
||||
for verbose/non-verbose and munkistatus-style output.
|
||||
"""
|
||||
msg = concat_message(msg, *args)
|
||||
if munkistatusoutput:
|
||||
pass
|
||||
elif verbose > 3:
|
||||
print ' %s' % msg.encode('UTF-8')
|
||||
if prefs.pref('LoggingLevel') > 2:
|
||||
munkilog.log('DEBUG2: %s' % msg)
|
||||
|
||||
|
||||
def display_warning(msg, *args):
|
||||
"""
|
||||
Prints warning msgs to stderr and the log
|
||||
"""
|
||||
msg = concat_message(msg, *args)
|
||||
warning = 'WARNING: %s' % msg
|
||||
if verbose > 0:
|
||||
print >> sys.stderr, warning.encode('UTF-8')
|
||||
munkilog.log(warning)
|
||||
# append this warning to our warnings log
|
||||
munkilog.log(warning, 'warnings.log')
|
||||
# collect the warning for later reporting
|
||||
if 'Warnings' not in reports.report:
|
||||
reports.report['Warnings'] = []
|
||||
reports.report['Warnings'].append('%s' % msg)
|
||||
|
||||
|
||||
def display_error(msg, *args):
|
||||
"""
|
||||
Prints msg to stderr and the log
|
||||
"""
|
||||
msg = concat_message(msg, *args)
|
||||
errmsg = 'ERROR: %s' % msg
|
||||
if verbose > 0:
|
||||
print >> sys.stderr, errmsg.encode('UTF-8')
|
||||
munkilog.log(errmsg)
|
||||
# append this error to our errors log
|
||||
munkilog.log(errmsg, 'errors.log')
|
||||
# collect the errors for later reporting
|
||||
if 'Errors' not in reports.report:
|
||||
reports.report['Errors'] = []
|
||||
reports.report['Errors'].append('%s' % msg)
|
||||
|
||||
|
||||
# module globals
|
||||
verbose = 1
|
||||
munkistatusoutput = False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
@@ -25,8 +25,9 @@ Utilities for working with disk images.
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from . import display
|
||||
from .. import FoundationPlist
|
||||
from .output import display_detail, display_error, display_warning
|
||||
|
||||
|
||||
# we use lots of camelCase-style names. Deal with it.
|
||||
# pylint: disable=C0103
|
||||
@@ -66,7 +67,7 @@ def DMGisWritable(dmgpath):
|
||||
bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(out, err) = proc.communicate()
|
||||
if err:
|
||||
display_error(
|
||||
display.display_error(
|
||||
u'hdiutil error %s with image %s.', err, dmgpath)
|
||||
(pliststr, out) = getFirstPlist(out)
|
||||
if pliststr:
|
||||
@@ -89,7 +90,7 @@ def DMGhasSLA(dmgpath):
|
||||
bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(out, err) = proc.communicate()
|
||||
if err:
|
||||
display_error(
|
||||
display.display_error(
|
||||
u'hdiutil error %s with image %s.', err, dmgpath)
|
||||
(pliststr, out) = getFirstPlist(out)
|
||||
if pliststr:
|
||||
@@ -115,7 +116,7 @@ def hdiutilInfo():
|
||||
bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(out, err) = proc.communicate()
|
||||
if err:
|
||||
display_error(u'hdiutil info error: %s', err)
|
||||
display.display_error(u'hdiutil info error: %s', err)
|
||||
(pliststr, out) = getFirstPlist(out)
|
||||
if pliststr:
|
||||
try:
|
||||
@@ -217,7 +218,7 @@ def mountdmg(dmgpath, use_shadow=False, use_existing_mounts=False):
|
||||
stdin = ''
|
||||
if DMGhasSLA(dmgpath):
|
||||
stdin = 'Y\n'
|
||||
display_detail(
|
||||
display.display_detail(
|
||||
'NOTE: %s has embedded Software License Agreement' % dmgname)
|
||||
cmd = ['/usr/bin/hdiutil', 'attach', dmgpath,
|
||||
'-mountRandom', '/tmp', '-nobrowse', '-plist']
|
||||
@@ -228,7 +229,7 @@ def mountdmg(dmgpath, use_shadow=False, use_existing_mounts=False):
|
||||
stderr=subprocess.PIPE, stdin=subprocess.PIPE)
|
||||
(out, err) = proc.communicate(stdin)
|
||||
if proc.returncode:
|
||||
display_error(
|
||||
display.display_error(
|
||||
'Error: "%s" while mounting %s.' % (err.rstrip(), dmgname))
|
||||
(pliststr, out) = getFirstPlist(out)
|
||||
if pliststr:
|
||||
@@ -238,7 +239,7 @@ def mountdmg(dmgpath, use_shadow=False, use_existing_mounts=False):
|
||||
if 'mount-point' in entity:
|
||||
mountpoints.append(entity['mount-point'])
|
||||
except FoundationPlist.NSPropertyListSerializationException:
|
||||
display_error(
|
||||
display.display_error(
|
||||
'Bad plist string returned when mounting diskimage %s:\n%s'
|
||||
% (dmgname, pliststr))
|
||||
return mountpoints
|
||||
@@ -254,20 +255,15 @@ def unmountdmg(mountpoint):
|
||||
(dummy_output, err) = proc.communicate()
|
||||
if proc.returncode:
|
||||
# ordinary unmount unsuccessful, try forcing
|
||||
display_warning('Polite unmount failed: %s' % err)
|
||||
display_warning('Attempting to force unmount %s' % mountpoint)
|
||||
display.display_warning('Polite unmount failed: %s' % err)
|
||||
display.display_warning('Attempting to force unmount %s' % mountpoint)
|
||||
cmd.append('-force')
|
||||
proc = subprocess.Popen(cmd, bufsize=-1, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(dummy_output, err) = proc.communicate()
|
||||
if proc.returncode:
|
||||
display_warning('Failed to unmount %s: %s', mountpoint, err)
|
||||
|
||||
|
||||
def main():
|
||||
"""Placeholder"""
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
display.display_warning('Failed to unmount %s: %s', mountpoint, err)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
71
code/client/munkilib/munkicommon/hash.py
Normal file
71
code/client/munkilib/munkicommon/hash.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Copyright 2009-2016 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.
|
||||
"""
|
||||
hash.py
|
||||
|
||||
Created by Greg Neagle on 2016-12-14.
|
||||
|
||||
|
||||
Munki's hash functions
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
def gethash(filename, hash_function):
|
||||
"""
|
||||
Calculates the hashvalue of the given file with the given hash_function.
|
||||
|
||||
Args:
|
||||
filename: The file name to calculate the hash value of.
|
||||
hash_function: The hash function object to use, which was instanciated
|
||||
before calling this function, e.g. hashlib.md5().
|
||||
|
||||
Returns:
|
||||
The hashvalue of the given file as hex string.
|
||||
"""
|
||||
if not os.path.isfile(filename):
|
||||
return 'NOT A FILE'
|
||||
|
||||
fileref = open(filename, 'rb')
|
||||
while 1:
|
||||
chunk = fileref.read(2**16)
|
||||
if not chunk:
|
||||
break
|
||||
hash_function.update(chunk)
|
||||
fileref.close()
|
||||
return hash_function.hexdigest()
|
||||
|
||||
|
||||
def getmd5hash(filename):
|
||||
"""
|
||||
Returns hex of MD5 checksum of a file
|
||||
"""
|
||||
hash_function = hashlib.md5()
|
||||
return gethash(filename, hash_function)
|
||||
|
||||
|
||||
def getsha256hash(filename):
|
||||
"""
|
||||
Returns the SHA-256 hash value of a file as a hex string.
|
||||
"""
|
||||
hash_function = hashlib.sha256()
|
||||
return gethash(filename, hash_function)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
@@ -37,10 +37,11 @@ import LaunchServices
|
||||
from Foundation import NSDate, NSMetadataQuery, NSPredicate, NSRunLoop
|
||||
# pylint: enable=E0611
|
||||
|
||||
from .osutils import getOsVersion, listdir
|
||||
from .output import display_debug1, display_error, display_warning, log
|
||||
from .pkgutils import getBundleVersion
|
||||
from .prefs import pref
|
||||
from . import display
|
||||
from . import munkilog
|
||||
from . import osutils
|
||||
from . import pkgutils
|
||||
from . import prefs
|
||||
from .. import FoundationPlist
|
||||
|
||||
# we use lots of camelCase-style names. Deal with it.
|
||||
@@ -241,7 +242,7 @@ def getFilesystems():
|
||||
# see man GETFSSTAT(2) for struct
|
||||
statfs_32_struct = '=hh ll ll ll lQ lh hl 2l 15s 90s 90s x 16x'
|
||||
statfs_64_struct = '=Ll QQ QQ Q ll l LLL 16s 1024s 1024s 32x'
|
||||
os_version = getOsVersion(as_tuple=True)
|
||||
os_version = osutils.getOsVersion(as_tuple=True)
|
||||
if os_version <= (10, 5):
|
||||
mode = 32
|
||||
else:
|
||||
@@ -265,7 +266,7 @@ def getFilesystems():
|
||||
n = libc.getfsstat(ctypes.byref(buf), bufsize, MNT_NOWAIT)
|
||||
|
||||
if n < 0:
|
||||
display_debug1('getfsstat() returned errno %d' % n)
|
||||
display.display_debug1('getfsstat() returned errno %d' % n)
|
||||
return {}
|
||||
|
||||
ofs = 0
|
||||
@@ -336,10 +337,12 @@ def isExcludedFilesystem(path, _retry=False):
|
||||
# perhaps the stat() on the path caused autofs to mount
|
||||
# the required filesystem and now it will be available.
|
||||
# try one more time to look for it after flushing the cache.
|
||||
display_debug1('Trying isExcludedFilesystem again for %s' % path)
|
||||
display.display_debug1(
|
||||
'Trying isExcludedFilesystem again for %s' % path)
|
||||
return isExcludedFilesystem(path, True)
|
||||
else:
|
||||
display_debug1('Could not match path %s to a filesystem' % path)
|
||||
display.display_debug1(
|
||||
'Could not match path %s to a filesystem' % path)
|
||||
return None
|
||||
|
||||
exc_flags = ('read-only' in FILESYSTEMS[st.st_dev]['f_flags_set'] or
|
||||
@@ -347,7 +350,7 @@ def isExcludedFilesystem(path, _retry=False):
|
||||
is_nfs = FILESYSTEMS[st.st_dev]['f_fstypename'] == 'nfs'
|
||||
|
||||
if is_nfs or exc_flags:
|
||||
display_debug1(
|
||||
display.display_debug1(
|
||||
'Excluding %s (flags %s, nfs %s)' % (path, exc_flags, is_nfs))
|
||||
|
||||
return is_nfs or exc_flags
|
||||
@@ -377,7 +380,7 @@ def findAppsInDirs(dirlist):
|
||||
query.stopQuery()
|
||||
|
||||
if runtime >= maxruntime:
|
||||
display_warning(
|
||||
display.display_warning(
|
||||
'Spotlight search for applications terminated due to excessive '
|
||||
'time. Possible causes: Spotlight indexing is turned off for a '
|
||||
'volume; Spotlight is reindexing a volume.')
|
||||
@@ -399,7 +402,7 @@ def getSpotlightInstalledApplications():
|
||||
dirlist = []
|
||||
applist = []
|
||||
|
||||
for f in listdir(u'/'):
|
||||
for f in osutils.listdir(u'/'):
|
||||
p = os.path.join(u'/', f)
|
||||
if os.path.isdir(p) and not os.path.islink(p) \
|
||||
and not isExcludedFilesystem(p):
|
||||
@@ -410,7 +413,7 @@ def getSpotlightInstalledApplications():
|
||||
|
||||
# Future code changes may mean we wish to look for Applications
|
||||
# installed on any r/w local volume.
|
||||
#for f in listdir(u'/Volumes'):
|
||||
#for f in osutils.listdir(u'/Volumes'):
|
||||
# p = os.path.join(u'/Volumes', f)
|
||||
# if os.path.isdir(p) and not os.path.islink(p) \
|
||||
# and not isExcludedFilesystem(p):
|
||||
@@ -445,76 +448,68 @@ def getLSInstalledApplications():
|
||||
return applist
|
||||
|
||||
|
||||
# we save SP_APPCACHE in a global to avoid querying system_profiler more than
|
||||
# once per session for application data, which can be slow
|
||||
SP_APPCACHE = None
|
||||
@Memoize
|
||||
def getSPApplicationData():
|
||||
'''Uses system profiler to get application info for this machine'''
|
||||
global SP_APPCACHE
|
||||
if SP_APPCACHE is None:
|
||||
cmd = ['/usr/sbin/system_profiler', 'SPApplicationsDataType', '-xml']
|
||||
# uses our internal Popen instead of subprocess's so we can timeout
|
||||
proc = Popen(cmd, shell=False, bufsize=-1,
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
try:
|
||||
output, dummy_error = proc.communicate(timeout=60)
|
||||
except TimeoutError:
|
||||
display_error(
|
||||
'system_profiler hung; skipping SPApplicationsDataType query')
|
||||
# return empty dict
|
||||
SP_APPCACHE = {}
|
||||
return SP_APPCACHE
|
||||
try:
|
||||
plist = FoundationPlist.readPlistFromString(output)
|
||||
# system_profiler xml is an array
|
||||
SP_APPCACHE = {}
|
||||
for item in plist[0]['_items']:
|
||||
SP_APPCACHE[item.get('path')] = item
|
||||
except BaseException:
|
||||
SP_APPCACHE = {}
|
||||
return SP_APPCACHE
|
||||
cmd = ['/usr/sbin/system_profiler', 'SPApplicationsDataType', '-xml']
|
||||
# uses our internal Popen instead of subprocess's so we can timeout
|
||||
proc = Popen(cmd, shell=False, bufsize=-1,
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
try:
|
||||
output, dummy_error = proc.communicate(timeout=60)
|
||||
except TimeoutError:
|
||||
display.display_error(
|
||||
'system_profiler hung; skipping SPApplicationsDataType query')
|
||||
# return empty dict
|
||||
return {}
|
||||
try:
|
||||
plist = FoundationPlist.readPlistFromString(output)
|
||||
# system_profiler xml is an array
|
||||
app_data = {}
|
||||
for item in plist[0]['_items']:
|
||||
app_data[item.get('path')] = item
|
||||
except BaseException:
|
||||
app_data = {}
|
||||
return app_data
|
||||
|
||||
|
||||
# we save APPDATA in a global to avoid querying LaunchServices more than
|
||||
# once per session
|
||||
APPDATA = None
|
||||
@Memoize
|
||||
def getAppData():
|
||||
"""Gets info on currently installed apps.
|
||||
Returns a list of dicts containing path, name, version and bundleid"""
|
||||
global APPDATA
|
||||
if APPDATA is None:
|
||||
APPDATA = []
|
||||
display_debug1('Getting info on currently installed applications...')
|
||||
applist = set(getLSInstalledApplications())
|
||||
applist.update(getSpotlightInstalledApplications())
|
||||
for pathname in applist:
|
||||
iteminfo = {}
|
||||
iteminfo['name'] = os.path.splitext(os.path.basename(pathname))[0]
|
||||
iteminfo['path'] = pathname
|
||||
plistpath = os.path.join(pathname, 'Contents', 'Info.plist')
|
||||
if os.path.exists(plistpath):
|
||||
try:
|
||||
plist = FoundationPlist.readPlist(plistpath)
|
||||
iteminfo['bundleid'] = plist.get('CFBundleIdentifier', '')
|
||||
if 'CFBundleName' in plist:
|
||||
iteminfo['name'] = plist['CFBundleName']
|
||||
iteminfo['version'] = getBundleVersion(pathname)
|
||||
APPDATA.append(iteminfo)
|
||||
except BaseException:
|
||||
pass
|
||||
else:
|
||||
# possibly a non-bundle app. Use system_profiler data
|
||||
# to get app name and version
|
||||
sp_app_data = getSPApplicationData()
|
||||
if pathname in sp_app_data:
|
||||
item = sp_app_data[pathname]
|
||||
iteminfo['bundleid'] = ''
|
||||
iteminfo['version'] = item.get('version') or '0.0.0.0.0'
|
||||
if item.get('_name'):
|
||||
iteminfo['name'] = item['_name']
|
||||
APPDATA.append(iteminfo)
|
||||
return APPDATA
|
||||
app_data = []
|
||||
display.display_debug1(
|
||||
'Getting info on currently installed applications...')
|
||||
applist = set(getLSInstalledApplications())
|
||||
applist.update(getSpotlightInstalledApplications())
|
||||
for pathname in applist:
|
||||
iteminfo = {}
|
||||
iteminfo['name'] = os.path.splitext(os.path.basename(pathname))[0]
|
||||
iteminfo['path'] = pathname
|
||||
plistpath = os.path.join(pathname, 'Contents', 'Info.plist')
|
||||
if os.path.exists(plistpath):
|
||||
try:
|
||||
plist = FoundationPlist.readPlist(plistpath)
|
||||
iteminfo['bundleid'] = plist.get('CFBundleIdentifier', '')
|
||||
if 'CFBundleName' in plist:
|
||||
iteminfo['name'] = plist['CFBundleName']
|
||||
iteminfo['version'] = pkgutils.getBundleVersion(pathname)
|
||||
app_data.append(iteminfo)
|
||||
except BaseException:
|
||||
pass
|
||||
else:
|
||||
# possibly a non-bundle app. Use system_profiler data
|
||||
# to get app name and version
|
||||
sp_app_data = getSPApplicationData()
|
||||
if pathname in sp_app_data:
|
||||
item = sp_app_data[pathname]
|
||||
iteminfo['bundleid'] = ''
|
||||
iteminfo['version'] = item.get('version') or '0.0.0.0.0'
|
||||
if item.get('_name'):
|
||||
iteminfo['name'] = item['_name']
|
||||
app_data.append(iteminfo)
|
||||
return app_data
|
||||
|
||||
|
||||
def get_version():
|
||||
@@ -598,7 +593,7 @@ def getIntel64Support():
|
||||
libc.sysctlbyname(
|
||||
"hw.optional.x86_64", ctypes.byref(buf), ctypes.byref(size), None, 0)
|
||||
|
||||
return (buf.value == 1)
|
||||
return buf.value == 1
|
||||
|
||||
|
||||
def getAvailableDiskSpace(volumepath='/'):
|
||||
@@ -614,7 +609,7 @@ def getAvailableDiskSpace(volumepath='/'):
|
||||
try:
|
||||
st = os.statvfs(volumepath)
|
||||
except OSError, e:
|
||||
display_error(
|
||||
display.display_error(
|
||||
'Error getting disk space in %s: %s', volumepath, str(e))
|
||||
return 0
|
||||
|
||||
@@ -628,7 +623,7 @@ def getMachineFacts():
|
||||
machine = dict()
|
||||
machine['hostname'] = os.uname()[1]
|
||||
machine['arch'] = os.uname()[4]
|
||||
machine['os_vers'] = getOsVersion(only_major_minor=False)
|
||||
machine['os_vers'] = osutils.getOsVersion(only_major_minor=False)
|
||||
hardware_info = get_hardware_info()
|
||||
machine['machine_model'] = hardware_info.get('machine_model', 'UNKNOWN')
|
||||
machine['munki_version'] = get_version()
|
||||
@@ -647,7 +642,7 @@ def validPlist(path):
|
||||
"""Uses plutil to determine if path contains a valid plist.
|
||||
Returns True or False."""
|
||||
retcode = subprocess.call(['/usr/bin/plutil', '-lint', '-s', path])
|
||||
return (retcode == 0)
|
||||
return retcode == 0
|
||||
|
||||
|
||||
@Memoize
|
||||
@@ -660,7 +655,7 @@ def getConditions():
|
||||
conditionalscriptdir = os.path.join(scriptdir, "conditions")
|
||||
# define path to ConditionalItems.plist
|
||||
conditionalitemspath = os.path.join(
|
||||
pref('ManagedInstallDir'), 'ConditionalItems.plist')
|
||||
prefs.pref('ManagedInstallDir'), 'ConditionalItems.plist')
|
||||
try:
|
||||
# delete CondtionalItems.plist so that we're starting fresh
|
||||
os.unlink(conditionalitemspath)
|
||||
@@ -668,7 +663,7 @@ def getConditions():
|
||||
pass
|
||||
if os.path.exists(conditionalscriptdir):
|
||||
from munkilib import utils
|
||||
for conditionalscript in listdir(conditionalscriptdir):
|
||||
for conditionalscript in osutils.listdir(conditionalscriptdir):
|
||||
if conditionalscript.startswith('.'):
|
||||
# skip files that start with a period
|
||||
continue
|
||||
@@ -704,7 +699,7 @@ def saveappdata():
|
||||
"""Save installed application data"""
|
||||
# data from getAppData() is meant for use by updatecheck
|
||||
# we need to massage it a bit for more general usage
|
||||
log('Saving application inventory...')
|
||||
munkilog.log('Saving application inventory...')
|
||||
app_inventory = []
|
||||
for item in getAppData():
|
||||
inventory_item = {}
|
||||
@@ -720,16 +715,11 @@ def saveappdata():
|
||||
FoundationPlist.writePlist(
|
||||
app_inventory,
|
||||
os.path.join(
|
||||
pref('ManagedInstallDir'), 'ApplicationInventory.plist'))
|
||||
prefs.pref('ManagedInstallDir'), 'ApplicationInventory.plist'))
|
||||
except FoundationPlist.NSPropertyListSerializationException, err:
|
||||
display_warning(
|
||||
display.display_warning(
|
||||
'Unable to save inventory report: %s' % err)
|
||||
|
||||
|
||||
def main():
|
||||
"""Placeholder"""
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
134
code/client/munkilib/munkicommon/munkilog.py
Executable file
134
code/client/munkilib/munkicommon/munkilog.py
Executable file
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Copyright 2009-2016 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.
|
||||
"""
|
||||
munkilog.py
|
||||
|
||||
Created by Greg Neagle on 2016-12-14.
|
||||
|
||||
|
||||
Logging functions for Munki
|
||||
"""
|
||||
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import time
|
||||
|
||||
from . import prefs
|
||||
|
||||
|
||||
def log(msg, logname=''):
|
||||
"""Generic logging function."""
|
||||
if len(msg) > 1000:
|
||||
# See http://bugs.python.org/issue11907 and RFC-3164
|
||||
# break up huge msg into chunks and send 1000 characters at a time
|
||||
msg_buffer = msg
|
||||
while msg_buffer:
|
||||
logging.info(msg_buffer[:1000])
|
||||
msg_buffer = msg_buffer[1000:]
|
||||
else:
|
||||
logging.info(msg) # noop unless configure_syslog() is called first.
|
||||
|
||||
# date/time format string
|
||||
formatstr = '%b %d %Y %H:%M:%S %z'
|
||||
if not logname:
|
||||
# use our regular logfile
|
||||
logpath = prefs.pref('LogFile')
|
||||
else:
|
||||
logpath = os.path.join(os.path.dirname(prefs.pref('LogFile')), logname)
|
||||
try:
|
||||
fileobj = open(logpath, mode='a', buffering=1)
|
||||
try:
|
||||
print >> fileobj, time.strftime(formatstr), msg.encode('UTF-8')
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
fileobj.close()
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
|
||||
|
||||
def configure_syslog():
|
||||
"""Configures logging to system.log, when pref('LogToSyslog') == True."""
|
||||
logger = logging.getLogger()
|
||||
# Remove existing handlers to avoid sending unexpected messages.
|
||||
for handler in logger.handlers:
|
||||
logger.removeHandler(handler)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# If /System/Library/LaunchDaemons/com.apple.syslogd.plist is restarted
|
||||
# then /var/run/syslog stops listening. If we fail to catch this then
|
||||
# Munki completely errors.
|
||||
try:
|
||||
syslog = logging.handlers.SysLogHandler('/var/run/syslog')
|
||||
except BaseException:
|
||||
log('LogToSyslog is enabled but socket connection failed.')
|
||||
return
|
||||
|
||||
syslog.setFormatter(logging.Formatter('munki: %(message)s'))
|
||||
syslog.setLevel(logging.INFO)
|
||||
logger.addHandler(syslog)
|
||||
|
||||
|
||||
def rotatelog(logname=''):
|
||||
"""Rotate a log"""
|
||||
if not logname:
|
||||
# use our regular logfile
|
||||
logpath = prefs.pref('LogFile')
|
||||
else:
|
||||
logpath = os.path.join(os.path.dirname(prefs.pref('LogFile')), logname)
|
||||
if os.path.exists(logpath):
|
||||
for i in range(3, -1, -1):
|
||||
try:
|
||||
os.unlink(logpath + '.' + str(i + 1))
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
try:
|
||||
os.rename(logpath + '.' + str(i), logpath + '.' + str(i + 1))
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
try:
|
||||
os.rename(logpath, logpath + '.0')
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
|
||||
|
||||
def rotate_main_log():
|
||||
"""Rotate our main log"""
|
||||
main_log = prefs.pref('LogFile')
|
||||
if os.path.exists(main_log):
|
||||
if os.path.getsize(main_log) > 1000000:
|
||||
rotatelog(main_log)
|
||||
|
||||
|
||||
def reset_warnings():
|
||||
"""Rotate our warnings log."""
|
||||
warningsfile = os.path.join(
|
||||
os.path.dirname(prefs.pref('LogFile')), 'warnings.log')
|
||||
if os.path.exists(warningsfile):
|
||||
rotatelog(warningsfile)
|
||||
|
||||
|
||||
def reset_errors():
|
||||
"""Rotate our errors.log"""
|
||||
errorsfile = os.path.join(
|
||||
os.path.dirname(prefs.pref('LogFile')), 'errors.log')
|
||||
if os.path.exists(errorsfile):
|
||||
rotatelog(errorsfile)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
@@ -35,7 +35,7 @@ import tempfile
|
||||
from SystemConfiguration import SCDynamicStoreCopyConsoleUser
|
||||
# pylint: enable=E0611
|
||||
|
||||
from .output import display_warning
|
||||
from . import display
|
||||
|
||||
# we use lots of camelCase-style names. Deal with it.
|
||||
# pylint: disable=C0103
|
||||
@@ -72,7 +72,7 @@ def cleanUpTmpDir():
|
||||
try:
|
||||
shutil.rmtree(_TMPDIR)
|
||||
except (OSError, IOError), err:
|
||||
display_warning(
|
||||
display.display_warning(
|
||||
'Unable to clean up temporary dir %s: %s', _TMPDIR, str(err))
|
||||
_TMPDIR = None
|
||||
|
||||
@@ -183,10 +183,5 @@ def osascript(osastring):
|
||||
_TMPDIR = None
|
||||
|
||||
|
||||
def main():
|
||||
"""Placeholder"""
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
@@ -1,475 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Copyright 2009-2016 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.
|
||||
"""
|
||||
output.py
|
||||
|
||||
Created by Greg Neagle on 2016-12-13.
|
||||
|
||||
Common output, logging, and reporting functions
|
||||
"""
|
||||
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
|
||||
# PyLint cannot properly find names inside Cocoa libraries, so issues bogus
|
||||
# No name 'Foo' in module 'Bar' warnings. Disable them.
|
||||
# pylint: disable=E0611
|
||||
from Foundation import NSDate
|
||||
# pylint: enable=E0611
|
||||
|
||||
from .. import munkistatus
|
||||
from .prefs import pref
|
||||
from .. import FoundationPlist
|
||||
|
||||
|
||||
# output functions
|
||||
|
||||
def getsteps(num_of_steps, limit):
|
||||
"""
|
||||
Helper function for display_percent_done
|
||||
"""
|
||||
steps = []
|
||||
current = 0.0
|
||||
for i in range(0, num_of_steps):
|
||||
if i == num_of_steps-1:
|
||||
steps.append(int(round(limit)))
|
||||
else:
|
||||
steps.append(int(round(current)))
|
||||
current += float(limit)/float(num_of_steps-1)
|
||||
return steps
|
||||
|
||||
|
||||
def display_percent_done(current, maximum):
|
||||
"""
|
||||
Mimics the command-line progress meter seen in some
|
||||
of Apple's tools (like softwareupdate), or tells
|
||||
MunkiStatus to display percent done via progress bar.
|
||||
"""
|
||||
if munkistatusoutput:
|
||||
step = getsteps(21, maximum)
|
||||
if current in step:
|
||||
if current == maximum:
|
||||
percentdone = 100
|
||||
else:
|
||||
percentdone = int(float(current)/float(maximum)*100)
|
||||
munkistatus.percent(str(percentdone))
|
||||
elif verbose > 0:
|
||||
step = getsteps(16, maximum)
|
||||
output = ''
|
||||
indicator = ['\t0', '.', '.', '20', '.', '.', '40', '.', '.',
|
||||
'60', '.', '.', '80', '.', '.', '100\n']
|
||||
for i in range(0, 16):
|
||||
if current >= step[i]:
|
||||
output += indicator[i]
|
||||
if output:
|
||||
sys.stdout.write('\r' + output)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def str_to_ascii(a_string):
|
||||
"""Given str (unicode, latin-1, or not) return ascii.
|
||||
|
||||
Args:
|
||||
s: str, likely in Unicode-16BE, UTF-8, or Latin-1 charset
|
||||
Returns:
|
||||
str, ascii form, no >7bit chars
|
||||
"""
|
||||
try:
|
||||
return unicode(a_string).encode('ascii', 'ignore')
|
||||
except UnicodeDecodeError:
|
||||
return a_string.decode('ascii', 'ignore')
|
||||
|
||||
|
||||
def to_unicode(obj, encoding='UTF-8'):
|
||||
"""Coerces basestring obj to unicode"""
|
||||
if isinstance(obj, basestring):
|
||||
if not isinstance(obj, unicode):
|
||||
obj = unicode(obj, encoding)
|
||||
return obj
|
||||
|
||||
|
||||
def concat_log_message(msg, *args):
|
||||
"""Concatenates a string with any additional arguments,
|
||||
making sure everything is unicode"""
|
||||
# coerce msg to unicode if it's not already
|
||||
msg = to_unicode(msg)
|
||||
if args:
|
||||
# coerce all args to unicode as well
|
||||
args = [to_unicode(arg) for arg in args]
|
||||
try:
|
||||
msg = msg % tuple(args)
|
||||
except TypeError, dummy_err:
|
||||
warnings.warn(
|
||||
'String format does not match concat args: %s'
|
||||
% (str(sys.exc_info())))
|
||||
return msg.rstrip()
|
||||
|
||||
|
||||
def display_status_major(msg, *args):
|
||||
"""
|
||||
Displays major status messages, formatting as needed
|
||||
for verbose/non-verbose and munkistatus-style output.
|
||||
"""
|
||||
msg = concat_log_message(msg, *args)
|
||||
log(msg)
|
||||
if munkistatusoutput:
|
||||
munkistatus.message(msg)
|
||||
munkistatus.detail('')
|
||||
munkistatus.percent(-1)
|
||||
elif verbose > 0:
|
||||
if msg.endswith('.') or msg.endswith(u'…'):
|
||||
print '%s' % msg.encode('UTF-8')
|
||||
else:
|
||||
print '%s...' % msg.encode('UTF-8')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def display_status_minor(msg, *args):
|
||||
"""
|
||||
Displays minor status messages, formatting as needed
|
||||
for verbose/non-verbose and munkistatus-style output.
|
||||
"""
|
||||
msg = concat_log_message(msg, *args)
|
||||
log(u' ' + msg)
|
||||
if munkistatusoutput:
|
||||
munkistatus.detail(msg)
|
||||
elif verbose > 0:
|
||||
if msg.endswith('.') or msg.endswith(u'…'):
|
||||
print ' %s' % msg.encode('UTF-8')
|
||||
else:
|
||||
print ' %s...' % msg.encode('UTF-8')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def display_info(msg, *args):
|
||||
"""
|
||||
Displays info messages.
|
||||
Not displayed in MunkiStatus.
|
||||
"""
|
||||
msg = concat_log_message(msg, *args)
|
||||
log(u' ' + msg)
|
||||
if munkistatusoutput:
|
||||
pass
|
||||
elif verbose > 0:
|
||||
print ' %s' % msg.encode('UTF-8')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def display_detail(msg, *args):
|
||||
"""
|
||||
Displays minor info messages.
|
||||
Not displayed in MunkiStatus.
|
||||
These are usually logged only, but can be printed to
|
||||
stdout if verbose is set greater than 1
|
||||
"""
|
||||
msg = concat_log_message(msg, *args)
|
||||
if munkistatusoutput:
|
||||
pass
|
||||
elif verbose > 1:
|
||||
print ' %s' % msg.encode('UTF-8')
|
||||
sys.stdout.flush()
|
||||
if pref('LoggingLevel') > 0:
|
||||
log(u' ' + msg)
|
||||
|
||||
|
||||
def display_debug1(msg, *args):
|
||||
"""
|
||||
Displays debug messages, formatting as needed
|
||||
for verbose/non-verbose and munkistatus-style output.
|
||||
"""
|
||||
msg = concat_log_message(msg, *args)
|
||||
if munkistatusoutput:
|
||||
pass
|
||||
elif verbose > 2:
|
||||
print ' %s' % msg.encode('UTF-8')
|
||||
sys.stdout.flush()
|
||||
if pref('LoggingLevel') > 1:
|
||||
log('DEBUG1: %s' % msg)
|
||||
|
||||
|
||||
def display_debug2(msg, *args):
|
||||
"""
|
||||
Displays debug messages, formatting as needed
|
||||
for verbose/non-verbose and munkistatus-style output.
|
||||
"""
|
||||
msg = concat_log_message(msg, *args)
|
||||
if munkistatusoutput:
|
||||
pass
|
||||
elif verbose > 3:
|
||||
print ' %s' % msg.encode('UTF-8')
|
||||
if pref('LoggingLevel') > 2:
|
||||
log('DEBUG2: %s' % msg)
|
||||
|
||||
|
||||
def reset_warnings():
|
||||
"""Rotate our warnings log."""
|
||||
warningsfile = os.path.join(os.path.dirname(pref('LogFile')),
|
||||
'warnings.log')
|
||||
if os.path.exists(warningsfile):
|
||||
rotatelog(warningsfile)
|
||||
|
||||
|
||||
def display_warning(msg, *args):
|
||||
"""
|
||||
Prints warning msgs to stderr and the log
|
||||
"""
|
||||
msg = concat_log_message(msg, *args)
|
||||
warning = 'WARNING: %s' % msg
|
||||
if verbose > 0:
|
||||
print >> sys.stderr, warning.encode('UTF-8')
|
||||
log(warning)
|
||||
# append this warning to our warnings log
|
||||
log(warning, 'warnings.log')
|
||||
# collect the warning for later reporting
|
||||
if not 'Warnings' in report:
|
||||
report['Warnings'] = []
|
||||
report['Warnings'].append('%s' % msg)
|
||||
|
||||
|
||||
def reset_errors():
|
||||
"""Rotate our errors.log"""
|
||||
errorsfile = os.path.join(os.path.dirname(pref('LogFile')), 'errors.log')
|
||||
if os.path.exists(errorsfile):
|
||||
rotatelog(errorsfile)
|
||||
|
||||
|
||||
def display_error(msg, *args):
|
||||
"""
|
||||
Prints msg to stderr and the log
|
||||
"""
|
||||
msg = concat_log_message(msg, *args)
|
||||
errmsg = 'ERROR: %s' % msg
|
||||
if verbose > 0:
|
||||
print >> sys.stderr, errmsg.encode('UTF-8')
|
||||
log(errmsg)
|
||||
# append this error to our errors log
|
||||
log(errmsg, 'errors.log')
|
||||
# collect the errors for later reporting
|
||||
if not 'Errors' in report:
|
||||
report['Errors'] = []
|
||||
report['Errors'].append('%s' % msg)
|
||||
|
||||
# logging functions
|
||||
|
||||
def format_time(timestamp=None):
|
||||
"""Return timestamp as an ISO 8601 formatted string, in the current
|
||||
timezone.
|
||||
If timestamp isn't given the current time is used."""
|
||||
if timestamp is None:
|
||||
return str(NSDate.new())
|
||||
else:
|
||||
return str(NSDate.dateWithTimeIntervalSince1970_(timestamp))
|
||||
|
||||
|
||||
def validateDateFormat(datetime_string):
|
||||
"""Returns a formatted date/time string"""
|
||||
formatted_datetime_string = ''
|
||||
try:
|
||||
formatted_datetime_string = time.strftime(
|
||||
'%Y-%m-%dT%H:%M:%SZ', time.strptime(datetime_string,
|
||||
'%Y-%m-%dT%H:%M:%SZ'))
|
||||
except BaseException:
|
||||
pass
|
||||
return formatted_datetime_string
|
||||
|
||||
|
||||
def log(msg, logname=''):
|
||||
"""Generic logging function."""
|
||||
if len(msg) > 1000:
|
||||
# See http://bugs.python.org/issue11907 and RFC-3164
|
||||
# break up huge msg into chunks and send 1000 characters at a time
|
||||
msg_buffer = msg
|
||||
while msg_buffer:
|
||||
logging.info(msg_buffer[:1000])
|
||||
msg_buffer = msg_buffer[1000:]
|
||||
else:
|
||||
logging.info(msg) # noop unless configure_syslog() is called first.
|
||||
|
||||
# date/time format string
|
||||
formatstr = '%b %d %Y %H:%M:%S %z'
|
||||
if not logname:
|
||||
# use our regular logfile
|
||||
logpath = pref('LogFile')
|
||||
else:
|
||||
logpath = os.path.join(os.path.dirname(pref('LogFile')), logname)
|
||||
try:
|
||||
fileobj = open(logpath, mode='a', buffering=1)
|
||||
try:
|
||||
print >> fileobj, time.strftime(formatstr), msg.encode('UTF-8')
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
fileobj.close()
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
|
||||
|
||||
def configure_syslog():
|
||||
"""Configures logging to system.log, when pref('LogToSyslog') == True."""
|
||||
logger = logging.getLogger()
|
||||
# Remove existing handlers to avoid sending unexpected messages.
|
||||
for handler in logger.handlers:
|
||||
logger.removeHandler(handler)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
# If /System/Library/LaunchDaemons/com.apple.syslogd.plist is restarted
|
||||
# then /var/run/syslog stops listening. If we fail to catch this then
|
||||
# Munki completely errors.
|
||||
try:
|
||||
syslog = logging.handlers.SysLogHandler('/var/run/syslog')
|
||||
except:
|
||||
log('LogToSyslog is enabled but socket connection failed.')
|
||||
return
|
||||
|
||||
syslog.setFormatter(logging.Formatter('munki: %(message)s'))
|
||||
syslog.setLevel(logging.INFO)
|
||||
logger.addHandler(syslog)
|
||||
|
||||
|
||||
def rotatelog(logname=''):
|
||||
"""Rotate a log"""
|
||||
if not logname:
|
||||
# use our regular logfile
|
||||
logpath = pref('LogFile')
|
||||
else:
|
||||
logpath = os.path.join(os.path.dirname(pref('LogFile')), logname)
|
||||
if os.path.exists(logpath):
|
||||
for i in range(3, -1, -1):
|
||||
try:
|
||||
os.unlink(logpath + '.' + str(i + 1))
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
try:
|
||||
os.rename(logpath + '.' + str(i), logpath + '.' + str(i + 1))
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
try:
|
||||
os.rename(logpath, logpath + '.0')
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
|
||||
|
||||
def rotate_main_log():
|
||||
"""Rotate our main log"""
|
||||
if os.path.exists(pref('LogFile')):
|
||||
if os.path.getsize(pref('LogFile')) > 1000000:
|
||||
rotatelog(pref('LogFile'))
|
||||
|
||||
# reporting functions
|
||||
|
||||
def printreportitem(label, value, indent=0):
|
||||
"""Prints a report item in an 'attractive' way"""
|
||||
indentspace = ' '
|
||||
if type(value) == type(None):
|
||||
print indentspace*indent, '%s: !NONE!' % label
|
||||
elif type(value) == list or type(value).__name__ == 'NSCFArray':
|
||||
if label:
|
||||
print indentspace*indent, '%s:' % label
|
||||
index = 0
|
||||
for item in value:
|
||||
index += 1
|
||||
printreportitem(index, item, indent+1)
|
||||
elif type(value) == dict or type(value).__name__ == 'NSCFDictionary':
|
||||
if label:
|
||||
print indentspace*indent, '%s:' % label
|
||||
for subkey in value.keys():
|
||||
printreportitem(subkey, value[subkey], indent+1)
|
||||
else:
|
||||
print indentspace*indent, '%s: %s' % (label, value)
|
||||
|
||||
|
||||
def printreport(reportdict):
|
||||
"""Prints the report dictionary in a pretty(?) way"""
|
||||
for key in reportdict.keys():
|
||||
printreportitem(key, reportdict[key])
|
||||
|
||||
|
||||
def savereport():
|
||||
"""Save our report"""
|
||||
FoundationPlist.writePlist(
|
||||
report, os.path.join(pref('ManagedInstallDir'),
|
||||
'ManagedInstallReport.plist'))
|
||||
|
||||
|
||||
def readreport():
|
||||
"""Read report data from file"""
|
||||
global report
|
||||
reportfile = os.path.join(pref('ManagedInstallDir'),
|
||||
'ManagedInstallReport.plist')
|
||||
try:
|
||||
report = FoundationPlist.readPlist(reportfile)
|
||||
except FoundationPlist.NSPropertyListSerializationException:
|
||||
report = {}
|
||||
|
||||
|
||||
def archive_report():
|
||||
"""Archive a report"""
|
||||
reportfile = os.path.join(pref('ManagedInstallDir'),
|
||||
'ManagedInstallReport.plist')
|
||||
if os.path.exists(reportfile):
|
||||
modtime = os.stat(reportfile).st_mtime
|
||||
formatstr = '%Y-%m-%d-%H%M%S'
|
||||
archivename = ('ManagedInstallReport-%s.plist'
|
||||
% time.strftime(formatstr, time.localtime(modtime)))
|
||||
archivepath = os.path.join(pref('ManagedInstallDir'), 'Archives')
|
||||
if not os.path.exists(archivepath):
|
||||
try:
|
||||
os.mkdir(archivepath)
|
||||
except (OSError, IOError):
|
||||
display_warning('Could not create report archive path.')
|
||||
try:
|
||||
os.rename(reportfile, os.path.join(archivepath, archivename))
|
||||
except (OSError, IOError):
|
||||
display_warning('Could not archive report.')
|
||||
# now keep number of archived reports to 100 or fewer
|
||||
proc = subprocess.Popen(['/bin/ls', '-t1', archivepath],
|
||||
bufsize=1, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(output, dummy_err) = proc.communicate()
|
||||
if output:
|
||||
archiveitems = [item
|
||||
for item in str(output).splitlines()
|
||||
if item.startswith('ManagedInstallReport-')]
|
||||
if len(archiveitems) > 100:
|
||||
for item in archiveitems[100:]:
|
||||
itempath = os.path.join(archivepath, item)
|
||||
if os.path.isfile(itempath):
|
||||
try:
|
||||
os.unlink(itempath)
|
||||
except (OSError, IOError):
|
||||
display_warning(
|
||||
'Could not remove archive item %s', itempath)
|
||||
|
||||
|
||||
# module globals
|
||||
#debug = False
|
||||
verbose = 1
|
||||
munkistatusoutput = False
|
||||
report = {}
|
||||
|
||||
|
||||
def main():
|
||||
"""Placeholder"""
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -33,8 +33,8 @@ from distutils import version
|
||||
from types import StringType
|
||||
from xml.dom import minidom
|
||||
|
||||
from .osutils import listdir, tmpdir
|
||||
from .output import display_debug2, display_error, display_warning
|
||||
from . import osutils
|
||||
from . import display
|
||||
from .. import FoundationPlist
|
||||
|
||||
# we use lots of camelCase-style names. Deal with it.
|
||||
@@ -56,8 +56,7 @@ def getPkgRestartInfo(filename):
|
||||
stderr=subprocess.PIPE)
|
||||
(out, err) = proc.communicate()
|
||||
if proc.returncode:
|
||||
display_error("installer -query failed: %s %s" %
|
||||
(out.decode('UTF-8'), err.decode('UTF-8')))
|
||||
display.display_error("installer -query failed: %s %s", out, err)
|
||||
return {}
|
||||
|
||||
if out:
|
||||
@@ -210,7 +209,7 @@ def getBundleVersion(bundlepath, key=None):
|
||||
infopath = os.path.join(
|
||||
bundlepath, 'Contents', 'Resources', 'English.lproj')
|
||||
if os.path.exists(infopath):
|
||||
for item in listdir(infopath):
|
||||
for item in osutils.osutils.listdir(infopath):
|
||||
if os.path.join(infopath, item).endswith('.info'):
|
||||
infofile = os.path.join(infopath, item)
|
||||
fileobj = open(infofile, mode='r')
|
||||
@@ -316,7 +315,7 @@ def getFlatPackageInfo(pkgpath):
|
||||
# get the absolute path to the pkg because we need to do a chdir later
|
||||
abspkgpath = os.path.abspath(pkgpath)
|
||||
# make a tmp dir to expand the flat package into
|
||||
pkgtmp = tempfile.mkdtemp(dir=tmpdir())
|
||||
pkgtmp = tempfile.mkdtemp(dir=osutils.tmpdir())
|
||||
# record our current working dir
|
||||
cwd = os.getcwd()
|
||||
# change into our tmpdir so we can use xar to unarchive the flat package
|
||||
@@ -340,8 +339,9 @@ def getFlatPackageInfo(pkgpath):
|
||||
infoarray = parsePkgRefs(packageinfoabspath)
|
||||
break
|
||||
else:
|
||||
display_warning("An error occurred while extracting %s: %s"
|
||||
% (toc_entry, err))
|
||||
display.display_warning(
|
||||
"An error occurred while extracting %s: %s",
|
||||
toc_entry, err)
|
||||
# If there are PackageInfo files elsewhere, gather them up
|
||||
elif toc_entry.endswith('.pkg/PackageInfo'):
|
||||
cmd_extract = ['/usr/bin/xar', '-xf', abspkgpath, toc_entry]
|
||||
@@ -351,8 +351,9 @@ def getFlatPackageInfo(pkgpath):
|
||||
os.path.join(pkgtmp, toc_entry))
|
||||
infoarray.extend(parsePkgRefs(packageinfoabspath))
|
||||
else:
|
||||
display_warning("An error occurred while extracting %s: %s"
|
||||
% (toc_entry, err))
|
||||
display.display_warning(
|
||||
"An error occurred while extracting %s: %s",
|
||||
toc_entry, err)
|
||||
if len(infoarray) == 0:
|
||||
for toc_entry in [item for item in toc
|
||||
if item.startswith('Distribution')]:
|
||||
@@ -366,13 +367,15 @@ def getFlatPackageInfo(pkgpath):
|
||||
path_to_pkg=pkgpath)
|
||||
break
|
||||
else:
|
||||
display_warning("An error occurred while extracting %s: %s"
|
||||
% (toc_entry, err))
|
||||
display.display_warning(
|
||||
"An error occurred while extracting %s: %s",
|
||||
toc_entry, err)
|
||||
|
||||
if len(infoarray) == 0:
|
||||
display_warning('No valid Distribution or PackageInfo found.')
|
||||
display.display_warning(
|
||||
'No valid Distribution or PackageInfo found.')
|
||||
else:
|
||||
display_warning(err)
|
||||
display.display_warning(err)
|
||||
|
||||
# change back to original working dir
|
||||
os.chdir(cwd)
|
||||
@@ -384,12 +387,12 @@ def getBomList(pkgpath):
|
||||
'''Gets bom listing from pkgpath, which should be a path
|
||||
to a bundle-style package'''
|
||||
bompath = None
|
||||
for item in listdir(os.path.join(pkgpath, 'Contents')):
|
||||
for item in osutils.listdir(os.path.join(pkgpath, 'Contents')):
|
||||
if item.endswith('.bom'):
|
||||
bompath = os.path.join(pkgpath, 'Contents', item)
|
||||
break
|
||||
if not bompath:
|
||||
for item in listdir(os.path.join(pkgpath, 'Contents', 'Resources')):
|
||||
for item in osutils.listdir(os.path.join(pkgpath, 'Contents', 'Resources')):
|
||||
if item.endswith('.bom'):
|
||||
bompath = os.path.join(pkgpath, 'Contents', 'Resources', item)
|
||||
break
|
||||
@@ -443,7 +446,7 @@ def getOnePackageInfo(pkgpath):
|
||||
infopath = os.path.join(
|
||||
pkgpath, 'Contents', 'Resources', 'English.lproj')
|
||||
if os.path.exists(infopath):
|
||||
for item in listdir(infopath):
|
||||
for item in osutils.listdir(infopath):
|
||||
if os.path.join(infopath, item).endswith('.info'):
|
||||
pkginfo['filename'] = os.path.basename(pkgpath)
|
||||
pkginfo['packageid'] = os.path.basename(pkgpath)
|
||||
@@ -478,7 +481,7 @@ def getBundlePackageInfo(pkgpath):
|
||||
|
||||
bundlecontents = os.path.join(pkgpath, 'Contents')
|
||||
if os.path.exists(bundlecontents):
|
||||
for item in listdir(bundlecontents):
|
||||
for item in osutils.listdir(bundlecontents):
|
||||
if item.endswith('.dist'):
|
||||
filename = os.path.join(bundlecontents, item)
|
||||
# return info using the distribution file
|
||||
@@ -500,7 +503,7 @@ def getBundlePackageInfo(pkgpath):
|
||||
for subdir in dirsToSearch:
|
||||
searchdir = os.path.join(pkgpath, subdir)
|
||||
if os.path.exists(searchdir):
|
||||
for item in listdir(searchdir):
|
||||
for item in osutils.listdir(searchdir):
|
||||
itempath = os.path.join(searchdir, item)
|
||||
if os.path.isdir(itempath):
|
||||
if itempath.endswith('.pkg'):
|
||||
@@ -519,7 +522,7 @@ def getReceiptInfo(pkgname):
|
||||
"""Get receipt info from a package"""
|
||||
info = []
|
||||
if hasValidPackageExt(pkgname):
|
||||
display_debug2('Examining %s' % pkgname)
|
||||
display.display_debug2('Examining %s' % pkgname)
|
||||
if os.path.isfile(pkgname): # new flat package
|
||||
info = getFlatPackageInfo(pkgname)
|
||||
|
||||
@@ -557,15 +560,15 @@ def getInstalledPackageVersion(pkgid):
|
||||
foundbundleid = plist.get('pkgid')
|
||||
foundvers = plist.get('pkg-version', '0.0.0.0.0')
|
||||
if pkgid == foundbundleid:
|
||||
display_debug2('\tThis machine has %s, version %s',
|
||||
pkgid, foundvers)
|
||||
display.display_debug2('\tThis machine has %s, version %s',
|
||||
pkgid, foundvers)
|
||||
return foundvers
|
||||
|
||||
# If we got to this point, we haven't found the pkgid yet.
|
||||
# Check /Library/Receipts
|
||||
receiptsdir = '/Library/Receipts'
|
||||
if os.path.exists(receiptsdir):
|
||||
installitems = listdir(receiptsdir)
|
||||
installitems = osutils.listdir(receiptsdir)
|
||||
highestversion = '0'
|
||||
for item in installitems:
|
||||
if item.endswith('.pkg'):
|
||||
@@ -580,13 +583,13 @@ def getInstalledPackageVersion(pkgid):
|
||||
highestversion = foundvers
|
||||
|
||||
if highestversion != '0':
|
||||
display_debug2('\tThis machine has %s, version %s',
|
||||
pkgid, highestversion)
|
||||
display.display_debug2('\tThis machine has %s, version %s',
|
||||
pkgid, highestversion)
|
||||
return highestversion
|
||||
|
||||
|
||||
# This package does not appear to be currently installed
|
||||
display_debug2('\tThis machine does not have %s' % pkgid)
|
||||
display.display_debug2('\tThis machine does not have %s' % pkgid)
|
||||
return ""
|
||||
|
||||
|
||||
@@ -756,11 +759,35 @@ def getPackageMetaData(pkgitem):
|
||||
|
||||
return cataloginfo
|
||||
|
||||
|
||||
def main():
|
||||
"""Placeholder"""
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
# This function doesn't really have anything to do with packages or receipts
|
||||
# but is used by makepkginfo, munkiimport, and installer.py, so it might as
|
||||
# well live here for now
|
||||
def isApplication(pathname):
|
||||
"""Returns true if path appears to be an OS X application"""
|
||||
# No symlinks, please
|
||||
if os.path.islink(pathname):
|
||||
return False
|
||||
if pathname.endswith('.app'):
|
||||
return True
|
||||
if os.path.isdir(pathname):
|
||||
# look for app bundle structure
|
||||
# use Info.plist to determine the name of the executable
|
||||
infoplist = os.path.join(pathname, 'Contents', 'Info.plist')
|
||||
if os.path.exists(infoplist):
|
||||
plist = FoundationPlist.readPlist(infoplist)
|
||||
if 'CFBundlePackageType' in plist:
|
||||
if plist['CFBundlePackageType'] != 'APPL':
|
||||
return False
|
||||
# get CFBundleExecutable,
|
||||
# falling back to bundle name if it's missing
|
||||
bundleexecutable = plist.get(
|
||||
'CFBundleExecutable', os.path.basename(pathname))
|
||||
bundleexecutablepath = os.path.join(
|
||||
pathname, 'Contents', 'MacOS', bundleexecutable)
|
||||
if os.path.exists(bundleexecutablepath):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
7
code/client/munkilib/munkicommon/prefs.py
Executable file → Normal file
7
code/client/munkilib/munkicommon/prefs.py
Executable file → Normal file
@@ -190,10 +190,5 @@ def pref(pref_name):
|
||||
return pref_value
|
||||
|
||||
|
||||
def main():
|
||||
"""Placeholder"""
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
@@ -23,9 +23,12 @@ Created by Greg Neagle on 2016-12-14.
|
||||
Functions for finding, listing, etc processes
|
||||
"""
|
||||
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
|
||||
from .output import display_debug1, display_detail
|
||||
from .constants import LOGINWINDOW
|
||||
from . import display
|
||||
|
||||
# we use lots of camelCase-style names. Deal with it.
|
||||
# pylint: disable=C0103
|
||||
@@ -64,7 +67,7 @@ def getRunningProcesses():
|
||||
def isAppRunning(appname):
|
||||
"""Tries to determine if the application in appname is currently
|
||||
running"""
|
||||
display_detail('Checking if %s is running...' % appname)
|
||||
display.display_detail('Checking if %s is running...' % appname)
|
||||
proc_list = getRunningProcesses()
|
||||
matching_items = []
|
||||
if appname.startswith('/'):
|
||||
@@ -86,14 +89,39 @@ def isAppRunning(appname):
|
||||
|
||||
if matching_items:
|
||||
# it's running!
|
||||
display_debug1('Matching process list: %s' % matching_items)
|
||||
display_detail('%s is running!' % appname)
|
||||
display.display_debug1('Matching process list: %s' % matching_items)
|
||||
display.display_detail('%s is running!' % appname)
|
||||
return True
|
||||
|
||||
# if we get here, we have no evidence that appname is running
|
||||
return False
|
||||
|
||||
|
||||
def blockingApplicationsRunning(pkginfoitem):
|
||||
"""Returns true if any application in the blocking_applications list
|
||||
is running or, if there is no blocking_applications list, if any
|
||||
application in the installs list is running."""
|
||||
|
||||
if 'blocking_applications' in pkginfoitem:
|
||||
appnames = pkginfoitem['blocking_applications']
|
||||
else:
|
||||
# if no blocking_applications specified, get appnames
|
||||
# from 'installs' list if it exists
|
||||
appnames = [os.path.basename(item.get('path'))
|
||||
for item in pkginfoitem.get('installs', [])
|
||||
if item['type'] == 'application']
|
||||
|
||||
display.display_debug1("Checking for %s" % appnames)
|
||||
running_apps = [appname for appname in appnames
|
||||
if isAppRunning(appname)]
|
||||
if running_apps:
|
||||
display.display_detail(
|
||||
"Blocking apps for %s are running:" % pkginfoitem['name'])
|
||||
display.display_detail(" %s" % running_apps)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def findProcesses(user=None, exe=None):
|
||||
"""Find processes in process list.
|
||||
|
||||
@@ -111,7 +139,8 @@ def findProcesses(user=None, exe=None):
|
||||
list of pids, or {} if none
|
||||
"""
|
||||
argv = ['/bin/ps', '-x', '-w', '-w', '-a', '-o', 'pid=,user=,comm=']
|
||||
ps_proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
ps_proc = subprocess.Popen(
|
||||
argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(stdout, dummy_stderr) = ps_proc.communicate()
|
||||
|
||||
pids = {}
|
||||
@@ -141,10 +170,32 @@ def findProcesses(user=None, exe=None):
|
||||
return pids
|
||||
|
||||
|
||||
def main():
|
||||
"""Placeholder"""
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
def forceLogoutNow():
|
||||
"""Force the logout of interactive GUI users and spawn MSU."""
|
||||
try:
|
||||
procs = findProcesses(exe=LOGINWINDOW)
|
||||
users = {}
|
||||
for pid in procs:
|
||||
users[procs[pid]['user']] = pid
|
||||
|
||||
if 'root' in users:
|
||||
del users['root']
|
||||
|
||||
# force MSU GUI to raise
|
||||
fileref = open('/private/tmp/com.googlecode.munki.installatlogout', 'w')
|
||||
fileref.close()
|
||||
|
||||
# kill loginwindows to cause logout of current users, whether
|
||||
# active or switched away via fast user switching.
|
||||
for user in users:
|
||||
try:
|
||||
os.kill(users[user], signal.SIGKILL)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
except BaseException, err:
|
||||
display.display_error('Exception in forceLogoutNow(): %s' % str(err))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
|
||||
150
code/client/munkilib/munkicommon/reports.py
Executable file
150
code/client/munkilib/munkicommon/reports.py
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Copyright 2009-2016 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.
|
||||
"""
|
||||
reports.py
|
||||
|
||||
Created by Greg Neagle on 2016-12-14.
|
||||
|
||||
|
||||
Reporting functions
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
# PyLint cannot properly find names inside Cocoa libraries, so issues bogus
|
||||
# No name 'Foo' in module 'Bar' warnings. Disable them.
|
||||
# pylint: disable=E0611
|
||||
from Foundation import NSDate
|
||||
# pylint: enable=E0611
|
||||
|
||||
from . import munkilog
|
||||
from . import prefs
|
||||
from .. import FoundationPlist
|
||||
|
||||
|
||||
def format_time(timestamp=None):
|
||||
"""Return timestamp as an ISO 8601 formatted string, in the current
|
||||
timezone.
|
||||
If timestamp isn't given the current time is used."""
|
||||
if timestamp is None:
|
||||
return str(NSDate.new())
|
||||
else:
|
||||
return str(NSDate.dateWithTimeIntervalSince1970_(timestamp))
|
||||
|
||||
|
||||
def printreportitem(label, value, indent=0):
|
||||
"""Prints a report item in an 'attractive' way"""
|
||||
indentspace = ' '
|
||||
if type(value) == type(None):
|
||||
print indentspace*indent, '%s: !NONE!' % label
|
||||
elif type(value) == list or type(value).__name__ == 'NSCFArray':
|
||||
if label:
|
||||
print indentspace*indent, '%s:' % label
|
||||
index = 0
|
||||
for item in value:
|
||||
index += 1
|
||||
printreportitem(index, item, indent+1)
|
||||
elif type(value) == dict or type(value).__name__ == 'NSCFDictionary':
|
||||
if label:
|
||||
print indentspace*indent, '%s:' % label
|
||||
for subkey in value.keys():
|
||||
printreportitem(subkey, value[subkey], indent+1)
|
||||
else:
|
||||
print indentspace*indent, '%s: %s' % (label, value)
|
||||
|
||||
|
||||
def printreport(reportdict):
|
||||
"""Prints the report dictionary in a pretty(?) way"""
|
||||
for key in reportdict.keys():
|
||||
printreportitem(key, reportdict[key])
|
||||
|
||||
|
||||
def savereport():
|
||||
"""Save our report"""
|
||||
FoundationPlist.writePlist(
|
||||
report, os.path.join(prefs.pref('ManagedInstallDir'),
|
||||
'ManagedInstallReport.plist'))
|
||||
|
||||
|
||||
def readreport():
|
||||
"""Read report data from file"""
|
||||
global report
|
||||
reportfile = os.path.join(prefs.pref('ManagedInstallDir'),
|
||||
'ManagedInstallReport.plist')
|
||||
try:
|
||||
report = FoundationPlist.readPlist(reportfile)
|
||||
except FoundationPlist.NSPropertyListSerializationException:
|
||||
report = {}
|
||||
|
||||
|
||||
def _warn(msg):
|
||||
"""We can't use display module functions here because that would require
|
||||
circular imports. So a partial reimplementation."""
|
||||
warning = 'WARNING: %s' % msg
|
||||
print >> sys.stderr, warning.encode('UTF-8')
|
||||
munkilog.log(warning)
|
||||
# append this warning to our warnings log
|
||||
munkilog.log(warning, 'warnings.log')
|
||||
|
||||
|
||||
def archive_report():
|
||||
"""Archive a report"""
|
||||
reportfile = os.path.join(prefs.pref('ManagedInstallDir'),
|
||||
'ManagedInstallReport.plist')
|
||||
if os.path.exists(reportfile):
|
||||
modtime = os.stat(reportfile).st_mtime
|
||||
formatstr = '%Y-%m-%d-%H%M%S'
|
||||
archivename = ('ManagedInstallReport-%s.plist'
|
||||
% time.strftime(formatstr, time.localtime(modtime)))
|
||||
archivepath = os.path.join(prefs.pref('ManagedInstallDir'), 'Archives')
|
||||
if not os.path.exists(archivepath):
|
||||
try:
|
||||
os.mkdir(archivepath)
|
||||
except (OSError, IOError):
|
||||
_warn('Could not create report archive path.')
|
||||
try:
|
||||
os.rename(reportfile, os.path.join(archivepath, archivename))
|
||||
except (OSError, IOError):
|
||||
_warn('Could not archive report.')
|
||||
# now keep number of archived reports to 100 or fewer
|
||||
proc = subprocess.Popen(['/bin/ls', '-t1', archivepath],
|
||||
bufsize=1, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(output, dummy_err) = proc.communicate()
|
||||
if output:
|
||||
archiveitems = [item
|
||||
for item in str(output).splitlines()
|
||||
if item.startswith('ManagedInstallReport-')]
|
||||
if len(archiveitems) > 100:
|
||||
for item in archiveitems[100:]:
|
||||
itempath = os.path.join(archivepath, item)
|
||||
if os.path.isfile(itempath):
|
||||
try:
|
||||
os.unlink(itempath)
|
||||
except (OSError, IOError):
|
||||
_warn('Could not remove archive item %s' % item)
|
||||
|
||||
|
||||
# module globals
|
||||
report = {}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
137
code/client/munkilib/munkicommon/scriptutils.py
Executable file
137
code/client/munkilib/munkicommon/scriptutils.py
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Copyright 2009-2016 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.
|
||||
"""
|
||||
scriptutils.py
|
||||
|
||||
Created by Greg Neagle on 2016-12-14.
|
||||
|
||||
|
||||
Functions to run scripts inside Munki
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from . import osutils
|
||||
from . import output
|
||||
from .. import munkistatus
|
||||
|
||||
|
||||
# we use lots of camelCase-style names. Deal with it.
|
||||
# pylint: disable=C0103
|
||||
|
||||
|
||||
def _writefile(stringdata, path):
|
||||
'''Writes string data to path.
|
||||
Returns the path on success, empty string on failure.'''
|
||||
try:
|
||||
fileobject = open(path, mode='w', buffering=1)
|
||||
# write line-by-line to ensure proper UNIX line-endings
|
||||
for line in stringdata.splitlines():
|
||||
print >> fileobject, line.encode('UTF-8')
|
||||
fileobject.close()
|
||||
return path
|
||||
except (OSError, IOError):
|
||||
output.display_error("Couldn't write %s" % stringdata)
|
||||
return ""
|
||||
|
||||
|
||||
def runEmbeddedScript(scriptname, pkginfo_item, suppress_error=False):
|
||||
'''Runs a script embedded in the pkginfo.
|
||||
Returns the result code.'''
|
||||
|
||||
# get the script text from the pkginfo
|
||||
script_text = pkginfo_item.get(scriptname)
|
||||
itemname = pkginfo_item.get('name')
|
||||
if not script_text:
|
||||
output.display_error(
|
||||
'Missing script %s for %s' % (scriptname, itemname))
|
||||
return -1
|
||||
|
||||
# write the script to a temp file
|
||||
scriptpath = os.path.join(osutils.tmpdir(), scriptname)
|
||||
if _writefile(script_text, scriptpath):
|
||||
cmd = ['/bin/chmod', '-R', 'o+x', scriptpath]
|
||||
retcode = subprocess.call(cmd)
|
||||
if retcode:
|
||||
output.display_error(
|
||||
'Error setting script mode in %s for %s'
|
||||
% (scriptname, itemname))
|
||||
return -1
|
||||
else:
|
||||
output.display_error(
|
||||
'Cannot write script %s for %s' % (scriptname, itemname))
|
||||
return -1
|
||||
|
||||
# now run the script
|
||||
return runScript(
|
||||
itemname, scriptpath, scriptname, suppress_error=suppress_error)
|
||||
|
||||
|
||||
def runScript(itemname, path, scriptname, suppress_error=False):
|
||||
'''Runs a script, Returns return code.'''
|
||||
if suppress_error:
|
||||
output.display_detail(
|
||||
'Running %s for %s ' % (scriptname, itemname))
|
||||
else:
|
||||
output.display_status_minor(
|
||||
'Running %s for %s ' % (scriptname, itemname))
|
||||
if output.munkistatusoutput:
|
||||
# set indeterminate progress bar
|
||||
munkistatus.percent(-1)
|
||||
|
||||
scriptoutput = []
|
||||
try:
|
||||
proc = subprocess.Popen(path, shell=False, bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
except OSError, err:
|
||||
output.display_error(
|
||||
'Error executing script %s: %s' % (scriptname, str(err)))
|
||||
return -1
|
||||
|
||||
while True:
|
||||
msg = proc.stdout.readline().decode('UTF-8')
|
||||
if not msg and (proc.poll() != None):
|
||||
break
|
||||
# save all script output in case there is
|
||||
# an error so we can dump it to the log
|
||||
scriptoutput.append(msg)
|
||||
msg = msg.rstrip("\n")
|
||||
output.display_info(msg)
|
||||
|
||||
retcode = proc.poll()
|
||||
if retcode and not suppress_error:
|
||||
output.display_error(
|
||||
'Running %s for %s failed.' % (scriptname, itemname))
|
||||
output.display_error("-"*78)
|
||||
for line in scriptoutput:
|
||||
output.display_error("\t%s" % line.rstrip("\n"))
|
||||
output.display_error("-"*78)
|
||||
elif not suppress_error:
|
||||
output.log('Running %s for %s was successful.' % (scriptname, itemname))
|
||||
|
||||
if output.munkistatusoutput:
|
||||
# clear indeterminate progress bar
|
||||
munkistatus.percent(0)
|
||||
|
||||
return retcode
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print 'This is a library of support tools for the Munki Suite.'
|
||||
Reference in New Issue
Block a user