"""GAM ChromeOS device management.""" import json import sys from gam.util.csv_pf import RI_J, RI_JCOUNT, RI_ITEM import os import time from gamlib import glaction from gamlib import glapi as API from gamlib import glcfg as GC from gamlib import glclargs from gamlib import glentity from gamlib import glgapi as GAPI from gamlib import glglobals as GM from gamlib import glindent from gamlib import glmsgs as Msg Act = glaction.GamAction() Ent = glentity.GamEntity() Ind = glindent.GamIndent() Cmd = glclargs.GamCLArgs() def _getMain(): return sys.modules['gam'] def __getattr__(name): """Fall back to gam module for any undefined names.""" main = _getMain() try: return getattr(main, name) except AttributeError: raise AttributeError(f"module {__name__!r} has no attribute {name!r}") UNKNOWN = 'Unknown' WARNING_PREFIX = 'WARNING: ' def getCrOSDeviceEntity(): if _getMain().checkArgumentPresent('crossn'): return _getMain().getItemsToModify(Cmd.ENTITY_CROS_SN, _getMain().getString(Cmd.OB_SERIAL_NUMBER_LIST)) if _getMain().checkArgumentPresent('query'): return _getMain().getItemsToModify(Cmd.ENTITY_CROS_QUERY, _getMain().getString(Cmd.OB_QUERY)) deviceId = _getMain().getString(Cmd.OB_CROS_DEVICE_ENTITY) if deviceId[:6].lower() == 'query:': query = deviceId[6:] if query[:12].lower() == 'orgunitpath:': return _getMain().getItemsToModify(Cmd.ENTITY_CROS_OU, query[12:]) return _getMain().getItemsToModify(Cmd.ENTITY_CROS_QUERY, query) Cmd.Backup() return _getMain().getEntityList(Cmd.OB_CROS_ENTITY) UPDATE_CROS_ARGUMENT_TO_PROPERTY_MAP = { 'annotatedassetid': 'annotatedAssetId', 'annotatedlocation': 'annotatedLocation', 'annotateduser': 'annotatedUser', 'asset': 'annotatedAssetId', 'assetid': 'annotatedAssetId', 'location': 'annotatedLocation', 'notes': 'notes', 'org': 'orgUnitPath', 'orgunitpath': 'orgUnitPath', 'ou': 'orgUnitPath', 'tag': 'annotatedAssetId', 'updatenotes': 'notes', 'user': 'annotatedUser', } CROS_ACTION_CHOICE_MAP = { 'deprovisiondifferentmodelreplace': ('CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_DEPROVISION', 'DEPROVISION_REASON_DIFFERENT_MODEL_REPLACEMENT'), 'deprovisiondifferentmodelreplacement': ('CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_DEPROVISION', 'DEPROVISION_REASON_DIFFERENT_MODEL_REPLACEMENT'), 'deprovisionretiringdevice': ('CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_DEPROVISION', 'DEPROVISION_REASON_RETIRING_DEVICE'), 'deprovisionsamemodelreplace': ('CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_DEPROVISION', 'DEPROVISION_REASON_SAME_MODEL_REPLACEMENT'), 'deprovisionsamemodelreplacement': ('CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_DEPROVISION', 'DEPROVISION_REASON_SAME_MODEL_REPLACEMENT'), 'deprovisionupgradetransfer': ('CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_DEPROVISION', 'DEPROVISION_REASON_UPGRADE_TRANSFER'), 'disable': ('CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_DISABLE', None), 'reenable': ('CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_REENABLE', None), # 'preprovisioneddisable': ('pre_provisioned_disable', None), # 'preprovisionedreenable': ('pre_provisioned_reenable', None) } CROS_ACTION_NAME_MAP = { 'CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_DEPROVISION': Act.DEPROVISION, 'CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_DISABLE': Act.DISABLE, 'CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_REENABLE': Act.REENABLE, # 'pre_provisioned_disable': Act.PRE_PROVISIONED_DISABLE, # 'pre_provisioned_reenable': Act.PRE_PROVISIONED_REENABLE } # gam update + [quickcrosmove []] [nobatchupdate] # gam update action [acknowledge_device_touch_requirement] # [actionbatchsize ] [maxtodeprov ] def updateCrOSDevices(entityList): cd = _getMain().buildGAPIObject(API.DIRECTORY) noBatchUpdate = False update_body = {} action_body = {} orgUnitPath = updateNotes = None ackWipe = False quickCrOSMove = GC.Values[GC.QUICK_CROS_MOVE] actionBatchSize = 10 maxToDeprovision = None while Cmd.ArgumentsRemaining(): myarg = _getMain().getArgument() if myarg in UPDATE_CROS_ARGUMENT_TO_PROPERTY_MAP: up = UPDATE_CROS_ARGUMENT_TO_PROPERTY_MAP[myarg] if up == 'orgUnitPath': orgUnitPath = _getMain().getOrgUnitItem() elif up == 'notes': update_body[up] = _getMain().getStringWithCRsNLs() updateNotes = update_body[up] if myarg == 'updatenotes' and update_body[up].find('#notes#') != -1 else None else: update_body[up] = _getMain().getString(Cmd.OB_STRING, minLen=0) elif myarg == 'action': actionLocation = Cmd.Location() action_body['changeChromeOsDeviceStatusAction'], deprovisionReason = _getMain().getChoice(CROS_ACTION_CHOICE_MAP, mapChoice=True) if deprovisionReason: action_body['deprovisionReason'] = deprovisionReason Act.Set(CROS_ACTION_NAME_MAP[action_body['changeChromeOsDeviceStatusAction']]) elif myarg == 'acknowledgedevicetouchrequirement': ackWipe = True elif myarg == 'quickcrosmove': quickCrOSMove = _getMain().getBoolean() elif myarg == 'nobatchupdate': noBatchUpdate = _getMain().getBoolean() elif myarg == 'actionbatchsize': actionBatchSize = _getMain().getInteger(minVal=10, maxVal=250) elif myarg == 'maxtodeprov': maxToDeprovision = _getMain().getInteger(minVal=0) else: _getMain().unknownArgumentExit() if action_body and update_body: Cmd.SetLocation(actionLocation-1) _getMain().usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format('action', '')) if orgUnitPath: status, orgUnitPath, orgUnitId = _getMain().checkOrgUnitPathExists(cd, orgUnitPath) if not status: _getMain().entityActionFailedWarning([Ent.CROS_DEVICE, ''], f'{Ent.Singular(Ent.ORGANIZATIONAL_UNIT)}: {orgUnitPath}, {Msg.DOES_NOT_EXIST}') return i, count, entityList = _getMain().getEntityArgument(entityList) # Action if action_body: if action_body['changeChromeOsDeviceStatusAction'] == 'CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_DEPROVISION': if not ackWipe: _getMain().stderrWarningMsg(Msg.REFUSING_TO_DEPROVISION_DEVICES.format(count)) _getMain().systemErrorExit(_getMain().ACTION_NOT_PERFORMED_RC, None) if maxToDeprovision is None: maxToDeprovision = 1 if count > maxToDeprovision > 0: _getMain().stderrWarningMsg(Msg.REFUSING_TO_DEPROVISION_N_DEVICES.format(count, maxToDeprovision)) _getMain().systemErrorExit(_getMain().ACTION_NOT_PERFORMED_RC, None) while i < count: bcount = min(count-i, actionBatchSize) action_body['deviceIds'] = entityList[i:i+bcount] try: result = _getMain().callGAPI(cd.customer().devices().chromeos(), 'batchChangeStatus', throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.CONDITION_NOT_MET, GAPI.INVALID_INPUT, GAPI.BAD_REQUEST, GAPI.FORBIDDEN], customerId=GC.Values[GC.CUSTOMER_ID], body=action_body) for status in result['changeChromeOsDeviceStatusResults']: i += 1 deviceId = status['deviceId'] if 'error' not in status: _getMain().entityActionPerformed([Ent.CROS_DEVICE, deviceId], i, count) else: _getMain().entityActionFailedWarning([Ent.CROS_DEVICE, deviceId], status['error']['message'], i, count) except (GAPI.invalid, GAPI.invalidArgument, GAPI.conditionNotMet, GAPI.invalidInput, GAPI.badRequest, GAPI.forbidden) as e: _getMain().entityActionFailedExit([Ent.CROS_DEVICE, None], str(e)) return # Update function = None if update_body or noBatchUpdate: if orgUnitPath and (not quickCrOSMove or noBatchUpdate): update_body['orgUnitPath'] = orgUnitPath if GC.Values[GC.UPDATE_CROS_OU_WITH_ID]: update_body['orgUnitId'] = orgUnitId orgUnitPath = None function = 'update' parmId = 'deviceId' kwargs = {parmId: None, 'body': update_body, 'fields': ''} if orgUnitPath: Act.Set(Act.ADD) _getMain()._batchMoveCrOSesToOrgUnit(cd, orgUnitPath, orgUnitId, 0, 0, entityList, quickCrOSMove) Act.Set(Act.UPDATE) if function is None: return for deviceId in entityList: i += 1 kwargs[parmId] = deviceId try: if updateNotes: oldNotes = _getMain().callGAPI(cd.chromeosdevices(), 'get', throwReasons=[GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN], customerId=GC.Values[GC.CUSTOMER_ID], deviceId=deviceId, fields='notes').get('notes', '') update_body['notes'] = updateNotes.replace('#notes#', oldNotes) _getMain().callGAPI(cd.chromeosdevices(), function, throwReasons=[GAPI.INVALID, GAPI.CONDITION_NOT_MET, GAPI.INVALID_INPUT, GAPI.INVALID_ORGUNIT, GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN], customerId=GC.Values[GC.CUSTOMER_ID], **kwargs) _getMain().entityActionPerformed([Ent.CROS_DEVICE, deviceId], i, count) except (GAPI.invalid, GAPI.conditionNotMet, GAPI.invalidInput) as e: _getMain().entityActionFailedWarning([Ent.CROS_DEVICE, deviceId], str(e), i, count) except GAPI.invalidOrgunit: _getMain().entityActionFailedWarning([Ent.CROS_DEVICE, deviceId], Msg.INVALID_ORGUNIT, i, count) except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden): _getMain().checkEntityAFDNEorAccessErrorExit(cd, Ent.CROS_DEVICE, deviceId, i, count) # gam update cros|croses + [quickcrosmove []] [nobatchupdate] # gam update cros|croses action [acknowledge_device_touch_requirement] def doUpdateCrOSDevices(): updateCrOSDevices(getCrOSDeviceEntity()) CROS_COMMAND_CHOICE_MAP = { 'reboot': 'REBOOT', 'remotepowerwash': 'REMOTE_POWERWASH', 'setvolume': 'SET_VOLUME', 'takeascreenshot': 'TAKE_A_SCREENSHOT', 'wipeusers': 'WIPE_USERS' } CROS_DOIT_REQUIRED_COMMANDS = {'WIPE_USERS', 'REMOTE_POWERWASH'} CROS_KIOSK_COMMANDS = {'REBOOT', 'SET_VOLUME', 'TAKE_A_SCREENSHOT'} CROS_COMMAND_FINAL_STATES = {'EXPIRED', 'CANCELLED', 'EXECUTED_BY_CLIENT'} CROS_COMMAND_TIME_OBJECTS = {'executeTime', 'issueTime', 'commandExpireTime'} def displayCrOSCommandResult(cd, deviceId, commandId, checkResultRetries, i, count, csvPF, addCSVData): Ind.Increment() try: for _ in range(0, checkResultRetries): time.sleep(2) result = _getMain().callGAPI(cd.customer().devices().chromeos().commands(), 'get', throwReasons=[GAPI.INVALID_ARGUMENT, GAPI.BAD_REQUEST, GAPI.NOT_FOUND, GAPI.FORBIDDEN], customerId=GC.Values[GC.CUSTOMER_ID], deviceId=deviceId, commandId=commandId) if csvPF: result['deviceId'] = deviceId if addCSVData: result.update(addCSVData) csvPF.WriteRowTitles(_getMain().flattenJSON(result, timeObjects=CROS_COMMAND_TIME_OBJECTS)) break _getMain().showJSON(None, result, timeObjects=CROS_COMMAND_TIME_OBJECTS) state = result.get('state') if state in CROS_COMMAND_FINAL_STATES: break except (GAPI.invalidArgument, GAPI.badRequest, GAPI.notFound, GAPI.forbidden) as e: _getMain().entityActionFailedWarning([Ent.CROS_DEVICE, deviceId, Ent.COMMAND_ID, commandId], str(e), i, count) Ind.Decrement() def writeCrOSCommandResults(csvPF, addCSVData): sortTitles = ['deviceId'] if addCSVData: sortTitles.extend(sorted(addCSVData.keys())) sortTitles.append('commandId') csvPF.SetSortTitles(sortTitles) csvPF.writeCSVfile('CrOS Commands') # gam issuecommand command # [times_to_check_status ] [doit] # [csv (addcsvdata )*] def issueCommandCrOSDevices(entityList): cd = _getMain().buildGAPIObject(API.DIRECTORY) csvPF = None addCSVData = {} body = {} checkResultRetries = 1 doit = False while Cmd.ArgumentsRemaining(): myarg = _getMain().getArgument() if myarg == 'command': body['commandType'] = _getMain().getChoice(CROS_COMMAND_CHOICE_MAP, mapChoice=True) if body['commandType'] == 'SET_VOLUME': body['payload'] = json.dumps({'volume': _getMain().getInteger(minVal=0, maxVal=100)}) elif myarg == 'timestocheckstatus': checkResultRetries = _getMain().getInteger(minVal=0) elif myarg == 'csv': csvPF = _getMain().CSVPrintFile(['deviceId']) elif csvPF and myarg == 'addcsvdata': _getMain().getAddCSVData(addCSVData) elif myarg == 'doit': doit = True else: _getMain().unknownArgumentExit() if not body: _getMain().missingArgumentExit('command ') i, count, entityList = _getMain().getEntityArgument(entityList) if body['commandType'] in CROS_DOIT_REQUIRED_COMMANDS and not doit: _getMain().actionNotPerformedNumItemsWarning(count, Ent.CROS_DEVICE, Msg.USE_DOIT_ARGUMENT_TO_PERFORM_ACTION) return for deviceId in entityList: i += 1 try: result = _getMain().callGAPI(cd.customer().devices().chromeos(), 'issueCommand', throwReasons=[GAPI.INVALID_ARGUMENT, GAPI.NOT_FOUND], customerId=GC.Values[GC.CUSTOMER_ID], deviceId=deviceId, body=body) commandId = result.get('commandId') _getMain().entityActionPerformed([Ent.CROS_DEVICE, deviceId, Ent.ACTION, body['commandType'], Ent.COMMAND_ID, commandId], i, count) displayCrOSCommandResult(cd, deviceId, commandId, checkResultRetries, i, count, csvPF, addCSVData) except GAPI.invalidArgument as e: errMsg = str(e) if body['commandType'] in CROS_KIOSK_COMMANDS: errMsg += Msg.KIOSK_MODE_REQUIRED.format(body['commandType']) _getMain().entityActionFailedWarning([Ent.CROS_DEVICE, deviceId], errMsg, i, count) except GAPI.notFound as e: _getMain().entityActionFailedWarning([Ent.CROS_DEVICE, deviceId], str(e), i, count) if csvPF: writeCrOSCommandResults(csvPF, addCSVData) # gam issuecommand command # [times_to_check_status ] [doit] # [csv (addcsvdata )*] def doIssueCommandCrOSDevices(): issueCommandCrOSDevices(getCrOSDeviceEntity()) # gam getcommand commandid # [times_to_check_status ] [csv] # [csv (addcsvdata )*] def getCommandResultCrOSDevices(entityList): cd = _getMain().buildGAPIObject(API.DIRECTORY) csvPF = None addCSVData = {} commandId = '' checkResultRetries = 1 while Cmd.ArgumentsRemaining(): myarg = _getMain().getArgument() if myarg == 'commandid': commandId = _getMain().getString(Cmd.OB_COMMAND_ID) elif myarg == 'timestocheckstatus': checkResultRetries = _getMain().getInteger(minVal=0) elif myarg == 'csv': csvPF = _getMain().CSVPrintFile(['deviceId']) elif csvPF and myarg == 'addcsvdata': _getMain().getAddCSVData(addCSVData) else: _getMain().unknownArgumentExit() if not commandId: _getMain().missingArgumentExit('commandid ') i, count, entityList = _getMain().getEntityArgument(entityList) for deviceId in entityList: i += 1 _getMain().printEntity([Ent.CROS_DEVICE, deviceId, Ent.COMMAND_ID, commandId], i, count) displayCrOSCommandResult(cd, deviceId, commandId, checkResultRetries, i, count, csvPF, addCSVData) if csvPF: writeCrOSCommandResults(csvPF, addCSVData) # gam getcommand commandid # [times_to_check_status ] [csv] # [csv (addcsvdata )*] def doGetCommandResultCrOSDevices(): getCommandResultCrOSDevices(getCrOSDeviceEntity()) # From https://www.chromium.org/chromium-os/tpm_firmware_update CROS_TPM_VULN_VERSIONS = ['41f', '420', '628', '8520'] CROS_TPM_FIXED_VERSIONS = ['422', '62b', '8521'] def checkTPMVulnerability(cros): if 'tpmVersionInfo' in cros and 'firmwareVersion' in cros['tpmVersionInfo']: if cros['tpmVersionInfo']['firmwareVersion'] in CROS_TPM_VULN_VERSIONS: cros['tpmVersionInfo']['tpmVulnerability'] = 'VULNERABLE' elif cros['tpmVersionInfo']['firmwareVersion'] in CROS_TPM_FIXED_VERSIONS: cros['tpmVersionInfo']['tpmVulnerability'] = 'UPDATED' else: cros['tpmVersionInfo']['tpmVulnerability'] = 'NOT IMPACTED' def _filterActiveTimeRanges(cros, selected, listLimit, startDate, endDate, activeTimeRangesOrder): if not selected: cros.pop('activeTimeRanges', None) return [] filteredItems = [] activeTimeRanges = cros.get('activeTimeRanges', []) if activeTimeRangesOrder == 'DESCENDING': activeTimeRanges.reverse() i = 0 for item in activeTimeRanges: activityDate = arrow.Arrow.strptime(item['date'], _getMain().YYYYMMDD_FORMAT) if ((startDate is None) or (activityDate >= startDate)) and ((endDate is None) or (activityDate <= endDate)): item['duration'] = formatMilliSeconds(item['activeTime']) item['minutes'] = item['activeTime']//60000 item['activeTime'] = str(item['activeTime']) filteredItems.append(item) i += 1 if listLimit and i == listLimit: break cros['activeTimeRanges'] = filteredItems return cros['activeTimeRanges'] def _filterDeviceFiles(cros, selected, listLimit, startTime, endTime): if not selected: cros.pop('deviceFiles', None) return [] filteredItems = [] i = 0 for item in cros.get('deviceFiles', []): timeValue = arrow.get(item['createTime']) if ((startTime is None) or (timeValue >= startTime)) and ((endTime is None) or (timeValue <= endTime)): item['createTime'] = formatLocalTime(item['createTime']) filteredItems.append(item) i += 1 if listLimit and i == listLimit: break cros['deviceFiles'] = filteredItems return cros['deviceFiles'] def _filterCPUStatusReports(cros, selected, listLimit, startTime, endTime): if not selected: cros.pop('cpuStatusReports', None) return [] filteredItems = [] i = 0 for item in cros.get('cpuStatusReports', []): timeValue = arrow.get(item['reportTime']) if ((startTime is None) or (timeValue >= startTime)) and ((endTime is None) or (timeValue <= endTime)): item['reportTime'] = formatLocalTime(item['reportTime']) for tempInfo in item.get('cpuTemperatureInfo', []): tempInfo['label'] = tempInfo['label'].strip() if 'cpuUtilizationPercentageInfo' in item: item['cpuUtilizationPercentageInfo'] = ','.join([str(x) for x in item['cpuUtilizationPercentageInfo']]) filteredItems.append(item) i += 1 if listLimit and i == listLimit: break cros['cpuStatusReports'] = filteredItems return cros['cpuStatusReports'] def _filterSystemRamFreeReports(cros, selected, listLimit, startTime, endTime): if not selected: cros.pop('systemRamFreeReports', None) return [] filteredItems = [] i = 0 for item in cros.get('systemRamFreeReports', []): timeValue = arrow.get(item['reportTime']) if ((startTime is None) or (timeValue >= startTime)) and ((endTime is None) or (timeValue <= endTime)): item['reportTime'] = formatLocalTime(item['reportTime']) item['systemRamFreeInfo'] = ','.join([str(x) for x in item['systemRamFreeInfo']]) filteredItems.append(item) i += 1 if listLimit and i == listLimit: break cros['systemRamFreeReports'] = filteredItems return cros['systemRamFreeReports'] def _filterRecentUsers(cros, selected, listLimit): if not selected: cros.pop('recentUsers', None) return [] filteredItems = [] i = 0 for item in cros.get('recentUsers', []): item['email'] = item.get('email', [UNKNOWN, 'UnmanagedUser'][item['type'] == 'USER_TYPE_UNMANAGED']) filteredItems.append(item) i += 1 if listLimit and i == listLimit: break cros['recentUsers'] = filteredItems return cros['recentUsers'] def _filterScreenshotFiles(cros, selected, listLimit, startTime, endTime): if not selected: cros.pop('screenshotFiles', None) return [] filteredItems = [] i = 0 for item in cros.get('screenshotFiles', []): timeValue = arrow.get(item['createTime']) if ((startTime is None) or (timeValue >= startTime)) and ((endTime is None) or (timeValue <= endTime)): item['createTime'] = formatLocalTime(item['createTime']) filteredItems.append(item) i += 1 if listLimit and i == listLimit: break cros['screenshotFiles'] = filteredItems return cros['screenshotFiles'] def _filterBasicList(cros, field, selected, listLimit): if not selected: cros.pop(field, None) return [] if listLimit: filteredItems = [] i = 0 for item in cros.get(field, []): filteredItems.append(item) i += 1 if listLimit and i == listLimit: break cros[field] = filteredItems return cros[field] return cros.get(field, []) def _computeDVRstorageFreePercentage(cros): for diskVolumeReport in cros.get('diskVolumeReports', []): volumeInfo = diskVolumeReport['volumeInfo'] for volume in volumeInfo: if volume['storageTotal'] != '0': volume['storageFreePercentage'] = str(int(int(volume['storageFree'])/int(volume['storageTotal'])*100)) else: volume['storageFreePercentage'] = '0' def _getFilterDateTime(): filterDate = _getMain().getYYYYMMDD(returnDateTime=True) return (filterDate, filterDate.replace(tzinfo='UTC')) CROS_FIELDS_CHOICE_MAP = { 'activetimeranges': 'activeTimeRanges', 'annotatedassetid': 'annotatedAssetId', 'annotatedlocation': 'annotatedLocation', 'annotateduser': 'annotatedUser', 'asset': 'annotatedAssetId', 'assetid': 'annotatedAssetId', 'autoupdateexpiration': 'autoUpdateExpiration', 'autoupdatethrough': 'autoUpdateThrough', 'backlightinfo': 'backlightInfo', 'bluetoothadapterinfo': 'bluetoothAdapterInfo', 'bootmode': 'bootMode', 'chromeostype': 'chromeOsType', 'cpuinfo': 'cpuInfo', 'cpustatusreports': 'cpuStatusReports', 'deprovisionreason': 'deprovisionReason', 'devicefiles': 'deviceFiles', 'deviceid': 'deviceId', 'devicelicensetype': 'deviceLicenseType', 'diskspaceusage': 'diskSpaceUsage', 'diskvolumereports': 'diskVolumeReports', 'dockmacaddress': 'dockMacAddress', 'ethernetmacaddress': 'ethernetMacAddress', 'ethernetmacaddress0': 'ethernetMacAddress0', 'extendedsupporteligible': 'extendedSupportEligible', 'extendedsupportstart': 'extendedSupportStart', 'extendedsupportenabled': 'extendedSupportEnabled', 'faninfo': 'fanInfo', 'firmwareversion': 'firmwareVersion', 'firstenrollmenttime': 'firstEnrollmentTime', 'lastdeprovisiontimestamp': 'lastDeprovisionTimestamp', 'lastenrollmenttime': 'lastEnrollmentTime', 'lastknownnetwork': 'lastKnownNetwork', 'lastsync': 'lastSync', 'location': 'annotatedLocation', 'macaddress': 'macAddress', 'manufacturedate': 'manufactureDate', 'meid': 'meid', 'model': 'model', 'notes': 'notes', 'ordernumber': 'orderNumber', 'org': 'orgUnitPath', 'orgunitid': 'orgUnitId', 'orgunitpath': 'orgUnitPath', 'osupdatestatus': 'osUpdateStatus', 'osversion': 'osVersion', 'osversioncompliance': 'osVersionCompliance', 'ou': 'orgUnitPath', 'platformversion': 'platformVersion', 'recentusers': 'recentUsers', 'screenshotfiles': 'screenshotFiles', 'serialnumber': 'serialNumber', 'status': 'status', 'supportenddate': 'supportEndDate', 'systemramfreereports': 'systemRamFreeReports', 'systemramtotal': 'systemRamTotal', 'tag': 'annotatedAssetId', 'timeranges': 'activeTimeRanges', 'times': 'activeTimeRanges', 'tpmversioninfo': 'tpmVersionInfo', 'user': 'annotatedUser', 'users': 'recentUsers', 'willautorenew': 'willAutoRenew', } CROS_BASIC_FIELDS_LIST = ['deviceId', 'annotatedAssetId', 'annotatedLocation', 'annotatedUser', 'lastSync', 'notes', 'serialNumber', 'status'] CROS_SCALAR_PROPERTY_PRINT_ORDER = [ 'orgUnitId', 'orgUnitPath', 'annotatedAssetId', 'annotatedLocation', 'annotatedUser', 'lastSync', 'notes', 'serialNumber', 'status', 'chromeOsType', 'deviceLicenseType', 'model', 'firmwareVersion', 'platformVersion', 'osVersion', 'osVersionCompliance', 'bootMode', 'meid', 'dockMacAddress', 'ethernetMacAddress', 'ethernetMacAddress0', 'macAddress', 'systemRamTotal', 'firstEnrollmentTime', 'lastEnrollmentTime', 'deprovisionReason', 'lastDeprovisionTimestamp', 'orderNumber', 'manufactureDate', 'supportEndDate', 'autoUpdateExpiration', 'autoUpdateThrough', 'willAutoRenew', ] CROS_LIST_FIELDS_CHOICE_MAP = { 'activetimeranges': 'activeTimeRanges', 'cpustatusreports': 'cpuStatusReports', 'devicefiles': 'deviceFiles', 'diskvolumereports': 'diskVolumeReports', 'files': 'deviceFiles', 'lastknownnetwork': 'lastKnownNetwork', 'recentusers': 'recentUsers', 'screenshotfiles': 'screenshotFiles', 'systemramfreereports': 'systemRamFreeReports', 'timeranges': 'activeTimeRanges', 'times': 'activeTimeRanges', 'users': 'recentUsers', } CROS_TIME_OBJECTS = { 'createTime', 'extendedSupportStart', 'firstEnrollmentTime', 'lastDeprovisionTimestamp', 'lastEnrollmentTime', 'lastSync', 'rebootTime', 'reportTime', 'supportEndDate', 'updateTime', 'updateCheckTime', } CROS_FIELDS_WITH_CRS_NLS = {'notes'} CROS_START_ARGUMENTS = ['start', 'startdate', 'oldestdate'] CROS_END_ARGUMENTS = ['end', 'enddate'] # gam info cros # gam info # [basic|full|allfields] * [fields ] # [nolists] # [start ] [end ] [listlimit ] # [reverselists ] # [timerangeorder ascending|descending] [showdvrsfp] # [downloadfile latest|