From afce440698108bfc96cc4a2d26a796f95fbcf3e0 Mon Sep 17 00:00:00 2001 From: Heig Gregorian Date: Fri, 2 Mar 2012 14:10:34 -0800 Subject: [PATCH] Initial commit for conditional items framework extension --- code/client/conditions/getnetworkinfo.py | 53 +++++++++++++++++++++++ code/client/munkilib/munkicommon.py | 55 +++++++++++++++++++++++- code/client/munkilib/updatecheck.py | 35 ++++++++++++++- 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100755 code/client/conditions/getnetworkinfo.py diff --git a/code/client/conditions/getnetworkinfo.py b/code/client/conditions/getnetworkinfo.py new file mode 100755 index 00000000..3fa24fbd --- /dev/null +++ b/code/client/conditions/getnetworkinfo.py @@ -0,0 +1,53 @@ +#!/usr/bin/python +'''This is a basic example of a conditional script which outputs 2 key/value pairs: +Examples: +if_name,en0 +ip_address,192.168.1.128 + +NOTE: Information gathered is ONLY for the primary interface''' + +from SystemConfiguration import * # from pyObjC +import socket +import collections +import os + +NETWORK_INFO = {} + +def getIPAddress(service_uuid): + # print service_uuid + ds = SCDynamicStoreCreate(None, 'GetIPv4Addresses', None, None) + newpattern = SCDynamicStoreKeyCreateNetworkServiceEntity(None, + kSCDynamicStoreDomainState, + service_uuid, + kSCEntNetIPv4) + + newpatterns = CFArrayCreate(None, (newpattern, ), 1, kCFTypeArrayCallBacks) + ipaddressDict = SCDynamicStoreCopyMultiple(ds, None, newpatterns) + for ipaddress in ipaddressDict.values(): + ipaddy = ipaddress['Addresses'][0] + return ipaddy + + +def getNetworkInfo(): + ds = SCDynamicStoreCreate(None, 'GetIPv4Addresses', None, None) + + pattern = SCDynamicStoreKeyCreateNetworkGlobalEntity(None, + kSCDynamicStoreDomainState, + kSCEntNetIPv4); + patterns = CFArrayCreate(None, (pattern, ), 1, kCFTypeArrayCallBacks) + valueDict = SCDynamicStoreCopyMultiple(ds, None, patterns) + + ipv4info = collections.namedtuple('ipv4info', 'ifname ip router service') + + + for serviceDict in valueDict.values(): + ifname = serviceDict[u'PrimaryInterface'] + NETWORK_INFO['interface'] = serviceDict[u'PrimaryInterface'] + NETWORK_INFO['service_uuid'] = serviceDict[u'PrimaryService'] + NETWORK_INFO['router'] = serviceDict[u'Router'] + NETWORK_INFO['ip_address'] = getIPAddress(serviceDict[u'PrimaryService']) + print "if_name,%s" % ifname + print "ip_address,%s" % NETWORK_INFO['ip_address'] + + +getNetworkInfo() diff --git a/code/client/munkilib/munkicommon.py b/code/client/munkilib/munkicommon.py index 876ec1dd..de545162 100644 --- a/code/client/munkilib/munkicommon.py +++ b/code/client/munkilib/munkicommon.py @@ -43,7 +43,7 @@ from distutils import version from types import StringType from xml.dom import minidom -from Foundation import NSDate, NSMetadataQuery, NSPredicate, NSRunLoop +from Foundation import NSArray, NSDate, NSMetadataQuery, NSPredicate, NSRunLoop from Foundation import CFPreferencesCopyAppValue from Foundation import CFPreferencesSetValue from Foundation import CFPreferencesAppSynchronize @@ -1941,6 +1941,59 @@ def getMachineFacts(): return MACHINE +CONDITIONS = {} +def getConditions(): + """Fetches key/value pairs from condition scripts + which can be placed into /usr/local/munki/conditions""" + if not CONDITIONS: + # define path to conditions directory which would contain admin created scripts + scriptdir = os.path.realpath(os.path.dirname(sys.argv[0])) + conditionsdir = os.path.join(scriptdir, "conditions") + if os.path.exists(conditionsdir): + from munkilib import utils + for condition_script in listdir(conditionsdir): + # grab path to each condition script + condition_script_path = os.path.join(conditionsdir, condition_script) + try: + # attempt to execute condition script + result, stdout, stderr = utils.runExternalScript(condition_script_path) + # condition scripts may contain multi-line output, + # each representing a key/value pair + condition_stdout = stdout.splitlines() + for condition in condition_stdout: + # format and prepare each line for inclusion into the CONDITIONS dict + condition = str(condition) + key_value_pair = filter(len,[x.strip() for x in condition.split(',')]) + key_value_length = len(key_value_pair) + if key_value_length == 2: + # traditional key/value pairing + condition_key = key_value_pair[0] + condition_value = key_value_pair[1] + elif key_value_length > 2: + # 'complex' key/value pairing - value is a list of multiple values + # keys with multiple values are cast as an NSArray. + # This allows for a slightly different predicate evaluation + condition_key = key_value_pair[0] + condition_value = key_value_pair[1:] + condition_value = NSArray.arrayWithArray_(condition_value) + else: + # key/value pair is invalid + pass + try: + # Build dict of condition key/value pairs + CONDITIONS[condition_key] = condition_value + except: + display_warning('No valid key/value pairs: %s', condition_script) + except utils.ScriptNotFoundError: + pass # script is not required, so pass + except utils.RunExternalScriptError, e: + print >> sys.stderr, str(e) + else: + # /usr/local/munki/conditions does not exist + pass + return CONDITIONS + + def isAppRunning(appname): """Tries to determine if the application in appname is currently running""" diff --git a/code/client/munkilib/updatecheck.py b/code/client/munkilib/updatecheck.py index dd1af0a7..8ca50731 100644 --- a/code/client/munkilib/updatecheck.py +++ b/code/client/munkilib/updatecheck.py @@ -1630,11 +1630,28 @@ def makePredicateInfoObject(): INFO_OBJECT['machine_type'] = 'laptop' else: INFO_OBJECT['machine_type'] = 'desktop' + for key in CONDITIONS.keys(): + INFO_OBJECT[key] = CONDITIONS[key] def predicateEvaluatesAsTrue(predicate_string): '''Evaluates predicate against our info object''' munkicommon.display_debug1('Evaluating predicate: %s' % predicate_string) + + # Parse condition item key from the predicate string + condition_key = predicate_string.split()[0] + if not condition_key in INFO_OBJECT: + # Stop processing a predicate if it's conditional item key has not been defined + munkicommon.display_warning('Condition "%s" is undefined', condition_key) + return False + + valueIsArray = False + if "array" in str(type(INFO_OBJECT[condition_key])).lower(): + # Test if the key's value is an array and prepare a new predicate string + predicate_string_orig = predicate_string + # Manipulate predicate_string in prep for different evaluation + predicate_string = predicate_string.replace(condition_key,'SELF') + valueIsArray = True try: p = NSPredicate.predicateWithFormat_(predicate_string) except Exception, e: @@ -1642,7 +1659,18 @@ def predicateEvaluatesAsTrue(predicate_string): # can't parse predicate, so return False return False - result = p.evaluateWithObject_(INFO_OBJECT) + if not valueIsArray: + # Traditional key/value pair evaluation + result = p.evaluateWithObject_(INFO_OBJECT) + else: + # Complex key/value pair evaluation + if INFO_OBJECT[condition_key].filteredArrayUsingPredicate_(p): + result = True + else: + result = False + # Set predicate string back to it's original form for easier comprehension + predicate_string = predicate_string_orig + munkicommon.display_debug1( 'Predicate %s is %s' % (predicate_string, result)) return result @@ -2308,6 +2336,7 @@ def getDownloadCachePath(destinationpathprefix, url): destinationpathprefix, getInstallerItemBasename(url)) MACHINE = {} +CONDITIONS = {} def check(client_id='', localmanifestpath=None): """Checks for available new or updated managed software, downloading installer items if needed. Returns 1 if there are available updates, @@ -2318,6 +2347,10 @@ def check(client_id='', localmanifestpath=None): MACHINE = munkicommon.getMachineFacts() munkicommon.report['MachineInfo'] = MACHINE + global CONDITIONS + munkicommon.getConditions() + CONDITIONS = munkicommon.getConditions() + ManagedInstallDir = munkicommon.pref('ManagedInstallDir') if munkicommon.munkistatusoutput: munkistatus.activate()