def checkin(request): data = request.POST # Take out some of the weird junk VMware puts in. Keep an eye out in case # Apple actually uses these: serial = data.get('serial', '').upper().translate(SERIAL_TRANSLATE) # Are we using Sal for some sort of inventory (like, I don't know, Puppet?) if utils.get_django_setting('ADD_NEW_MACHINES', True): if serial: try: machine = Machine.objects.get(serial=serial) except Machine.DoesNotExist: machine = Machine(serial=serial) else: machine = get_object_or_404(Machine, serial=serial) machine_group_key = data.get('key') if machine_group_key in (None, 'None'): machine_group_key = utils.get_django_setting('DEFAULT_MACHINE_GROUP_KEY') machine.machine_group = get_object_or_404(MachineGroup, key=machine_group_key) machine.last_checkin = django.utils.timezone.now() machine.hostname = data.get('name', '<NO NAME>') machine.sal_version = data.get('sal_version') if utils.get_django_setting('DEPLOYED_ON_CHECKIN', True): machine.deployed = True if bool(data.get('broken_client', False)): machine.broken_client = True machine.save() return HttpResponse("Broken Client report submmitted for %s" % data.get('serial')) report = None # Find the report in the submitted data. It could be encoded # and/or compressed with base64 and bz2. for key in ('bz2report', 'base64report', 'base64bz2report'): if key in data: encoded_report = data[key] report = text_utils.decode_to_string(encoded_report, compression=key) break machine.report = report if not report: machine.activity = False machine.errors = machine.warnings = 0 return report_data = plistlib.readPlistFromString(report) if report_data.get('ConsoleUser') and report_data.get('ConsoleUser') != '_mbsetupuser': machine.console_user = report_data.get('ConsoleUser') elif data.get('username') and data.get('username') != '_mbsetupuser': machine.console_user = data.get('username') else: machine.console_user = None activity_keys = ('AppleUpdates', 'InstallResults', 'RemovalResults') machine.activity = any(report_data.get(s) for s in activity_keys) # Check errors and warnings. machine.errors = len(report_data.get("Errors", [])) machine.warnings = len(report_data.get("Warnings", [])) machine.puppet_version = report_data.get('Puppet_Version') machine.manifest = report_data.get('ManifestName') machine.munki_version = report_data.get('ManagedInstallVersion') puppet = report_data.get('Puppet', {}) if 'time' in puppet: last_run_epoch = float(puppet['time']['last_run']) machine.last_puppet_run = datetime.fromtimestamp(last_run_epoch, tz=pytz.UTC) if 'events' in puppet: machine.puppet_errors = puppet['events']['failure'] # Handle gosal submissions slightly differently from others. machine.os_family = ( report_data['OSFamily'] if 'OSFamily' in report_data else report_data.get('os_family')) machine_info = report_data.get('MachineInfo', {}) if 'os_vers' in machine_info: machine.operating_system = machine_info['os_vers'] # macOS major OS updates don't have a minor version, so add one. if len(machine.operating_system) <= 4 and machine.os_family == 'Darwin': machine.operating_system = machine.operating_system + '.0' else: # Handle gosal and missing os_vers cases. machine.operating_system = machine_info.get('OSVers') # TODO: These should be a number type. # TODO: Cleanup all of the casting to str if we make a number. machine.hd_space = report_data.get('AvailableDiskSpace', '0') machine.hd_total = data.get('disk_size', '0') space = float(machine.hd_space) total = float(machine.hd_total) if space == float(0) or total == float(0): machine.hd_percent = '0' else: try: machine.hd_percent = str(int((total - space) / total * 100)) except ZeroDivisionError: machine.hd_percent = '0' # Get macOS System Profiler hardware info. # Older versions use `HardwareInfo` key, so start there. hwinfo = machine_info.get('HardwareInfo', {}) if not hwinfo: for profile in machine_info.get('SystemProfile', []): if profile['_dataType'] == 'SPHardwareDataType': hwinfo = profile._items[0] break if hwinfo: key_style = 'old' if 'MachineModel' in hwinfo else 'new' machine.machine_model = hwinfo.get(MACHINE_KEYS['machine_model'][key_style]) machine.machine_model_friendly = machine_info.get('machine_model_friendly', '') machine.cpu_type = hwinfo.get(MACHINE_KEYS['cpu_type'][key_style]) machine.cpu_speed = hwinfo.get(MACHINE_KEYS['cpu_speed'][key_style]) machine.memory = hwinfo.get(MACHINE_KEYS['memory'][key_style]) machine.memory_kb = process_memory(machine) # if not machine.machine_model_friendly: # try: # machine.machine_model_friendly = utils.friendly_machine_model(machine) # except Exception: # machine.machine_model_friendly = machine.machine_model machine.save() historical_days = utils.get_setting('historical_retention') now = django.utils.timezone.now() datelimit = now - timedelta(days=historical_days) # Process plugin scripts. # Clear out too-old plugin script submissions first. PluginScriptSubmission.objects.filter(recorded__lt=datelimit).delete() utils.process_plugin_script(report_data.get('Plugin_Results', []), machine) process_managed_items(machine, report_data, data.get('uuid'), now, datelimit) process_facts(machine, report_data, datelimit) process_conditions(machine, report_data) utils.run_plugin_processing(machine, report_data) if utils.get_setting('send_data') in (None, True): # If setting is None, it hasn't been configured yet; assume True utils.send_report() return HttpResponse("Sal report submmitted for %s" % data.get('name'))
def checkin(request): if request.method != 'POST': print 'not post data' return HttpResponseNotFound('No POST data sent') data = request.POST key = data.get('key') uuid = data.get('uuid') serial = data.get('serial') serial = serial.upper() broken_client = data.get('broken_client', False) # Take out some of the weird junk VMware puts in. Keep an eye out in case # Apple actually uses these: serial = serial.replace('/', '') serial = serial.replace('+', '') # Are we using Sal for some sort of inventory (like, I don't know, Puppet?) try: add_new_machines = settings.ADD_NEW_MACHINES except Exception: add_new_machines = True if add_new_machines: # look for serial number - if it doesn't exist, create one if serial: try: machine = Machine.objects.get(serial=serial) except Machine.DoesNotExist: machine = Machine(serial=serial) else: machine = get_object_or_404(Machine, serial=serial) try: deployed_on_checkin = settings.DEPLOYED_ON_CHECKIN except Exception: deployed_on_checkin = True if key is None or key == 'None': try: key = settings.DEFAULT_MACHINE_GROUP_KEY except Exception: pass machine_group = get_object_or_404(MachineGroup, key=key) machine.machine_group = machine_group machine.last_checkin = django.utils.timezone.now() if bool(broken_client): machine.broken_client = True machine.save() return HttpResponse("Broken Client report submmitted for %s" % data.get('serial')) else: machine.broken_client = False historical_days = utils.get_setting('historical_retention') machine.hostname = data.get('name', '<NO NAME>') if 'username' in data: if data.get('username') != '_mbsetupuser': machine.console_user = data.get('username') if 'base64bz2report' in data: machine.update_report(data.get('base64bz2report')) if 'base64report' in data: machine.update_report(data.get('base64report'), 'base64') if 'sal_version' in data: machine.sal_version = data.get('sal_version') # extract machine data from the report report_data = machine.get_report() if 'Puppet_Version' in report_data: machine.puppet_version = report_data['Puppet_Version'] if 'ManifestName' in report_data: manifest = report_data['ManifestName'] machine.manifest = manifest if 'MachineInfo' in report_data: machine.operating_system = report_data['MachineInfo'].get( 'os_vers', 'UNKNOWN') # some machines are reporting 10.9, some 10.9.0 - make them the same if len(machine.operating_system) <= 4: machine.operating_system = machine.operating_system + '.0' # if gosal is the sender look for OSVers key if 'OSVers' in report_data['MachineInfo']: machine.operating_system = report_data['MachineInfo'].get('OSVers') machine.hd_space = report_data.get('AvailableDiskSpace') or 0 machine.hd_total = int(data.get('disk_size')) or 0 if machine.hd_total == 0: machine.hd_percent = 0 else: machine.hd_percent = int( round(((float(machine.hd_total) - float(machine.hd_space)) / float(machine.hd_total)) * 100)) machine.munki_version = report_data.get('ManagedInstallVersion') or 0 hwinfo = {} # macOS System Profiler if 'SystemProfile' in report_data.get('MachineInfo', []): for profile in report_data['MachineInfo']['SystemProfile']: if profile['_dataType'] == 'SPHardwareDataType': hwinfo = profile._items[0] break if 'HardwareInfo' in report_data.get('MachineInfo', []): hwinfo = report_data['MachineInfo']['HardwareInfo'] if 'Puppet' in report_data: puppet = report_data.get('Puppet') if 'time' in puppet: machine.last_puppet_run = datetime.fromtimestamp( float(puppet['time']['last_run'])) if 'events' in puppet: machine.puppet_errors = puppet['events']['failure'] if hwinfo: # setup vars for hash keys we might get sent if 'MachineModel' in hwinfo: var_machine_model = 'MachineModel' var_cpu_type = 'CPUType' var_cpu_speed = 'CurrentProcessorSpeed' var_memory = 'PhysicalMemory' else: var_machine_model = 'machine_model' var_cpu_type = 'cpu_type' var_cpu_speed = 'current_processor_speed' var_memory = 'physical_memory' machine.machine_model = hwinfo.get(var_machine_model) machine.cpu_type = hwinfo.get(var_cpu_type) machine.cpu_speed = hwinfo.get(var_cpu_speed) machine.memory = hwinfo.get(var_memory) if hwinfo.get(var_memory)[-2:] == 'KB': machine.memory_kb = int(hwinfo.get(var_memory)[:-3]) if hwinfo.get(var_memory)[-2:] == 'MB': memory_mb = float(hwinfo.get(var_memory)[:-3]) machine.memory_kb = int(memory_mb * 1024) if hwinfo.get(var_memory)[-2:] == 'GB': memory_gb = float(hwinfo.get(var_memory)[:-3]) machine.memory_kb = int(memory_gb * 1024 * 1024) if hwinfo.get(var_memory)[-2:] == 'TB': memory_tb = float(hwinfo.get(var_memory)[:-3]) machine.memory_kb = int(memory_tb * 1024 * 1024 * 1024) if 'os_family' in report_data: machine.os_family = report_data['os_family'] # support golang strict structure if 'OSFamily' in report_data: machine.os_family = report_data['OSFamily'] if not machine.machine_model_friendly: try: machine.machine_model_friendly = utils.friendly_machine_model( machine) except Exception: machine.machine_model_friendly = machine.machine_model if deployed_on_checkin is True: machine.deployed = True machine.save() # If Plugin_Results are in the report, handle them try: datelimit = django.utils.timezone.now() - timedelta( days=historical_days) PluginScriptSubmission.objects.filter(recorded__lt=datelimit).delete() except Exception: pass if 'Plugin_Results' in report_data: utils.process_plugin_script(report_data.get('Plugin_Results'), machine) # Remove existing PendingUpdates for the machine machine.pending_updates.all().delete() now = django.utils.timezone.now() if 'ItemsToInstall' in report_data: pending_update_to_save = [] update_history_item_to_save = [] for update in report_data.get('ItemsToInstall'): display_name = update.get('display_name', update['name']) update_name = update.get('name') version = str(update['version_to_install']) if version: pending_update = PendingUpdate(machine=machine, display_name=display_name, update_version=version, update=update_name) if IS_POSTGRES: pending_update_to_save.append(pending_update) else: pending_update.save() # Let's handle some of those lovely pending installs into the UpdateHistory Model try: update_history = UpdateHistory.objects.get( name=update_name, version=version, machine=machine, update_type='third_party') except UpdateHistory.DoesNotExist: update_history = UpdateHistory(name=update_name, version=version, machine=machine, update_type='third_party') update_history.save() if not update_history.pending_recorded: update_history_item = UpdateHistoryItem( update_history=update_history, status='pending', recorded=now, uuid=uuid) update_history.pending_recorded = True update_history.save() if IS_POSTGRES: update_history_item_to_save.append(update_history_item) else: update_history_item.save() if IS_POSTGRES: PendingUpdate.objects.bulk_create(pending_update_to_save) UpdateHistoryItem.objects.bulk_create(update_history_item_to_save) machine.installed_updates.all().delete() if 'ManagedInstalls' in report_data: # Due to a quirk in how Munki 3 processes updates with dependencies, # it's possible to have multiple entries in the ManagedInstalls list # that share an update_name and installed_version. This causes an # IntegrityError in Django since (machine_id, update, update_version) # must be unique.Until/(unless!) this is addressed in Munki, we need to # be careful to not add multiple items with the same name and version. # We'll store each (update_name, version) combo as we see them. seen_names_and_versions = [] installed_updates_to_save = [] for update in report_data.get('ManagedInstalls'): display_name = update.get('display_name', update['name']) update_name = update.get('name') version = str(update.get('installed_version', 'UNKNOWN')) installed = update.get('installed') if (update_name, version) not in seen_names_and_versions: seen_names_and_versions.append((update_name, version)) if (version != 'UNKNOWN' and version is not None and len(version) != 0): installed_update = InstalledUpdate( machine=machine, display_name=display_name, update_version=version, update=update_name, installed=installed) if IS_POSTGRES: installed_updates_to_save.append(installed_update) else: installed_update.save() if IS_POSTGRES: InstalledUpdate.objects.bulk_create(installed_updates_to_save) # Remove existing PendingAppleUpdates for the machine machine.pending_apple_updates.all().delete() if 'AppleUpdates' in report_data: for update in report_data.get('AppleUpdates'): display_name = update.get('display_name', update['name']) update_name = update.get('name') version = str(update['version_to_install']) try: pending_update = PendingAppleUpdate.objects.get( machine=machine, display_name=display_name, update_version=version, update=update_name) except PendingAppleUpdate.DoesNotExist: pending_update = PendingAppleUpdate(machine=machine, display_name=display_name, update_version=version, update=update_name) pending_update.save() # Let's handle some of those lovely pending installs into the UpdateHistory Model try: update_history = UpdateHistory.objects.get(name=update_name, version=version, machine=machine, update_type='apple') except UpdateHistory.DoesNotExist: update_history = UpdateHistory(name=update_name, version=version, machine=machine, update_type='apple') update_history.save() if not update_history.pending_recorded: update_history_item = UpdateHistoryItem( update_history=update_history, status='pending', recorded=now, uuid=uuid) update_history_item.save() update_history.pending_recorded = True update_history.save() # if Facter data is submitted, we need to first remove any existing facts for this machine if IS_POSTGRES: # If we are using postgres, we can just dump them all and do a bulk create if 'Facter' in report_data: facts = machine.facts.all().delete() try: datelimit = django.utils.timezone.now() - timedelta( days=historical_days) HistoricalFact.objects.filter( fact_recorded__lt=datelimit).delete() except Exception: pass try: historical_facts = settings.HISTORICAL_FACTS except Exception: historical_facts = [] pass facts_to_be_created = [] historical_facts_to_be_created = [] for fact_name, fact_data in report_data['Facter'].iteritems(): skip = False if hasattr(settings, 'IGNORE_FACTS'): for prefix in settings.IGNORE_FACTS: if fact_name.startswith(prefix): skip = True if skip: continue facts_to_be_created.append( Fact(machine=machine, fact_data=fact_data, fact_name=fact_name)) if fact_name in historical_facts: historical_facts_to_be_created.append( HistoricalFact(machine=machine, fact_data=fact_data, fact_name=fact_name)) Fact.objects.bulk_create(facts_to_be_created) if len(historical_facts_to_be_created) != 0: HistoricalFact.objects.bulk_create( historical_facts_to_be_created) else: if 'Facter' in report_data: facts = machine.facts.all() for fact in facts: skip = False if hasattr(settings, 'IGNORE_FACTS'): for prefix in settings.IGNORE_FACTS: if fact.fact_name.startswith(prefix): skip = True fact.delete() break if not skip: continue found = False for fact_name, fact_data in report_data['Facter'].iteritems(): if fact.fact_name == fact_name: found = True break if not found: fact.delete() # Delete old historical facts try: datelimit = django.utils.timezone.now() - timedelta( days=historical_days) HistoricalFact.objects.filter( fact_recorded__lt=datelimit).delete() except Exception: pass try: historical_facts = settings.HISTORICAL_FACTS except Exception: historical_facts = [] pass # now we need to loop over the submitted facts and save them facts = machine.facts.all() for fact_name, fact_data in report_data['Facter'].iteritems(): if machine.os_family == 'Windows': # We had a little trouble parsing out facts on Windows, clean up here if fact_name.startswith('value=>'): fact_name = fact_name.replace('value=>', '', 1) # does fact exist already? found = False skip = False if hasattr(settings, 'IGNORE_FACTS'): for prefix in settings.IGNORE_FACTS: if fact_name.startswith(prefix): skip = True break if skip: continue for fact in facts: if fact_name == fact.fact_name: # it exists, make sure it's got the right info found = True if fact_data == fact.fact_data: # it's right, break break else: fact.fact_data = fact_data fact.save() break if not found: fact = Fact(machine=machine, fact_data=fact_data, fact_name=fact_name) fact.save() if fact_name in historical_facts: fact = HistoricalFact(machine=machine, fact_name=fact_name, fact_data=fact_data, fact_recorded=datetime.now()) fact.save() if IS_POSTGRES: if 'Conditions' in report_data: machine.conditions.all().delete() conditions_to_be_created = [] for condition_name, condition_data in report_data[ 'Conditions'].iteritems(): # Skip the conditions that come from facter if 'Facter' in report_data and condition_name.startswith( 'facter_'): continue condition_data = text_utils.stringify(condition_data) conditions_to_be_created.append( Condition(machine=machine, condition_name=condition_name, condition_data=text_utils.safe_unicode( condition_data))) Condition.objects.bulk_create(conditions_to_be_created) else: if 'Conditions' in report_data: conditions = machine.conditions.all() for condition in conditions: found = False for condition_name, condition_data in report_data[ 'Conditions'].iteritems(): if condition.condition_name == condition_name: found = True break if found is False: condition.delete() conditions = machine.conditions.all() for condition_name, condition_data in report_data[ 'Conditions'].iteritems(): # Skip the conditions that come from facter if 'Facter' in report_data and condition_name.startswith( 'facter_'): continue # if it's a list (more than one result), # we're going to conacetnate it into one comma separated string. condition_data = text_utils.stringify(condition_data) found = False for condition in conditions: if condition_name == condition.condition_name: # it exists, make sure it's got the right info found = True if condition_data == condition.condition_data: # it's right, break break else: condition.condition_data = condition_data condition.save() break if found is False: condition = Condition( machine=machine, condition_name=condition_name, condition_data=text_utils.safe_unicode(condition_data)) condition.save() utils.run_plugin_processing(machine, report_data) if utils.get_setting('send_data') in (None, True): # If setting is None, it hasn't been configured yet; assume True current_version = utils.send_report() else: current_version = utils.get_current_release_version_number() if current_version: utils.set_setting('current_version', current_version) return HttpResponse("Sal report submmitted for %s" % data.get('name'))