mirror of
https://github.com/munki/munki.git
synced 2026-01-07 06:59:57 -06:00
152 lines
5.0 KiB
Python
152 lines
5.0 KiB
Python
# encoding: utf-8
|
|
#
|
|
# Copyright 2009-2018 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
|
|
# pylint: disable=invalid-name
|
|
report = {}
|
|
# pylint: enable=invalid-name
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print 'This is a library of support tools for the Munki Suite.'
|