From 4c8d10b2a29cfeafa67e002cb2fe521f49a752bc Mon Sep 17 00:00:00 2001 From: Bryson Tyrrell Date: Fri, 5 Jan 2024 10:21:23 -0600 Subject: [PATCH 01/11] Add mobile device inventory options. --- src/jamf_pro_sdk/models/pro/api_options.py | 205 +++++++++++++++++++++ 1 file changed, 205 insertions(+) diff --git a/src/jamf_pro_sdk/models/pro/api_options.py b/src/jamf_pro_sdk/models/pro/api_options.py index 1441cf7..ed80a7e 100644 --- a/src/jamf_pro_sdk/models/pro/api_options.py +++ b/src/jamf_pro_sdk/models/pro/api_options.py @@ -223,3 +223,208 @@ "SET_AUTO_ADMIN_PASSWORD", "UNKNOWN", ] + +get_mobile_device_inventory_v1_allowed_sections = [ + "GENERAL", + "HARDWARE", + "USER_AND_LOCATION", + "PURCHASING", + "SECURITY", + "APPLICATIONS", + "EBOOKS", + "NETWORK", + "SERVICE_SUBSCRIPTIONS", + "CERTIFICATES", + "PROFILES", + "USER_PROFILES", + "PROVISIONING_PROFILES", + "SHARED_USERS", + "EXTENSION_ATTRIBUTES", +] + +get_mobile_device_inventory_v1_allowed_sort_fields = [ + "airPlayPassword", + "appAnalyticsEnabled", + "assetTag", + "availableSpaceMb", + "batteryLevel", + "bluetoothLowEnergyCapable", + "bluetoothMacAddress", + "capacityMb", + "lostModeEnabledDate", + "declarativeDeviceManagementEnabled", + "deviceId", + "deviceLocatorServiceEnabled", + "devicePhoneNumber", + "diagnosticAndUsageReportingEnabled", + "displayName", + "doNotDisturbEnabled", + "enrollmentSessionTokenValid", + "exchangeDeviceId", + "cloudBackupEnabled", + "osBuild", + "osSupplementalBuildVersion", + "osVersion", + "osRapidSecurityResponse", + "ipAddress", + "itunesStoreAccountActive", + "mobileDeviceId", + "languages", + "lastBackupDate", + "lastEnrolledDate", + "lastCloudBackupDate", + "lastInventoryUpdateDate", + "locales", + "locationServicesForSelfServiceMobileEnabled", + "lostModeEnabled", + "managed", + "mdmProfileExpirationDate", + "model", + "modelIdentifier", + "modelNumber", + "modemFirmwareVersion", + "quotaSize", + "residentUsers", + "serialNumber", + "sharedIpad", + "supervised", + "tethered", + "timeZone", + "udid", + "usedSpacePercentage", + "wifiMacAddress", + "deviceOwnershipType", + "building", + "department", + "emailAddress", + "fullName", + "userPhoneNumber", + "position", + "room", + "username", + "appleCareId", + "leaseExpirationDate", + "lifeExpectancyYears", + "poDate", + "poNumber", + "purchasePrice", + "purchasedOrLeased", + "purchasingAccount", + "purchasingContact", + "vendor", + "warrantyExpirationDate", + "activationLockEnabled", + "blockEncryptionCapable", + "dataProtection", + "fileEncryptionCapable", + "hardwareEncryptionSupported", + "jailbreakStatus", + "passcodeCompliant", + "passcodeCompliantWithProfile", + "passcodeLockGracePeriodEnforcedSeconds", + "passcodePresent", + "personalDeviceProfileCurrent", + "carrierSettingsVersion", + "cellularTechnology", + "currentCarrierNetwork", + "currentMobileCountryCode", + "currentMobileNetworkCode", + "dataRoamingEnabled", + "eid", + "network", + "homeMobileCountryCode", + "homeMobileNetworkCode", + "iccid", + "imei", + "imei2", + "meid", + "personalHotspotEnabled", + "voiceRoamingEnabled", + "roaming", +] + +get_mobile_device_inventory_v1_allowed_filter_fields = [ + "airPlayPassword", + "appAnalyticsEnabled", + "assetTag", + "availableSpaceMb", + "batteryLevel", + "bluetoothLowEnergyCapable", + "bluetoothMacAddress", + "capacityMb", + "declarativeDeviceManagementEnabled", + "deviceId", + "deviceLocatorServiceEnabled", + "devicePhoneNumber", + "diagnosticAndUsageReportingEnabled", + "displayName", + "doNotDisturbEnabled", + "exchangeDeviceId", + "cloudBackupEnabled", + "osBuild", + "osSupplementalBuildVersion", + "osVersion", + "osRapidSecurityResponse", + "ipAddress", + "itunesStoreAccountActive", + "mobileDeviceId", + "languages", + "locales", + "locationServicesForSelfServiceMobileEnabled", + "lostModeEnabled", + "managed", + "model", + "modelIdentifier", + "modelNumber", + "modemFirmwareVersion", + "quotaSize", + "residentUsers", + "serialNumber", + "sharedIpad", + "supervised", + "tethered", + "timeZone", + "udid", + "usedSpacePercentage", + "wifiMacAddress", + "building", + "department", + "emailAddress", + "fullName", + "userPhoneNumber", + "position", + "room", + "username", + "appleCareId", + "lifeExpectancyYears", + "poNumber", + "purchasePrice", + "purchasedOrLeased", + "purchasingAccount", + "purchasingContact", + "vendor", + "activationLockEnabled", + "blockEncryptionCapable", + "dataProtection", + "fileEncryptionCapable", + "passcodeCompliant", + "passcodeCompliantWithProfile", + "passcodeLockGracePeriodEnforcedSeconds", + "passcodePresent", + "personalDeviceProfileCurrent", + "carrierSettingsVersion", + "currentCarrierNetwork", + "currentMobileCountryCode", + "currentMobileNetworkCode", + "dataRoamingEnabled", + "eid", + "network", + "homeMobileCountryCode", + "homeMobileNetworkCode", + "iccid", + "imei", + "imei2", + "meid", + "personalHotspotEnabled", + "roaming", +] From 9137c6410af3e62e1b8dfa600926021a8e8cc729 Mon Sep 17 00:00:00 2001 From: Bryson Tyrrell Date: Tue, 13 Feb 2024 00:24:14 -0600 Subject: [PATCH 02/11] Mobile Device models --- src/jamf_pro_sdk/models/pro/mobile_devices.py | 305 ++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 src/jamf_pro_sdk/models/pro/mobile_devices.py diff --git a/src/jamf_pro_sdk/models/pro/mobile_devices.py b/src/jamf_pro_sdk/models/pro/mobile_devices.py new file mode 100644 index 0000000..ab37733 --- /dev/null +++ b/src/jamf_pro_sdk/models/pro/mobile_devices.py @@ -0,0 +1,305 @@ +from datetime import datetime +from enum import Enum +from typing import List, Optional + +from pydantic import ConfigDict + +from .. import BaseModel + + +class MobileDeviceType(str, Enum): + iOS: str = "iOS" + tvOS: str = "tvOS" + + +class MobileDeviceExtensionAttributeType(str, Enum): + STRING: str = "STRING" + INTEGER: str = "INTEGER" + DATE: str = "DATE" + + +class MobileDeviceExtensionAttribute(BaseModel): + model_config = ConfigDict(extra="allow") + + id: Optional[str] = None + name: Optional[str] = None + type: Optional[MobileDeviceExtensionAttributeType] = None + value: Optional[List[str]] = None + extensionAttributeCollectionAllowed: Optional[bool] = None + inventoryDisplay: Optional[str] = None + + +class MobileDeviceHardware(BaseModel): + model_config = ConfigDict(extra="allow") + + capacityMb: Optional[int] = None + availableSpaceMb: Optional[int] = None + usedSpacePercentage: Optional[int] = None + batteryLevel: Optional[int] = None + serialNumber: Optional[str] = None + wifiMacAddress: Optional[str] = None + bluetoothMacAddress: Optional[str] = None + modemFirmwareVersion: Optional[str] = None + model: Optional[str] = None + modelIdentifier: Optional[str] = None + modelNumber: Optional[str] = None + bluetoothLowEnergyCapable: Optional[bool] = None + deviceId: Optional[str] = None + extensionAttributes: Optional[List[MobileDeviceExtensionAttribute]] = None + + +class MobileDeviceUserAndLocation(BaseModel): + model_config = ConfigDict(extra="allow") + + username: Optional[str] = None + realName: Optional[str] = None + emailAddress: Optional[str] = None + position: Optional[str] = None + phoneNumber: Optional[str] = None + departmentId: Optional[str] = None + buildingId: Optional[str] = None + room: Optional[str] = None + building: Optional[str] = None + department: Optional[str] = None + extensionAttributes: Optional[List[MobileDeviceExtensionAttribute]] = None + + +class MobileDevicePurchasing(BaseModel): + model_config = ConfigDict(extra="allow") + + purchased: Optional[bool] = None + leased: Optional[bool] = None + poNumber: Optional[str] = None + vendor: Optional[str] = None + appleCareId: Optional[str] = None + purchasePrice: Optional[str] = None + purchasingAccount: Optional[str] = None + poDate: Optional[datetime] = None + warrantyExpiresDate: Optional[datetime] = None + leaseExpiresDate: Optional[datetime] = None + lifeExpectancy: Optional[int] = None + purchasingContact: Optional[str] = None + extensionAttributes: Optional[List[MobileDeviceExtensionAttribute]] = None + + +class MobileDeviceApplication(BaseModel): + model_config = ConfigDict(extra="allow") + + identifier: Optional[str] = None + name: Optional[str] = None + version: Optional[str] = None + shortVersion: Optional[str] = None + managementStatus: Optional[str] = None + validationStatus: Optional[bool] = None + bundleSize: Optional[str] = None + dynamicSize: Optional[str] = None + + +class MobileDeviceCertificate(BaseModel): + model_config = ConfigDict(extra="allow") + + commonName: Optional[str] = None + identity: Optional[bool] = None + expirationDate: Optional[datetime] = None + + +class MobileDeviceProfile(BaseModel): + model_config = ConfigDict(extra="allow") + + displayName: Optional[str] = None + version: Optional[str] = None + uuid: Optional[str] = None + identifier: Optional[str] = None + removable: Optional[bool] = None + lastInstalled: Optional[datetime] = None + + +class MobileDeviceUserProfile(MobileDeviceProfile): + model_config = ConfigDict(extra="allow") + + username: Optional[str] = None + + +class MobileDeviceOwnershipType(str, Enum): + Institutional: str = "Institutional" + PersonalDeviceProfile: str = "PersonalDeviceProfile" + UserEnrollment: str = "UserEnrollment" + AccountDrivenUserEnrollment: str = "AccountDrivenUserEnrollment" + AccountDrivenDeviceEnrollment: str = "AccountDrivenDeviceEnrollment" + + +class MobileDeviceEnrollmentMethodPrestage(BaseModel): + model_config = ConfigDict(extra="allow") + + mobileDevicePrestageId: Optional[str] = None + profileName: Optional[str] = None + + +class MobileDeviceGeneral(BaseModel): + model_config = ConfigDict(extra="allow") + + udid: Optional[str] = None + displayName: Optional[str] = None + assetTag: Optional[str] = None + siteId: Optional[str] = None + lastInventoryUpdateDate: Optional[datetime] = None + osVersion: Optional[str] = None + osRapidSecurityResponse: Optional[str] = None + osBuild: Optional[str] = None + osSupplementalBuildVersion: Optional[str] = None + softwareUpdateDeviceId: Optional[str] = None + ipAddress: Optional[str] = None + managed: Optional[bool] = None + supervised: Optional[bool] = None + deviceOwnershipType = Optional[MobileDeviceOwnershipType] + enrollmentMethodPrestage: Optional[MobileDeviceEnrollmentMethodPrestage] = None + enrollmentSessionTokenValid: Optional[bool] = None + lastEnrolledDate: Optional[datetime] = None + mdmProfileExpirationDate: Optional[datetime] = None + timeZone: Optional[str] = None + declarativeDeviceManagementEnabled: Optional[bool] = None + extensionAttributes: Optional[List[MobileDeviceExtensionAttribute]] = None + airPlayPassword: Optional[str] = None + locales: Optional[str] = None + languages: Optional[str] = None + + +class MobileDeviceSecurityLostModeLocation(BaseModel): + """iOS devices only.""" + + model_config = ConfigDict(extra="allow") + + lastLocationUpdate: Optional[datetime] = None + lostModeLocationHorizontalAccuracyMeters: Optional[int] = None + lostModeLocationVerticalAccuracyMeters: Optional[int] = None + lostModeLocationAltitudeMeters: Optional[int] = None + lostModeLocationSpeedMetersPerSecond: Optional[int] = None + lostModeLocationCourseDegrees: Optional[int] = None + lostModeLocationTimestamp: Optional[str] = None + + +class MobileDeviceSecurity(BaseModel): + """iOS devices only.""" + + model_config = ConfigDict(extra="allow") + + dataProtected: Optional[bool] = None + blockLevelEncryptionCapable: Optional[bool] = None + fileLevelEncryptionCapable: Optional[bool] = None + passcodePresent: Optional[bool] = None + passcodeCompliant: Optional[bool] = None + passcodeCompliantWithProfile: Optional[bool] = None + hardwareEncryption: Optional[int] = None + activationLockEnabled: Optional[bool] = None + jailBreakDetected: Optional[bool] = None + passcodeLockGracePeriodEnforcedSeconds: Optional[int] = None + personalDeviceProfileCurrent: Optional[bool] = None + lostModeEnabled: Optional[bool] = None + lostModePersistent: Optional[bool] = None + lostModeMessage: Optional[str] = None + lostModePhoneNumber: Optional[str] = None + lostModeFootnote: Optional[str] = None + lostModeLocation: Optional[MobileDeviceSecurityLostModeLocation] = None + + +class MobileDeviceEbook(BaseModel): + """iOS devices only.""" + + model_config = ConfigDict(extra="allow") + + author: Optional[str] = None + title: Optional[str] = None + version: Optional[str] = None + kind: Optional[str] = None + managementState: Optional[str] = None + + +class MobileDeviceNetwork(BaseModel): + """iOS devices only.""" + + model_config = ConfigDict(extra="allow") + + cellularTechnology: Optional[str] = None + voiceRoamingEnabled: Optional[bool] = None + imei: Optional[str] = None + iccid: Optional[str] = None + meid: Optional[str] = None + eid: Optional[str] = None + carrierSettingsVersion: Optional[str] = None + currentCarrierNetwork: Optional[str] = None + currentMobileCountryCode: Optional[str] = None + currentMobileNetworkCode: Optional[str] = None + homeCarrierNetwork: Optional[str] = None + homeMobileCountryCode: Optional[str] = None + homeMobileNetworkCode: Optional[str] = None + dataRoamingEnabled: Optional[bool] = None + roaming: Optional[bool] = None + personalHotspotEnabled: Optional[bool] = None + phoneNumber: Optional[str] = None + + +class MobileDeviceServiceSubscription(BaseModel): + """iOS devices only.""" + + model_config = ConfigDict(extra="allow") + + carrierSettingsVersion: Optional[str] = None + currentCarrierNetwork: Optional[str] = None + currentMobileCountryCode: Optional[str] = None + currentMobileNetworkCode: Optional[str] = None + subscriberCarrierNetwork: Optional[str] = None + eid: Optional[str] = None + iccid: Optional[str] = None + imei: Optional[str] = None + dataPreferred: Optional[bool] = None + roaming: Optional[bool] = None + voicePreferred: Optional[bool] = None + label: Optional[str] = None + labelId: Optional[str] = None + meid: Optional[str] = None + phoneNumber: Optional[str] = None + slot: Optional[str] = None + + +class ProvisioningProfile(BaseModel): + """iOS devices only.""" + + model_config = ConfigDict(extra="allow") + + displayName: Optional[str] = None + uuid: Optional[str] = None + expirationDate: Optional[datetime] = None + + +class SharedUser(BaseModel): + """iOS devices only.""" + + model_config = ConfigDict(extra="allow") + + managedAppleId: Optional[str] = None + loggedIn: Optional[bool] = None + dataToSync: Optional[bool] = None + + +class MobileDevice(BaseModel): + """Represents a full mobile device inventory record.""" + + model_config = ConfigDict(extra="allow") + + mobileDeviceId: Optional[str] = None + deviceType: Optional[MobileDeviceType] = None + hardware: Optional[MobileDeviceHardware] = None + userAndLocation: Optional[MobileDeviceUserAndLocation] = None + purchasing: Optional[MobileDevicePurchasing] = None + applications: Optional[List[MobileDeviceApplication]] = None + certificates: Optional[List[MobileDeviceCertificate]] = None + profiles: Optional[List[MobileDeviceProfile]] = None + userProfiles: Optional[List[MobileDeviceUserProfile]] = None + extensionAttributes: Optional[List[MobileDeviceExtensionAttribute]] = None + general: Optional[MobileDeviceGeneral] = None + security: Optional[MobileDeviceSecurity] = None + ebooks: Optional[List[MobileDeviceEbook]] = None + network: Optional[MobileDeviceNetwork] = None + serviceSubscriptions: Optional[List[MobileDeviceServiceSubscription]] = None + provisioningProfiles: Optional[List[ProvisioningProfile]] = None + sharedUsers: Optional[List[SharedUser]] From 5a22a5cd8c938f4d0802508cc9766fad645781d9 Mon Sep 17 00:00:00 2001 From: Bryson Tyrrell Date: Tue, 13 Feb 2024 08:33:06 -0600 Subject: [PATCH 03/11] Rename mobile device options v1 -> v2 --- src/jamf_pro_sdk/models/pro/api_options.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jamf_pro_sdk/models/pro/api_options.py b/src/jamf_pro_sdk/models/pro/api_options.py index ed80a7e..0d6f00e 100644 --- a/src/jamf_pro_sdk/models/pro/api_options.py +++ b/src/jamf_pro_sdk/models/pro/api_options.py @@ -224,7 +224,7 @@ "UNKNOWN", ] -get_mobile_device_inventory_v1_allowed_sections = [ +get_mobile_device_inventory_v2_allowed_sections = [ "GENERAL", "HARDWARE", "USER_AND_LOCATION", @@ -242,7 +242,7 @@ "EXTENSION_ATTRIBUTES", ] -get_mobile_device_inventory_v1_allowed_sort_fields = [ +get_mobile_device_inventory_v2_allowed_sort_fields = [ "airPlayPassword", "appAnalyticsEnabled", "assetTag", @@ -343,7 +343,7 @@ "roaming", ] -get_mobile_device_inventory_v1_allowed_filter_fields = [ +get_mobile_device_inventory_v2_allowed_filter_fields = [ "airPlayPassword", "appAnalyticsEnabled", "assetTag", From fdbc9e36e684a7472349d92a485889081c264b62 Mon Sep 17 00:00:00 2001 From: Bryson Tyrrell Date: Tue, 13 Feb 2024 08:33:45 -0600 Subject: [PATCH 04/11] Pro get_mobile_device_inventory_v2 --- src/jamf_pro_sdk/clients/pro_api/__init__.py | 96 ++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/jamf_pro_sdk/clients/pro_api/__init__.py b/src/jamf_pro_sdk/clients/pro_api/__init__.py index d368eda..2f79ca2 100644 --- a/src/jamf_pro_sdk/clients/pro_api/__init__.py +++ b/src/jamf_pro_sdk/clients/pro_api/__init__.py @@ -20,6 +20,7 @@ SetRecoveryLockCommand, ShutDownDeviceCommand, ) +from ...models.pro.mobile_devices import MobileDevice from .pagination import Paginator if TYPE_CHECKING: @@ -259,6 +260,7 @@ def get_mdm_commands_v2( self, filter_expression: FilterExpression, start_page: int = 0, + end_page: int = None, page_size: int = 100, sort_expression: SortExpression = None, return_generator: bool = False, @@ -279,6 +281,10 @@ def get_mdm_commands_v2( :class:`Paginator` for more information. :type start_page: int + :param end_page: (optional) The page to end returning results at. See :class:`Paginator` for + more information. + :type start_page: int + :param page_size: (optional) The number of results to include in each requested page. See :class:`Paginator` for more information. :type page_size: int @@ -319,6 +325,96 @@ def get_mdm_commands_v2( resource_path="v2/mdm/commands", return_model=MdmCommandStatus, start_page=start_page, + end_page=end_page, + page_size=page_size, + sort_expression=sort_expression, + filter_expression=filter_expression, + ) + + return paginator(return_generator=return_generator) + + def get_mobile_device_inventory_v2( + self, + sections: List[str] = None, + start_page: int = 0, + end_page: int = None, + page_size: int = 100, + sort_expression: SortExpression = None, + filter_expression: FilterExpression = None, + return_generator: bool = False, + ) -> Union[List[MobileDevice], Iterator[Page]]: + """Returns a list of mobile device (iOS and tvOS) inventory records. + + :param sections: (optional) Select which sections of the computer's details to return. If + not specific the request will default to ``GENERAL``. If ``ALL`` is passed then all + sections will be returned. + + Allowed sections: + + .. autoapioptions:: jamf_pro_sdk.models.pro.api_options.get_mobile_device_inventory_v2_allowed_sections + + :type sections: List[str] + + :param start_page: (optional) The page to begin returning results from. See + :class:`Paginator` for more information. + :type start_page: int + + :param end_page: (optional) The page to end returning results at. See :class:`Paginator` for + more information. + :type start_page: int + + :param page_size: (optional) The number of results to include in each requested page. See + :class:`Paginator` for more information. + :type page_size: int + + :param sort_expression: (optional) The sort fields to apply to the request. See the + documentation for :ref:`Pro API Sorting` for more information. + + Allowed sort fields: + + .. autoapioptions:: jamf_pro_sdk.models.pro.api_options.get_mobile_device_inventory_v2_allowed_sort_fields + + :type sort_expression: SortExpression + + :param filter_expression: (optional) The filter expression to apply to the request. See the + documentation for :ref:`Pro API Filtering` for more information. + + Allowed filter fields: + + .. autoapioptions:: jamf_pro_sdk.models.pro.api_options.get_mobile_device_inventory_v2_allowed_filter_fields + + :type filter_expression: FilterExpression + + :param return_generator: If ``True`` a generator is returned to iterate over pages. By + default, the results for all pages will be returned in a single response. + :type return_generator: bool + + :return: List of computers OR a paginator generator. + :rtype: List[~jamf_pro_sdk.models.pro.mobile_devices.MobileDevice] | Iterator[Page] + + """ + if not sections: + sections = ["GENERAL"] + elif "ALL" in sections: + sections = get_mobile_device_inventory_v2_allowed_sections[1:] + + if not all([i in get_mobile_device_inventory_v2_allowed_sections for i in sections]): + raise ValueError( + f"Values for 'sections' must be one of: {', '.join(get_mobile_device_inventory_v2_allowed_sections)}" + ) + + if sort_expression: + sort_expression.validate(get_mobile_device_inventory_v2_allowed_sort_fields) + + if filter_expression: + filter_expression.validate(get_mobile_device_inventory_v2_allowed_filter_fields) + + paginator = Paginator( + api_client=self, + resource_path="v1/mobile-devices/detail", + return_model=MobileDevice, + start_page=start_page, + end_page=end_page, page_size=page_size, sort_expression=sort_expression, filter_expression=filter_expression, From c428f8813261e3763e3c968ef147525084970f38 Mon Sep 17 00:00:00 2001 From: Bryson Tyrrell Date: Tue, 13 Feb 2024 08:52:07 -0600 Subject: [PATCH 05/11] Added integration test for get_mobile_device_inventory_v2. Fix path version. --- src/jamf_pro_sdk/clients/pro_api/__init__.py | 2 +- src/jamf_pro_sdk/models/pro/mobile_devices.py | 8 +++++--- tests/integration/conftest.py | 13 ++++++++----- .../integration/test_pro_client_mobile_devices.py | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 tests/integration/test_pro_client_mobile_devices.py diff --git a/src/jamf_pro_sdk/clients/pro_api/__init__.py b/src/jamf_pro_sdk/clients/pro_api/__init__.py index 2f79ca2..18ba644 100644 --- a/src/jamf_pro_sdk/clients/pro_api/__init__.py +++ b/src/jamf_pro_sdk/clients/pro_api/__init__.py @@ -411,7 +411,7 @@ def get_mobile_device_inventory_v2( paginator = Paginator( api_client=self, - resource_path="v1/mobile-devices/detail", + resource_path="v2/mobile-devices/detail", return_model=MobileDevice, start_page=start_page, end_page=end_page, diff --git a/src/jamf_pro_sdk/models/pro/mobile_devices.py b/src/jamf_pro_sdk/models/pro/mobile_devices.py index ab37733..9f37406 100644 --- a/src/jamf_pro_sdk/models/pro/mobile_devices.py +++ b/src/jamf_pro_sdk/models/pro/mobile_devices.py @@ -8,6 +8,8 @@ class MobileDeviceType(str, Enum): + """Not in use: the value of this attribute can be an undocumented state.""" + iOS: str = "iOS" tvOS: str = "tvOS" @@ -151,7 +153,7 @@ class MobileDeviceGeneral(BaseModel): ipAddress: Optional[str] = None managed: Optional[bool] = None supervised: Optional[bool] = None - deviceOwnershipType = Optional[MobileDeviceOwnershipType] + deviceOwnershipType: Optional[MobileDeviceOwnershipType] = None enrollmentMethodPrestage: Optional[MobileDeviceEnrollmentMethodPrestage] = None enrollmentSessionTokenValid: Optional[bool] = None lastEnrolledDate: Optional[datetime] = None @@ -287,7 +289,7 @@ class MobileDevice(BaseModel): model_config = ConfigDict(extra="allow") mobileDeviceId: Optional[str] = None - deviceType: Optional[MobileDeviceType] = None + deviceType: Optional[str] = None hardware: Optional[MobileDeviceHardware] = None userAndLocation: Optional[MobileDeviceUserAndLocation] = None purchasing: Optional[MobileDevicePurchasing] = None @@ -302,4 +304,4 @@ class MobileDevice(BaseModel): network: Optional[MobileDeviceNetwork] = None serviceSubscriptions: Optional[List[MobileDeviceServiceSubscription]] = None provisioningProfiles: Optional[List[ProvisioningProfile]] = None - sharedUsers: Optional[List[SharedUser]] + sharedUsers: Optional[List[SharedUser]] = None diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index ec392c5..5c67ef8 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -4,15 +4,16 @@ import pytest from jamf_pro_sdk import ( - BasicAuthProvider, JamfProClient, SessionConfig, logger_quick_setup, ) +from jamf_pro_sdk.clients.auth import ApiClientCredentialsProvider -JAMF_PRO_HOST = os.getenv("JAMF_PRO_HOST", "dummy.jamfcloud.com") -JAMF_PRO_USERNAME = os.getenv("JAMF_PRO_USERNAME", "demo") -JAMF_PRO_PASS = os.getenv("JAMF_PRO_PASS", "tryitout") +# https://developer.jamf.com/developer-guide/docs/populating-dummy-data +JAMF_PRO_HOST = os.getenv("JAMF_PRO_HOST") +JAMF_PRO_CLIENT_ID = os.getenv("JAMF_PRO_CLIENT_ID") +JAMF_PRO_CLIENT_SECRET = os.getenv("JAMF_PRO_CLIENT_SECRET") # Run pytest with '-s' to view logging output logger_quick_setup(logging.DEBUG) @@ -22,7 +23,9 @@ def jamf_client(): client = JamfProClient( server=JAMF_PRO_HOST, - credentials=BasicAuthProvider(username=JAMF_PRO_USERNAME, password=JAMF_PRO_PASS), + credentials=ApiClientCredentialsProvider( + client_id=JAMF_PRO_CLIENT_ID, client_secret=JAMF_PRO_CLIENT_SECRET + ), session_config=SessionConfig(timeout=30), ) diff --git a/tests/integration/test_pro_client_mobile_devices.py b/tests/integration/test_pro_client_mobile_devices.py new file mode 100644 index 0000000..b3c0048 --- /dev/null +++ b/tests/integration/test_pro_client_mobile_devices.py @@ -0,0 +1,14 @@ +# from jamf_pro_sdk.clients.pro_api.pagination import FilterField, SortField + + +def test_integration_pro_computer_inventory_v1_default(jamf_client): + # This test is only valid if the computer inventory is less than the max page size + + # Test at max page size to get full inventory count + result_one_call = jamf_client.pro_api.get_mobile_device_inventory_v2(page_size=2000) + result_total_count = len(result_one_call) + assert result_total_count > 1 + + # Test paginated response matches full inventory count above + result_paginated = jamf_client.pro_api.get_mobile_device_inventory_v2(page_size=10) + assert result_total_count == len(result_paginated) From 965aa2f22415bbb23fe844456754b124052c165a Mon Sep 17 00:00:00 2001 From: Bryson Tyrrell Date: Tue, 13 Feb 2024 09:05:52 -0600 Subject: [PATCH 06/11] Add mobile device models to docs --- docs/reference/models_pro.rst | 27 +++++++++++++++++++ src/jamf_pro_sdk/models/pro/mobile_devices.py | 2 ++ 2 files changed, 29 insertions(+) diff --git a/docs/reference/models_pro.rst b/docs/reference/models_pro.rst index 2987054..bb7dbae 100644 --- a/docs/reference/models_pro.rst +++ b/docs/reference/models_pro.rst @@ -84,6 +84,33 @@ MDM Commands MdmCommandStatusStates MdmCommandStatusTypes +Mobile Devices +-------------- + +.. currentmodule:: jamf_pro_sdk.models.pro.mobile_devices + +.. autosummary:: + :toctree: _autosummary + + MobileDevice + MobileDeviceHardware + MobileDeviceUserAndLocation + MobileDevicePurchasing + MobileDeviceApplication + MobileDeviceCertificate + MobileDeviceProfile + MobileDeviceUserProfile + MobileDeviceExtensionAttribute + MobileDeviceGeneral + MobileDeviceOwnershipType + MobileDeviceEnrollmentMethodPrestage + MobileDeviceSecurity + MobileDeviceEbook + MobileDeviceNetwork + MobileDeviceServiceSubscription + ProvisioningProfile + SharedUser + Pagination ---------- diff --git a/src/jamf_pro_sdk/models/pro/mobile_devices.py b/src/jamf_pro_sdk/models/pro/mobile_devices.py index 9f37406..e79a955 100644 --- a/src/jamf_pro_sdk/models/pro/mobile_devices.py +++ b/src/jamf_pro_sdk/models/pro/mobile_devices.py @@ -117,6 +117,8 @@ class MobileDeviceProfile(BaseModel): class MobileDeviceUserProfile(MobileDeviceProfile): + """Extends :class:`~jamf_pro_sdk.models.pro.mobile_devices.MobileDeviceProfile`.""" + model_config = ConfigDict(extra="allow") username: Optional[str] = None From e39f576836c1807cdf78bbc8313ede2b1c7e13a0 Mon Sep 17 00:00:00 2001 From: Bryson Tyrrell Date: Tue, 13 Feb 2024 09:12:19 -0600 Subject: [PATCH 07/11] Add integration tests as non-failing job --- .github/workflows/main_pr_tests.yaml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main_pr_tests.yaml b/.github/workflows/main_pr_tests.yaml index 15d5d87..abb3dcf 100644 --- a/.github/workflows/main_pr_tests.yaml +++ b/.github/workflows/main_pr_tests.yaml @@ -13,7 +13,7 @@ permissions: contents: read jobs: - automated-tests: + setup-env: runs-on: ubuntu-latest steps: - name: Checkout @@ -28,8 +28,20 @@ jobs: - name: Install run: make install + automated-tests: + needs: setup-env + runs-on: ubuntu-latest + steps: - name: Lint Checker run: make lint - name: Tests run: make test + + exp-integration-tests: + needs: setup-env + continue-on-error: true + runs-on: ubuntu-latest + steps: + - name: Run Integration Tests + run: make test-all From 629fcc479ec635b0883df9ff027a2c8748242424 Mon Sep 17 00:00:00 2001 From: Bryson Tyrrell Date: Tue, 13 Feb 2024 09:29:17 -0600 Subject: [PATCH 08/11] Integration test job needs its own environment - revert changes to standard lints/tests. --- .github/workflows/main_pr_tests.yaml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main_pr_tests.yaml b/.github/workflows/main_pr_tests.yaml index abb3dcf..4bd686d 100644 --- a/.github/workflows/main_pr_tests.yaml +++ b/.github/workflows/main_pr_tests.yaml @@ -28,10 +28,6 @@ jobs: - name: Install run: make install - automated-tests: - needs: setup-env - runs-on: ubuntu-latest - steps: - name: Lint Checker run: make lint @@ -39,9 +35,17 @@ jobs: run: make test exp-integration-tests: - needs: setup-env continue-on-error: true runs-on: ubuntu-latest steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + cache: 'pip' + - name: Run Integration Tests run: make test-all From 61ae9a609429f5834930926f5efdd910a900b100 Mon Sep 17 00:00:00 2001 From: Bryson Tyrrell Date: Tue, 13 Feb 2024 09:40:43 -0600 Subject: [PATCH 09/11] Missed install step --- .github/workflows/main_pr_tests.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main_pr_tests.yaml b/.github/workflows/main_pr_tests.yaml index 4bd686d..ebddcd7 100644 --- a/.github/workflows/main_pr_tests.yaml +++ b/.github/workflows/main_pr_tests.yaml @@ -47,5 +47,8 @@ jobs: python-version: '3.9' cache: 'pip' + - name: Install + run: make install + - name: Run Integration Tests run: make test-all From 752b17193632fa45e73fe4bd963e8b6844163475 Mon Sep 17 00:00:00 2001 From: Bryson Tyrrell Date: Tue, 13 Feb 2024 09:41:31 -0600 Subject: [PATCH 10/11] Add --force-reinstall to make command to force updates (in line with PR tests). Fix linting for new version of black. Fix config for updated version of ruff. --- Makefile | 2 +- pyproject.toml | 16 ++++++++-------- src/jamf_pro_sdk/clients/pro_api/pagination.py | 8 +++++--- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 153e7d0..1ceceb0 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ SHELL := /bin/bash .PHONY: docs install: - python3 -m pip install --upgrade --editable '.[dev]' + python3 -m pip install --upgrade --force-reinstall --editable '.[dev]' uninstall: python3 -m pip uninstall -y -r <(python3 -m pip freeze) diff --git a/pyproject.toml b/pyproject.toml index f70fa88..87f07d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,13 @@ line-length = 100 [tool.ruff] line-length = 100 target-version = "py39" +src = [ + "src", + "tests" +] + + +[tool.ruff.lint] select = [ "E101", "F401", @@ -93,14 +100,7 @@ select = [ "N802", "N806" ] -src = [ - "src", - "tests" -] - - -[tool.ruff.per-file-ignores] -"__init__.py" = ["F401"] +per-file-ignores = {"__init__.py" = ["F401"]} [tool.pytest.ini_options] diff --git a/src/jamf_pro_sdk/clients/pro_api/pagination.py b/src/jamf_pro_sdk/clients/pro_api/pagination.py index 2f889aa..d4d8627 100644 --- a/src/jamf_pro_sdk/clients/pro_api/pagination.py +++ b/src/jamf_pro_sdk/clients/pro_api/pagination.py @@ -228,9 +228,11 @@ def _paginated_request(self, page: int) -> Page: page=page, page_count=len(response["results"]), total_count=response["totalCount"], - results=[self.return_model.model_validate(i) for i in response["results"]] - if self.return_model - else response["results"], + results=( + [self.return_model.model_validate(i) for i in response["results"]] + if self.return_model + else response["results"] + ), ) def _request(self) -> Iterator[Page]: From d9b1a13bdfc771f8bf9d8503c8aa2286ec81f840 Mon Sep 17 00:00:00 2001 From: Bryson Tyrrell Date: Tue, 13 Feb 2024 09:46:23 -0600 Subject: [PATCH 11/11] Add in repo vars --- .github/workflows/main_pr_tests.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main_pr_tests.yaml b/.github/workflows/main_pr_tests.yaml index ebddcd7..111fc5d 100644 --- a/.github/workflows/main_pr_tests.yaml +++ b/.github/workflows/main_pr_tests.yaml @@ -52,3 +52,7 @@ jobs: - name: Run Integration Tests run: make test-all + env: + JAMF_PRO_HOST: ${{ vars.JAMF_PRO_HOST }} + JAMF_PRO_CLIENT_ID: ${{ vars.JAMF_PRO_CLIENT_ID }} + JAMF_PRO_CLIENT_SECRET: ${{ vars.JAMF_PRO_CLIENT_SECRET }}