ruby-jss 4.2.0b2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +25 -7
  3. data/README-2.0.0.md +19 -8
  4. data/README.md +43 -30
  5. data/lib/jamf/api/classic/api_objects/patch_title.rb +0 -2
  6. data/lib/jamf/api/jamf_pro/api_objects/api_client.rb +3 -0
  7. data/lib/jamf/api/jamf_pro/api_objects/api_role.rb +3 -0
  8. data/lib/jamf/api/jamf_pro/api_objects/j_package.rb +307 -119
  9. data/lib/jamf/api/jamf_pro/api_objects/managed_software_updates/plan.rb +237 -0
  10. data/lib/jamf/api/jamf_pro/api_objects/managed_software_updates.rb +291 -0
  11. data/lib/jamf/api/jamf_pro/base_classes/oapi_object.rb +8 -0
  12. data/lib/jamf/api/jamf_pro/mixins/collection_resource.rb +23 -3
  13. data/lib/jamf/api/jamf_pro/mixins/filterable.rb +8 -0
  14. data/lib/jamf/api/jamf_pro/mixins/jpapi_resource.rb +17 -3
  15. data/lib/jamf/api/jamf_pro/mixins/macos_managed_updates.rb +2 -0
  16. data/lib/jamf/api/jamf_pro/mixins/prestage.rb +3 -0
  17. data/lib/jamf/api/jamf_pro/oapi_schemas/account_group.rb +123 -0
  18. data/lib/jamf/api/jamf_pro/oapi_schemas/account_preferences_v1.rb +105 -0
  19. data/lib/jamf/api/jamf_pro/oapi_schemas/assign_remove_profile_response_sync_state.rb +112 -0
  20. data/lib/jamf/api/jamf_pro/oapi_schemas/auth_account_v1.rb +159 -0
  21. data/lib/jamf/api/jamf_pro/oapi_schemas/authentication_type.rb +97 -0
  22. data/lib/jamf/api/jamf_pro/oapi_schemas/{device_enrollment_disown_body.rb → available_updates.rb} +14 -10
  23. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_application.rb +124 -0
  24. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_attachment.rb +102 -0
  25. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_certificate.rb +151 -0
  26. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_configuration_profile.rb +118 -0
  27. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching.rb +372 -0
  28. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_alert.rb +120 -0
  29. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_cache_detail.rb +97 -0
  30. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_data_migration_error.rb +98 -0
  31. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_data_migration_error_user_info.rb +89 -0
  32. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_parent.rb +131 -0
  33. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_parent_alert.rb +105 -0
  34. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_parent_capabilities.rb +124 -0
  35. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_parent_details.rb +118 -0
  36. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_parent_local_network.rb +97 -0
  37. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_disk.rb +143 -0
  38. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_disk_encryption.rb +120 -0
  39. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_extension_attribute.rb +175 -0
  40. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_font.rb +93 -0
  41. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_general.rb +244 -0
  42. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_hardware.rb +264 -0
  43. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_ibeacon.rb +81 -0
  44. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_inventory.rb +276 -0
  45. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_inventory_file_vault.rb +127 -0
  46. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_licensed_software.rb +88 -0
  47. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_local_user_account.rb +196 -0
  48. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_mdm_capability.rb +88 -0
  49. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_operating_system.rb +148 -0
  50. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_package_receipts.rb +97 -0
  51. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_partition.rb +145 -0
  52. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_partition_encryption.rb +94 -0
  53. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_partition_file_vault2_state.rb +97 -0
  54. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_plugin.rb +93 -0
  55. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_prestage_v3.rb +129 -0
  56. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_printer.rb +99 -0
  57. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_purchase.rb +158 -0
  58. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_remote_management.rb +88 -0
  59. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_section.rb +109 -0
  60. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_security.rb +193 -0
  61. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_service.rb +81 -0
  62. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_software_update.rb +93 -0
  63. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_storage.rb +90 -0
  64. data/lib/jamf/api/jamf_pro/oapi_schemas/computer_user_and_location.rb +131 -0
  65. data/lib/jamf/api/jamf_pro/oapi_schemas/dss_declaration.rb +111 -0
  66. data/lib/jamf/api/jamf_pro/oapi_schemas/dss_declarations.rb +88 -0
  67. data/lib/jamf/api/jamf_pro/oapi_schemas/enrollment_method.rb +97 -0
  68. data/lib/jamf/api/jamf_pro/oapi_schemas/group_membership.rb +94 -0
  69. data/lib/jamf/api/jamf_pro/oapi_schemas/inventory_preload_extension_attribute.rb +89 -0
  70. data/lib/jamf/api/jamf_pro/oapi_schemas/location_information.rb +146 -0
  71. data/lib/jamf/api/jamf_pro/oapi_schemas/{package_manifest.rb → location_information_v2.rb} +35 -37
  72. data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan.rb +181 -0
  73. data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_event_store.rb +85 -0
  74. data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_group_post.rb +99 -0
  75. data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_post.rb +98 -0
  76. data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_post_response.rb +100 -0
  77. data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_toggle.rb +115 -0
  78. data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_toggle_status.rb +169 -0
  79. data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_toggle_status_wrapper.rb +91 -0
  80. data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plans.rb +102 -0
  81. data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_status.rb +173 -0
  82. data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_statuses.rb +102 -0
  83. data/lib/jamf/api/jamf_pro/oapi_schemas/mobile_device_prestage_name_v2.rb +94 -0
  84. data/lib/jamf/api/jamf_pro/oapi_schemas/mobile_device_prestage_names_v2.rb +118 -0
  85. data/lib/jamf/api/jamf_pro/oapi_schemas/plan_configuration_post.rb +142 -0
  86. data/lib/jamf/api/jamf_pro/oapi_schemas/plan_device.rb +105 -0
  87. data/lib/jamf/api/jamf_pro/oapi_schemas/plan_device_post.rb +97 -0
  88. data/lib/jamf/api/jamf_pro/oapi_schemas/plan_device_response.rb +96 -0
  89. data/lib/jamf/api/jamf_pro/oapi_schemas/plan_group_post.rb +96 -0
  90. data/lib/jamf/api/jamf_pro/oapi_schemas/plan_search_results.rb +89 -0
  91. data/lib/jamf/api/jamf_pro/oapi_schemas/plan_status.rb +127 -0
  92. data/lib/jamf/api/jamf_pro/oapi_schemas/{device_enrollment_prestage.rb → prestage_purchasing_information.rb} +51 -88
  93. data/lib/jamf/api/jamf_pro/oapi_schemas/prestage_purchasing_information_v2.rb +174 -0
  94. data/lib/jamf/api/jamf_pro/oapi_schemas/prestage_scope_assignment_v2.rb +94 -0
  95. data/lib/jamf/api/jamf_pro/oapi_schemas/v1_site.rb +91 -0
  96. data/lib/jamf/api/jamf_pro/oapi_schemas.rb +24 -1
  97. data/lib/jamf/composer.rb +114 -107
  98. data/lib/jamf/oapi_validate.rb +1 -0
  99. data/lib/jamf/version.rb +1 -1
  100. data/test/bin/runtests +2 -2
  101. data/test/lib/jamf_test/collection_tests.rb +10 -2
  102. data/test/tests/computer_group.rb +29 -12
  103. data/test/tests/{jp_building.rb → j_building.rb} +2 -2
  104. data/test/tests/j_package.rb +47 -0
  105. metadata +87 -8
@@ -0,0 +1,237 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "Apache License")
4
+ # with the following modification; you may not use this file except in
5
+ # compliance with the Apache License and the following modification to it:
6
+ # Section 6. Trademarks. is deleted and replaced with:
7
+ #
8
+ # 6. Trademarks. This License does not grant permission to use the trade
9
+ # names, trademarks, service marks, or product names of the Licensor
10
+ # and its affiliates, except as required to comply with Section 4(c) of
11
+ # the License and to reproduce the content of the NOTICE file.
12
+ #
13
+ # You may obtain a copy of the Apache License at
14
+ #
15
+ # http://www.apache.org/licenses/LICENSE-2.0
16
+ #
17
+ # Unless required by applicable law or agreed to in writing, software
18
+ # distributed under the Apache License with the above modification is
19
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20
+ # KIND, either express or implied. See the Apache License for the specific
21
+ # language governing permissions and limitations under the Apache License.
22
+
23
+ # frozen_string_literal: true
24
+
25
+ module Jamf
26
+
27
+ module ManagedSoftwareUpdates
28
+
29
+ # A ManagedSoftwareUpdate Plan contains the details for
30
+ # installing managed software updates via MDM/DDM on a device.
31
+ # When plans are created for a group there will be one for every member of the group
32
+ #
33
+ class Plan < Jamf::OAPISchemas::ManagedSoftwareUpdatePlan
34
+
35
+ # Mix-Ins
36
+ #####################################
37
+
38
+ include Jamf::CollectionResource
39
+ extend Jamf::Filterable
40
+ include Jamf::Immutable
41
+
42
+ ########### RELATED OAPI OBJECTS
43
+
44
+ # The OAPI object class we get back from a 'list' query to get the
45
+ # whole collection, or a subset of it. It contains a :results key
46
+ # which is an array of data for objects of the parent class.
47
+ SEARCH_RESULT_OBJECT = Jamf::OAPISchemas::ManagedSoftwareUpdatePlans
48
+
49
+ # The OAPI object class we send with a POST request to make a new member of
50
+ # the collection in Jamf. This is often the same as the parent class.
51
+ POST_OBJECT = Jamf::OAPISchemas::ManagedSoftwareUpdatePlanPost
52
+
53
+ GROUP_POST_OBJECT = Jamf::OAPISchemas::ManagedSoftwareUpdatePlanGroupPost
54
+
55
+ ############# API PATHS
56
+
57
+ # The path for GETting the list of all objects in the collection, possibly
58
+ # filtered, sorted, and/or paged
59
+ # REQUIRED for all collection resources
60
+ #
61
+ # GET_PATH, POST_PATH, PUT_PATH, PATCH_PATH, and DELETE_PATH are automatically
62
+ # assumed from the LIST_PATH if they follow the standards:
63
+ # - GET_PATH = "#{LIST_PATH}/id"
64
+ # - fetch an object from the collection
65
+ # - POST_PATH = LIST_PATH
66
+ # - create a new object in the collection
67
+ # - PUT_PATH = "#{LIST_PATH}/id"
68
+ # - update an object passing all its values back.
69
+ # Most objects use this or PATCH but not both
70
+ # - PATCH_PATH = "#{LIST_PATH}/id"
71
+ # - update an object passing some of its values back
72
+ # Most objects use this or PUT but not both
73
+ # - DELETE_PATH = "#{LIST_PATH}/id"
74
+ # - delete an object from the collection
75
+ #
76
+ # If those paths differ from the standards, the constants must be defined
77
+ # here
78
+ #
79
+ LIST_PATH = "#{MANAGED_SW_UPDATES_PATH}/plans"
80
+
81
+ # GETting this resource provides a list of existing group plans
82
+ # POSTing to this resource will initiate a new plan targeting a group of devices
83
+ GROUP_PLANS_PATH = "#{LIST_PATH}/group"
84
+
85
+ # Must define this when extending Filterable
86
+ FILTER_KEYS = %i[
87
+ planUuid device.deviceId device.objectType updateAction versionType specificVersion maxDeferrals recipeId forceInstallLocalDateTime state
88
+ ].freeze
89
+
90
+ GROUP_TYPES = {
91
+ computer: 'COMPUTER_GROUP',
92
+ mobile_device: 'MOBILE_DEVICE_GROUP'
93
+ }.freeze
94
+
95
+ # Class Methods
96
+ ######################################
97
+
98
+ # Get an Array of all plans for a given group, either computer or mobile device.
99
+ #
100
+ # @param group_id [Integer] the ID of the group to get plans for
101
+ # @param type [Symbol] the type of group, either :computer or :mobile_device
102
+ # @param cnx [Jamf::Connection] the connection to use, defaults to Jamf.cnx
103
+ # @return [Array<Jamf::ManagedSoftwareUpdates::Plan>] the plans for the group
104
+ ################################
105
+ def self.group_plans(group_id:, type:, cnx: Jamf.cnx)
106
+ gtype = GROUP_TYPES[type.to_sym]
107
+ raise ArgumentError, "Invalid group type: #{type}, must be one of :#{GROUP_TYPES.keys.join ', :'}" unless gtype
108
+
109
+ plans = Jamf.cnx.jp_get("#{GROUP_PLANS_PATH}/#{group_id}?group-type=#{gtype}")[:results]
110
+ plans.map do |plan_data|
111
+ plan_data[:cnx] = cnx
112
+ plan_data[:instantiate_me] = true
113
+ new(**plan_data)
114
+ end
115
+ end
116
+
117
+ # Get the declarations for a plan
118
+ #
119
+ # @param plan_uuid [String] the UUID of the plan to get declarations for
120
+ #
121
+ # @param cnx [Jamf::Connection] the connection to use, defaults to Jamf.cnx
122
+ #
123
+ # @return [Array<Jamf::OAPISchemas::DssDeclaration>] the declarations for the plan
124
+ ############################
125
+ def self.declarations(planUuid, cnx: Jamf.cnx)
126
+ cnx.jp_get("#{LIST_PATH}/#{planUuid}/declarations")[:declarations].map do |declaration|
127
+ Jamf::OAPISchemas::DssDeclaration.new(declaration)
128
+ end
129
+ end
130
+
131
+ # get the events for a plan
132
+ #
133
+ # BUG ? : At lease thru Jamf Pro 11.17.1, this endpoint returns a double-wrapped JSON object
134
+ # The first one is a Hash with one key :events, which is a String containing the JSON
135
+ # for the actual events - an Array of Hashes, which don't have an OAPI schema.
136
+ #
137
+ # Those Hashes look like this, but aren't consistent
138
+ #
139
+ # {:type=>".QueueAvailableOsUpdatesCommand",
140
+ # :eventSentEpoch=>1749158978751,
141
+ # :managementUUID=>"a94b11f0-c870-4006-82fe-e7afa981d61c",
142
+ # :processManagerUUID=>"a4b45b0a-4a46-4a52-8322-ab4f9895ab21",
143
+ # :availableOSUpdateDelay=>300},
144
+ #
145
+ # {:id=>4238,
146
+ # :type=>".AvailableOsUpdateRequestCompletedEvent",
147
+ # :deviceObjectId=>1,
148
+ # :managementUUID=>"a94b11f0-c870-4006-82fe-e7afa981d61c",
149
+ # :eventReceivedEpoch=>1749159337161,
150
+ # :processManagerUUID=>"a4b45b0a-4a46-4a52-8322-ab4f9895ab21",
151
+ # :availableOSUpdatesDto=>
152
+ # {:deviceObjectId=>1,
153
+ # :managementUUID=>"a94b11f0-c870-4006-82fe-e7afa981d61c",
154
+ # :availableOsUpdates=>
155
+ # [{:build=>"",
156
+ # :preview=>false,
157
+ # :version=>"16.4",
158
+ # :critical=>false,
159
+ # :productKey=>"082-41241",
160
+ # :installSize=>0,
161
+ # :productName=>"",
162
+ # :downloadSize=>882235914,
163
+ # :majorOSUpdate=>false,
164
+ # :firmwareUpdate=>false,
165
+ # :restartRequired=>false,
166
+ # :humanReadableName=>"Command Line Tools for Xcode",
167
+ # :allowsInstallLater=>true,
168
+ # :appIdentifiersToClose=>[],
169
+ # :configurationDataUpdate=>false},
170
+ # {:build=>"",
171
+ # :preview=>false,
172
+ # :version=>"5299",
173
+ # :critical=>false,
174
+ # :productKey=>"082-54857",
175
+ # :installSize=>0,
176
+ # :productName=>"",
177
+ # :downloadSize=>1256157,
178
+ # :majorOSUpdate=>false,
179
+ # :firmwareUpdate=>false,
180
+ # :restartRequired=>false,
181
+ # :humanReadableName=>"XProtectPlistConfigData",
182
+ # :allowsInstallLater=>true,
183
+ # :appIdentifiersToClose=>[],
184
+ # :configurationDataUpdate=>true}],
185
+ # :eventReceivedEpoch=>1749159337161
186
+ # }
187
+ # }
188
+ #
189
+ # note that some have id's and some don't. Some have response data (like the available OS updates)
190
+ # which can contain a more complex data structure.
191
+ #
192
+ # NOTE: This may be intentional, but is not documented in the JPAPI docs. The endpoint is "events"
193
+ # but what's returned is an 'event store'. Its possible that the data comes from Apple as JSON
194
+ # and Jamf is just passing it through.
195
+ # Awaiting clarification from Jamf on this.
196
+ #
197
+ # In any case, this method will unwrap the JSON and return the events as an Array of Hashes
198
+ #
199
+ # @param plan_uuid [String] the UUID of the plan to get events for
200
+ #
201
+ # @param cnx [Jamf::Connection] the connection to use, defaults to Jamf.cnx
202
+ #
203
+ # @return [Array<Hash>] the events for the plan
204
+ ############################
205
+ def self.event_store(planUuid, cnx: Jamf.cnx)
206
+ data = cnx.jp_get("#{LIST_PATH}/#{planUuid}/events")
207
+ if data[:events].is_a?(String) && data[:events].start_with?('{"events":')
208
+ JSON.parse data[:events], symbolize_names: true
209
+ else
210
+ data[:events]
211
+ end
212
+ end
213
+
214
+ # Instance Methods
215
+ ######################################
216
+
217
+ # get the declarations for this plan
218
+ #
219
+ # @return [Array<Jamf::OAPISchemas::DssDeclaration>] the declarations for the plan
220
+ ############################
221
+ def declarations
222
+ self.class.declarations(planUuid, cnx: cnx)
223
+ end
224
+
225
+ # get the events for this plan
226
+ #
227
+ # @return [Array<Jamf::OAPISchemas::DssDeclaration>] the events for the plan
228
+ ############################
229
+ def event_store
230
+ self.class.event_store(planUuid, cnx: cnx)
231
+ end
232
+
233
+ end # class Plan
234
+
235
+ end # module ManagedSoftwareUpdates
236
+
237
+ end # module Jamf
@@ -0,0 +1,291 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "Apache License")
4
+ # with the following modification; you may not use this file except in
5
+ # compliance with the Apache License and the following modification to it:
6
+ # Section 6. Trademarks. is deleted and replaced with:
7
+ #
8
+ # 6. Trademarks. This License does not grant permission to use the trade
9
+ # names, trademarks, service marks, or product names of the Licensor
10
+ # and its affiliates, except as required to comply with Section 4(c) of
11
+ # the License and to reproduce the content of the NOTICE file.
12
+ #
13
+ # You may obtain a copy of the Apache License at
14
+ #
15
+ # http://www.apache.org/licenses/LICENSE-2.0
16
+ #
17
+ # Unless required by applicable law or agreed to in writing, software
18
+ # distributed under the Apache License with the above modification is
19
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20
+ # KIND, either express or implied. See the Apache License for the specific
21
+ # language governing permissions and limitations under the Apache License.
22
+
23
+ # frozen_string_literal: true
24
+
25
+ module Jamf
26
+
27
+ # This module provides access to the Managed Software Updates endpoints of the Jamf Pro API.
28
+ #
29
+ # Sending managed software updates is done by creating a "plan" for one or more devices, or
30
+ # a group of devices, either computers or mobile devices. Plans are created with the
31
+ # {.send_managed_sw_update} module method, which takes the target devices or group, the update action,
32
+ # the version type, and other optional parameters such as specific version, build version.
33
+ #
34
+ # Once created/sent, Plans, identified by their planUuid, can be retrieved with the
35
+ # Jamf::ManagedSoftwareUpdate::Plan class which encapsulates the details of the plan, including
36
+ # the latest status of the update from the Jamf Pro server.
37
+ #
38
+ # You can also retrieve the status of any computer or group from the MDM server/Client Machines via the
39
+ # Jamf::ManagedSoftwareUpdates.status method, which returns an array of Status objects
40
+ # for the devices or group members you specify.
41
+ #
42
+ # TODO: We will integrate the ManagedSoftwareUpdate::Plan class into Jamf::Computer,
43
+ # Jamf::MobileDevice, Jamf::ComputerGroup, and Jamf::MobileDeviceGroup (and/or their JP API versions)
44
+ # as both a class method and an instance method, so that you can send updates directly
45
+ # from those classes and instances.
46
+ #
47
+ # We will probably not add support for the "feature-toggle" endpoints, since eventually
48
+ # this 'feature' will be the only way to do these updates, and that such toggling is better
49
+ # done in the web app.
50
+ #
51
+ module ManagedSoftwareUpdates
52
+
53
+ ############# API PATHS
54
+
55
+ MANAGED_SW_UPDATES_PATH = 'v1/managed-software-updates'
56
+
57
+ AVAILABLE_VERSIONS_PATH = "#{MANAGED_SW_UPDATES_PATH}/available-updates"
58
+
59
+ DEVICE_POST_PATH = "#{MANAGED_SW_UPDATES_PATH}/plans"
60
+
61
+ GROUP_POST_PATH = "#{MANAGED_SW_UPDATES_PATH}/plans/group"
62
+
63
+ STATUS_PATH = "#{MANAGED_SW_UPDATES_PATH}/update-statuses"
64
+
65
+ COMPUTER_STATUS_PATH = "#{STATUS_PATH}/computers"
66
+
67
+ COMPUTER_GROUP_STATUS_PATH = "#{STATUS_PATH}/computer-groups"
68
+
69
+ MOBILE_DEVICE_STATUS_PATH = "#{STATUS_PATH}/mobile-devices"
70
+
71
+ MOBILE_DEVICE_GROUP_STATUS_PATH = "#{STATUS_PATH}/mobile-device-groups"
72
+
73
+ STATUS_FILTER_KEYS = %i[
74
+ osUpdatesStatusId device.deviceId device.objectType downloaded downloadPercentComplete productKey status deferralsRemaining maxDeferrals nextScheduledInstall created updated
75
+ ].freeze
76
+
77
+ DEVICE_TYPES = Jamf::OAPISchemas::PlanDevicePost::OBJECT_TYPE_OPTIONS
78
+
79
+ GROUP_TYPES = Jamf::OAPISchemas::PlanGroupPost::OBJECT_TYPE_OPTIONS
80
+
81
+ UPDATE_ACTIONS = Jamf::OAPISchemas::PlanConfigurationPost::UPDATE_ACTION_OPTIONS
82
+
83
+ VERSION_TYPES = Jamf::OAPISchemas::PlanConfigurationPost::VERSION_TYPE_OPTIONS
84
+
85
+ # Class Methods
86
+ ######################################
87
+
88
+ # get the list of available OS versions for macOS and/or iOS/iPadOS
89
+ #
90
+ # @param cnx [Jamf::Connection] the connection to use, defaults to Jamf.cnx
91
+ # @return [Hash {Symbol => Array<String>}]
92
+ ###################
93
+ def self.available_os_updates(cnx: Jamf.cnx)
94
+ cnx.jp_get(AVAILABLE_VERSIONS_PATH)[:availableUpdates]
95
+ end
96
+
97
+ # Send an MDM/DDM OS update command to one or more target devices or a group
98
+ #
99
+ # TODO: Integrate this into Jamf::Computer, Jamf::MobileDevice, Jamf::ComputerGroup,
100
+ # and Jamf::MobileDeviceGroup as both a class method and an instance method.
101
+ #
102
+ # @param deviceIds [String, Integer, Array<String, Integer>] Required if no groupId is given.
103
+ # The Jamf ID for the device targets, may be integers or integer-strings.
104
+ #
105
+ # @param groupId [String, Integer] Requied if no deviceIds are given. The Jamf ID for
106
+ # the group target, may be integer or integer-string.
107
+ #
108
+ # @param targetType [Symbol, String] Required. The type of device or group.
109
+ # For devices, one of :computer, :mobile_device, :apple_tv.
110
+ # For groups, one of :computer_group, :mobile_device_group.
111
+ #
112
+ # @param updateAction [Symbol, String] Required. One of :download_only, :download_install,
113
+ # :download_install_allow_deferral, :download_install_restart, :download_install_schedule.
114
+ #
115
+ # @param versionType [Symbol, String] Required. One of :latest_major, :latest_minor,
116
+ # :latest_any, :specific_version, :custom_version
117
+ #
118
+ # @param specificVersion [String] Optional. Indicates the specific version to update to.
119
+ # Only available when the versionType is set to specific_version or custom_version
120
+ #
121
+ # @param buildVersion [String] Optional. Indicates the build version to update to.
122
+ # Only available when the version type is set to custom version.
123
+ #
124
+ # @param maxDeferrals [Integer] Required when the provided updateAction is :download_install_allow_deferral.
125
+ # Not applicable to all updateActions.
126
+ #
127
+ # @param forceInstallLocalDateTime [Time, Jamf::Timestamp, String] Optional. The local date and time
128
+ # of the device to force update by. NOTE: This operation requires Declarative Device Management and
129
+ # is only available for Jamf Cloud customers.
130
+ #
131
+ # @param cnx [Jamf::Connection] The API connection to use. Defaults to Jamf.cnx
132
+ #
133
+ # @return [Hash {String => String}] deviceId => planUuid, for all devices targeted.
134
+ ########################
135
+ def self.send_managed_sw_update(targetType:, updateAction:, versionType:, deviceIds: nil, groupId: nil, cnx: Jamf.cnx, **opts)
136
+ deviceIds, groupId, targetType = validate_targets(targetType: targetType, deviceIds: deviceIds, groupId: groupId)
137
+
138
+ # Ensure we have a valid update action
139
+ updateAction = validate_update_action(updateAction)
140
+
141
+ # Ensure we have a valid version type
142
+ versionType = validate_version_type(versionType)
143
+
144
+ # Build the request body, starting with the common config
145
+ request_body = {
146
+ config: {
147
+ updateAction: updateAction,
148
+ versionType: versionType
149
+ }
150
+ }
151
+
152
+ request_body[:config][:specificVersion] = opts[:specificVersion] if opts[:specificVersion]
153
+ request_body[:config][:buildVersion] = opts[:buildVersion] if opts[:buildVersion]
154
+ request_body[:config][:maxDeferrals] = opts[:maxDeferrals] if opts[:maxDeferrals]
155
+ if opts[:forceInstallLocalDateTime]
156
+ time = Time.parse opts[:forceInstallLocalDateTime] unless opts[:forceInstallLocalDateTime].is_a? Time
157
+ request_body[:config][:forceInstallLocalDateTime] = time.strftime('%FT%T')
158
+ end
159
+
160
+ # Add the target information
161
+ if deviceIds
162
+ request_body[:devices] = deviceIds.map { |id| { deviceId: id, objectType: targetType } }
163
+ post_path = DEVICE_POST_PATH
164
+ else
165
+ request_body[:group] = {
166
+ groupId: groupId,
167
+ objectType: targetType
168
+ }
169
+ post_path = GROUP_POST_PATH
170
+ end
171
+
172
+ response = cnx.jp_post(post_path, request_body)
173
+ parsed_response = Jamf::OAPISchemas::ManagedSoftwareUpdatePlanPostResponse.new response
174
+
175
+ result = {}
176
+ parsed_response.plans.each do |plan|
177
+ result[plan.device.deviceId] = plan.planId
178
+ end
179
+
180
+ result
181
+ end
182
+
183
+ # Retrieve one or more ManagedSoftwareUpdateStatuses objects for a device, group members,
184
+ # or the result of an arbitrary filter on all Status objects.
185
+ #
186
+ # TODO: Integrate this into Jamf::Computer, Jamf::MobileDevice, Jamf::ComputerGroup,
187
+ # and Jamf::MobileDeviceGroup as both a class method and an instance method.
188
+ #
189
+ # @param type [Symbol] Required unless using a filter. One of :computer, :mobile_device,
190
+ # :mobile_device_group, :computer_group.
191
+ #
192
+ # @param id [Integer, String] Required unless using a filter. The Jamf ID of the device or group
193
+ #
194
+ # @param filter [String] Required unless providing type: and id:. An RSQL filter string to apply
195
+ # to the collection of Status objects. Available filter keys are: osUpdatesStatusId device.deviceId
196
+ # device.objectType downloaded downloadPercentComplete productKey status deferralsRemaining
197
+ # maxDeferrals nextScheduledInstall created updated
198
+ #
199
+ # @param cnx [Jamf::Connection] The API connection to use. Defaults to Jamf.cnx
200
+ #
201
+ # @return [Array<Jamf::OAPISchemas::ManagedSoftwareUpdateStatus>] The Status objects for the device
202
+ # or members of the group, or the filter results.
203
+ #
204
+ ###############################
205
+ def self.status(type: nil, id: nil, filter: nil, cnx: Jamf.cnx)
206
+ raise ArgumentError, 'Must provide either type and id, or a filter' if (type || id) && filter
207
+ raise ArgumentError, 'Must provide both type and id if using either' if (type || id) && !(type && id)
208
+
209
+ get_path =
210
+ case type
211
+ when :computer
212
+ "#{COMPUTER_STATUS_PATH}/#{id}"
213
+ when :mobile_device
214
+ "#{MOBILE_DEVICE_STATUS_PATH}/#{id}"
215
+ when :computer_group
216
+ "#{COMPUTER_GROUP_STATUS_PATH}/#{id}"
217
+ when :mobile_device_group
218
+ "#{MOBILE_DEVICE_GROUP_STATUS_PATH}/#{id}"
219
+ else
220
+ unless type.nil?
221
+ raise ArgumentError,
222
+ "Invalid type: #{type}, must be one of: #{%i[computer mobile_device computer_group mobile_device_group].join ', '}"
223
+ end
224
+ raise ArgumentError, 'filter required if not using type and id' unless filter
225
+
226
+ "#{STATUS_PATH}/?filter=#{CGI.escape filter}"
227
+ end
228
+
229
+ Jamf::OAPISchemas::ManagedSoftwareUpdateStatuses.new(cnx.jp_get(get_path)).results
230
+ end
231
+
232
+ # Validate the device or group ids and type, returen validated values.
233
+ #
234
+ # @param deviceIds [String, Integer, Array<String, Integer>] Required if no groupId is given.
235
+ # Identifiers for the device targets.
236
+ #
237
+ # @param groupId [String, Integer] Requied if no deviceIds are given. Identifier for
238
+ # the group target.
239
+ #
240
+ # @param type [Symbol, String] Required. The type of device or group.
241
+ # For devices, one of :computer, :mobile_device, :apple_tv.
242
+ # For groups, one of :computer_group, :mobile_device_group.
243
+ #
244
+ # @return [Hash] a hash with the keys :type, :deviceIds, and :groupId
245
+ ##########################
246
+ def self.validate_targets(targetType:, deviceIds: nil, groupId: nil)
247
+ raise ArgumentError, 'Must provide either deviceIds or groupId' if deviceIds.nil? && groupId.nil?
248
+ raise ArgumentError, 'Must specify either deviceIds or groupId, but not both' if deviceIds && groupId
249
+
250
+ targetType = targetType.to_s.upcase
251
+ if deviceIds
252
+ # Ensure we have a valid device type
253
+ raise ArgumentError, "Invalid device type: #{targetType}, mst be one of: #{DEVICE_TYPES.join ', '}" unless DEVICE_TYPES.include?(targetType)
254
+
255
+ deviceIds = [deviceIds] unless deviceIds.is_a?(Array)
256
+ else
257
+ # Ensure we have a valid group type
258
+ raise ArgumentError, "Invalid group type: #{targetType}, mst be one of: #{GROUP_TYPES.join ', '}" unless GROUP_TYPES.include?(targetType)
259
+ end
260
+
261
+ [deviceIds, groupId, targetType]
262
+ end
263
+ private_class_method :validate_targets
264
+
265
+ # Validate the update action, convert it to the format expected by the API
266
+ # @param updateAction [Symbol, String] the update action to validate
267
+ # @return [String] the update action in the format expected by the API
268
+ ###########################
269
+ def self.validate_update_action(updateAction)
270
+ updateAction = updateAction.to_s.upcase
271
+ raise ArgumentError, "Invalid updateAction: #{updateAction}, must be one of: #{UPDATE_ACTIONS.join ', '}" unless UPDATE_ACTIONS.include?(updateAction)
272
+
273
+ updateAction
274
+ end
275
+ private_class_method :validate_update_action
276
+
277
+ # Validate the version type, convert it to the format expected by the API
278
+ # @param versionType [Symbol, String] the version type to validate
279
+ # @return [String] the version type in the format expected by the API
280
+ #########################
281
+ def self.validate_version_type(versionType)
282
+ versionType = versionType.to_s.upcase
283
+ raise ArgumentError, "Invalid versionType: #{versionType}, must be one of: #{VERSION_TYPES.join ', '}" unless VERSION_TYPES.include?(versionType)
284
+
285
+ versionType
286
+ end
287
+ private_class_method :validate_version_type
288
+
289
+ end # module MacOSManagedUpdates
290
+
291
+ end # module Jamf
@@ -104,6 +104,11 @@ module Jamf
104
104
  create_setters attr_name, attr_def
105
105
  end # do |attr_name, attr_def|
106
106
 
107
+ if defined? self::OBJECT_NAME_ATTR
108
+ alias_method(:name, self::OBJECT_NAME_ATTR)
109
+ alias_method('name=', "#{self::OBJECT_NAME_ATTR}=")
110
+ end
111
+
107
112
  @oapi_properties_parsed = true
108
113
  end # parse_object_model
109
114
 
@@ -470,6 +475,9 @@ module Jamf
470
475
  # Comparable by the sha1 hash of our properties.
471
476
  # Subclasses or mixins may override this in ways that make
472
477
  # sense for them
478
+ # TODO: Using this may not make sense for most objects, esp
479
+ # when comparing objects instantiated from Create vs those
480
+ # from Fetch.
473
481
  def <=>(other)
474
482
  sha1_hash <=> other.sha1_hash
475
483
  end
@@ -380,6 +380,9 @@ module Jamf
380
380
  # if we're here, we should know our ident key and value
381
381
  raise ArgumentError, 'Required parameter "identifier: value", where identifier is id:, name: etc.' unless ident && value
382
382
 
383
+ # if the ident is :name, and there's a constant for the name attr, use that
384
+ ident = self::OBJECT_NAME_ATTR if defined?(self::OBJECT_NAME_ATTR) && ident == :name
385
+
383
386
  return raw_data_by_id(value, cnx: cnx) if ident == :id
384
387
  return unless identifiers.include? ident
385
388
 
@@ -421,6 +424,7 @@ module Jamf
421
424
  return pager(filter: "#{identifier}==\"#{value}\"", page_size: 1, cnx: cnx).page(:first).first if filterable? && filter_keys.include?(identifier)
422
425
 
423
426
  # otherwise we have to loop thru all the objects looking for the value
427
+ # which can be slow if there are lots of objects.
424
428
  cmp_val = value.to_s
425
429
  all(cnx: cnx).each do |data|
426
430
  return data if data[identifier].to_s.casecmp? cmp_val
@@ -469,7 +473,16 @@ module Jamf
469
473
  #
470
474
  ######################################
471
475
  def valid_id(searchterm = nil, cnx: Jamf.cnx, **ident_and_val)
472
- raw_data(searchterm, cnx: cnx, **ident_and_val)&.dig(:id)
476
+ data =
477
+ if ident_and_val.empty?
478
+ raw_data(searchterm, cnx: cnx)
479
+ else
480
+ ident = ident_and_val.keys.first
481
+ value = ident_and_val.values.first
482
+ raw_data(cnx: cnx, ident: ident, value: value)
483
+ end
484
+
485
+ data&.dig(:id)
473
486
  end
474
487
 
475
488
  # By default, Collection Resources are creatable,
@@ -585,28 +598,35 @@ module Jamf
585
598
 
586
599
  # Dynamically create_identifier_list_methods
587
600
  # when one is called.
601
+ ######################################
588
602
  def method_missing(method, *args, &block)
589
603
  if available_list_methods.key? method.to_s
590
604
  attr_name = available_list_methods[method.to_s]
591
605
  create_identifier_list_method attr_name.to_sym, method
592
606
  send method, *args
607
+ elsif method.to_s == 'all_names' && defined?(self::OBJECT_NAME_ATTR)
608
+ define_singleton_method(:all_names) do |_refresh = nil, cnx: Jamf.cnx, cached_list: nil|
609
+ send "all_#{self::OBJECT_NAME_ATTR}s", *args
610
+ end
611
+ send method, *args
593
612
  else
594
613
  super
595
614
  end
596
615
  end
597
616
 
598
617
  # this is needed to prevent problems with method_missing!
618
+ ######################################
599
619
  def respond_to_missing?(method, *)
600
- available_list_methods.key?(method.to_s) || super
620
+ available_list_methods.key?(method.to_s) || method.to_s == 'all_names' || super
601
621
  end
602
622
 
603
623
  # @return [Hash{String: Symbol}] Method name to matching attribute name for
604
624
  # all identifiers
625
+ ######################################
605
626
  def available_list_methods
606
627
  return @available_list_methods if @available_list_methods
607
628
 
608
629
  @available_list_methods = {}
609
-
610
630
  identifiers.each do |i|
611
631
  meth_name = i.to_s.end_with?('s') ? "all_#{i}es" : "all_#{i}s"
612
632
  @available_list_methods[meth_name] = i
@@ -31,6 +31,8 @@ module Jamf
31
31
  #
32
32
  # Classes doing so must define the FILTER_KEYS constant, an Array of
33
33
  # Symbols of keys from OAPI_PROPERTIES which can be used in filters.
34
+ #
35
+ # TODO: Actually implement this module in CollectionResources?
34
36
  module Filterable
35
37
 
36
38
  def self.extended(extender)
@@ -41,6 +43,12 @@ module Jamf
41
43
 
42
44
  # generate the RSQL filter to put into the url
43
45
  # This is callable from anywhere without mixing in.
46
+ #
47
+ # @param filter [String, nil] the filter to apply, or nil. If the
48
+ # filter starts with FILTER_PARAM_PREFIX, it is returned as-is,
49
+ # assuming it is already properly escaped.
50
+ # @return [String] the filter to use in the URL, with FILTER_PARAM_PREFIX
51
+ ##############################################
44
52
  def self.parse_url_filter_param(filter)
45
53
  return filter if filter.nil? || filter.start_with?(FILTER_PARAM_PREFIX)
46
54
 
@@ -46,7 +46,15 @@ module Jamf
46
46
  API_SOURCE = :jamf_pro
47
47
 
48
48
  # These methods are allowed to call .new
49
- NEW_CALLERS = ['fetch', 'create', 'all', 'cached_all', 'block in all', 'block in cached_all'].freeze
49
+ NEW_CALLERS = [
50
+ 'fetch',
51
+ 'create',
52
+ 'all',
53
+ 'cached_all',
54
+ 'block in all',
55
+ 'block in cached_all',
56
+ 'block in page'
57
+ ].freeze
50
58
 
51
59
  # The resource version for previewing new features
52
60
  RSRC_PREVIEW_VERSION = 'preview'.freeze
@@ -67,14 +75,20 @@ module Jamf
67
75
  end
68
76
 
69
77
  # Disallow direct use of ruby's .new class method for creating instances.
70
- # Require use of .fetch or .create, or 'all'
78
+ # Require use of a method in NEW_CALLERS, or the data must include
79
+ # :instantiate_me
80
+ #
81
+ # WARNING: do not abuse :instantiate_me, it exists so we don't constantly
82
+ # have to update NEW_CALLERS
71
83
  #
72
84
  def new(**data)
73
85
  calling_method = caller_locations(1..1).first.label
74
- unless NEW_CALLERS.include? calling_method
86
+ unless NEW_CALLERS.include? calling_method || data[:instantiate_me]
75
87
  raise Jamf::UnsupportedError, 'Use .fetch, .create, or .all(instantiate:true) to instantiate Jamf::JPAPIResource objects'
76
88
  end
77
89
 
90
+ data.delete :instantiate_me
91
+
78
92
  super(**data)
79
93
  end
80
94