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.
- checksums.yaml +4 -4
- data/CHANGES.md +25 -7
- data/README-2.0.0.md +19 -8
- data/README.md +43 -30
- data/lib/jamf/api/classic/api_objects/patch_title.rb +0 -2
- data/lib/jamf/api/jamf_pro/api_objects/api_client.rb +3 -0
- data/lib/jamf/api/jamf_pro/api_objects/api_role.rb +3 -0
- data/lib/jamf/api/jamf_pro/api_objects/j_package.rb +307 -119
- data/lib/jamf/api/jamf_pro/api_objects/managed_software_updates/plan.rb +237 -0
- data/lib/jamf/api/jamf_pro/api_objects/managed_software_updates.rb +291 -0
- data/lib/jamf/api/jamf_pro/base_classes/oapi_object.rb +8 -0
- data/lib/jamf/api/jamf_pro/mixins/collection_resource.rb +23 -3
- data/lib/jamf/api/jamf_pro/mixins/filterable.rb +8 -0
- data/lib/jamf/api/jamf_pro/mixins/jpapi_resource.rb +17 -3
- data/lib/jamf/api/jamf_pro/mixins/macos_managed_updates.rb +2 -0
- data/lib/jamf/api/jamf_pro/mixins/prestage.rb +3 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/account_group.rb +123 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/account_preferences_v1.rb +105 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/assign_remove_profile_response_sync_state.rb +112 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/auth_account_v1.rb +159 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/authentication_type.rb +97 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/{device_enrollment_disown_body.rb → available_updates.rb} +14 -10
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_application.rb +124 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_attachment.rb +102 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_certificate.rb +151 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_configuration_profile.rb +118 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching.rb +372 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_alert.rb +120 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_cache_detail.rb +97 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_data_migration_error.rb +98 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_data_migration_error_user_info.rb +89 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_parent.rb +131 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_parent_alert.rb +105 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_parent_capabilities.rb +124 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_parent_details.rb +118 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_content_caching_parent_local_network.rb +97 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_disk.rb +143 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_disk_encryption.rb +120 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_extension_attribute.rb +175 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_font.rb +93 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_general.rb +244 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_hardware.rb +264 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_ibeacon.rb +81 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_inventory.rb +276 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_inventory_file_vault.rb +127 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_licensed_software.rb +88 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_local_user_account.rb +196 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_mdm_capability.rb +88 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_operating_system.rb +148 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_package_receipts.rb +97 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_partition.rb +145 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_partition_encryption.rb +94 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_partition_file_vault2_state.rb +97 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_plugin.rb +93 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_prestage_v3.rb +129 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_printer.rb +99 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_purchase.rb +158 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_remote_management.rb +88 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_section.rb +109 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_security.rb +193 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_service.rb +81 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_software_update.rb +93 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_storage.rb +90 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/computer_user_and_location.rb +131 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/dss_declaration.rb +111 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/dss_declarations.rb +88 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/enrollment_method.rb +97 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/group_membership.rb +94 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/inventory_preload_extension_attribute.rb +89 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/location_information.rb +146 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/{package_manifest.rb → location_information_v2.rb} +35 -37
- data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan.rb +181 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_event_store.rb +85 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_group_post.rb +99 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_post.rb +98 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_post_response.rb +100 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_toggle.rb +115 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_toggle_status.rb +169 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plan_toggle_status_wrapper.rb +91 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_plans.rb +102 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_status.rb +173 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/managed_software_update_statuses.rb +102 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/mobile_device_prestage_name_v2.rb +94 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/mobile_device_prestage_names_v2.rb +118 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/plan_configuration_post.rb +142 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/plan_device.rb +105 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/plan_device_post.rb +97 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/plan_device_response.rb +96 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/plan_group_post.rb +96 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/plan_search_results.rb +89 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/plan_status.rb +127 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/{device_enrollment_prestage.rb → prestage_purchasing_information.rb} +51 -88
- data/lib/jamf/api/jamf_pro/oapi_schemas/prestage_purchasing_information_v2.rb +174 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/prestage_scope_assignment_v2.rb +94 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas/v1_site.rb +91 -0
- data/lib/jamf/api/jamf_pro/oapi_schemas.rb +24 -1
- data/lib/jamf/composer.rb +114 -107
- data/lib/jamf/oapi_validate.rb +1 -0
- data/lib/jamf/version.rb +1 -1
- data/test/bin/runtests +2 -2
- data/test/lib/jamf_test/collection_tests.rb +10 -2
- data/test/tests/computer_group.rb +29 -12
- data/test/tests/{jp_building.rb → j_building.rb} +2 -2
- data/test/tests/j_package.rb +47 -0
- 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
|
-
|
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 = [
|
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
|
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
|
|