ruby-jss 4.2.0b2 → 4.2.1

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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +44 -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 +333 -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 +24 -3
  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/api/jamf_pro/other_classes/pager.rb +7 -1
  98. data/lib/jamf/composer.rb +114 -107
  99. data/lib/jamf/oapi_validate.rb +1 -0
  100. data/lib/jamf/utility.rb +20 -14
  101. data/lib/jamf/version.rb +1 -1
  102. data/test/bin/runtests +2 -2
  103. data/test/lib/jamf_test/collection_tests.rb +10 -2
  104. data/test/tests/computer_group.rb +29 -12
  105. data/test/tests/{jp_building.rb → j_building.rb} +2 -2
  106. data/test/tests/j_package.rb +47 -0
  107. 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
 
@@ -113,6 +118,9 @@ module Jamf
113
118
  # create a getter for an attribute, and any aliases needed
114
119
  ##############################
115
120
  def self.create_getters(attr_name, attr_def)
121
+ # if the getter has already been defined, don't overwrite it
122
+ return if instance_methods.include? attr_name.to_sym
123
+
116
124
  # multi_value - only return a frozen dup, no direct editing of the Array
117
125
  if attr_def[:multi]
118
126
  define_method(attr_name) do
@@ -134,6 +142,11 @@ module Jamf
134
142
  # create setter(s) for an attribute, and any aliases needed
135
143
  ##############################
136
144
  def self.create_setters(attr_name, attr_def)
145
+ setter_method_name = "#{attr_name}="
146
+
147
+ # if the setter has already been defined, don't overwrite it
148
+ return if instance_methods.include? setter_method_name.to_sym
149
+
137
150
  # multi_value
138
151
  if attr_def[:multi]
139
152
  create_array_setters(attr_name, attr_def)
@@ -141,7 +154,7 @@ module Jamf
141
154
  end
142
155
 
143
156
  # single value
144
- define_method("#{attr_name}=") do |new_value|
157
+ define_method(setter_method_name) do |new_value|
145
158
  new_value = validate_attr attr_name, new_value
146
159
  old_value = instance_variable_get("@#{attr_name}")
147
160
  return if new_value == old_value
@@ -340,6 +353,11 @@ module Jamf
340
353
  # @return [Hash]
341
354
  attr_reader :init_data
342
355
 
356
+ # If this is true, we are being created via the .create method
357
+ # and not fetched from the API.
358
+ # @return [Boolean]
359
+ attr_reader :creating_from_create
360
+
343
361
  # Constructor
344
362
  #####################################
345
363
 
@@ -351,9 +369,9 @@ module Jamf
351
369
  @init_data = data
352
370
 
353
371
  # creating a new one via ruby-jss, not fetching one from the API
354
- creating = data.delete :creating_from_create if data.is_a?(Hash)
372
+ @creating_from_create = data.delete :creating_from_create if data.is_a?(Hash)
355
373
 
356
- if creating
374
+ if @creating_from_create
357
375
  self.class::OAPI_PROPERTIES.each_key do |attr_name|
358
376
  # we'll enforce required values when we save
359
377
  next unless data.key? attr_name
@@ -470,6 +488,9 @@ module Jamf
470
488
  # Comparable by the sha1 hash of our properties.
471
489
  # Subclasses or mixins may override this in ways that make
472
490
  # sense for them
491
+ # TODO: Using this may not make sense for most objects, esp
492
+ # when comparing objects instantiated from Create vs those
493
+ # from Fetch.
473
494
  def <=>(other)
474
495
  sha1_hash <=> other.sha1_hash
475
496
  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