ruby-jss 1.4.1 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-jss might be problematic. Click here for more details.

Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +38 -0
  3. data/lib/jamf.rb +10 -3
  4. data/lib/jamf/api/abstract_classes/collection_resource.rb +329 -150
  5. data/lib/jamf/api/abstract_classes/generic_reference.rb +9 -1
  6. data/lib/jamf/api/abstract_classes/json_object.rb +106 -82
  7. data/lib/jamf/api/abstract_classes/prestage.rb +54 -29
  8. data/lib/jamf/api/abstract_classes/prestage_skip_setup_items.rb +21 -0
  9. data/lib/jamf/api/abstract_classes/resource.rb +4 -4
  10. data/lib/jamf/api/abstract_classes/singleton_resource.rb +1 -1
  11. data/lib/jamf/api/connection.rb +13 -9
  12. data/lib/jamf/api/connection/api_error.rb +8 -8
  13. data/lib/jamf/api/connection/token.rb +16 -15
  14. data/lib/jamf/api/json_objects/computer_prestage_skip_setup_items.rb +14 -1
  15. data/lib/jamf/api/json_objects/device_enrollment_device.rb +14 -7
  16. data/lib/jamf/api/json_objects/device_enrollment_device_sync_state.rb +81 -0
  17. data/lib/jamf/api/json_objects/locale.rb +59 -0
  18. data/lib/jamf/api/json_objects/md_prestage_skip_setup_items.rb +50 -1
  19. data/lib/jamf/api/json_objects/prestage_location.rb +3 -3
  20. data/lib/jamf/api/json_objects/prestage_purchasing_data.rb +7 -7
  21. data/lib/jamf/api/json_objects/prestage_scope.rb +1 -1
  22. data/lib/jamf/api/{resources/collection_resources → json_objects}/time_zone.rb +9 -23
  23. data/lib/jamf/api/mixins/bulk_deletable.rb +27 -6
  24. data/lib/jamf/api/mixins/change_log.rb +201 -51
  25. data/lib/jamf/api/mixins/filterable.rb +51 -0
  26. data/lib/jamf/api/mixins/pageable.rb +208 -0
  27. data/lib/jamf/api/mixins/sortable.rb +59 -0
  28. data/lib/jamf/api/resources/collection_resources/building.rb +19 -8
  29. data/lib/jamf/api/resources/collection_resources/category.rb +5 -3
  30. data/lib/jamf/api/resources/collection_resources/computer_prestage.rb +11 -4
  31. data/lib/jamf/api/resources/collection_resources/device_enrollment.rb +10 -10
  32. data/lib/jamf/api/resources/collection_resources/inventory_preload_record.rb +11 -3
  33. data/lib/jamf/api/resources/collection_resources/mobile_device_prestage.rb +24 -22
  34. data/lib/jamf/api/resources/collection_resources/script.rb +61 -25
  35. data/lib/jamf/api/resources/singleton_resources/app_store_country_codes.rb +15 -5
  36. data/lib/jamf/api/resources/singleton_resources/client_checkin_settings.rb +14 -14
  37. data/lib/jamf/api/resources/singleton_resources/locales.rb +155 -0
  38. data/lib/jamf/api/resources/singleton_resources/time_zones.rb +213 -0
  39. data/lib/jamf/validate.rb +63 -24
  40. data/lib/jamf/version.rb +1 -1
  41. data/lib/jss.rb +2 -1
  42. data/lib/jss/api_connection.rb +110 -370
  43. data/lib/jss/api_object.rb +3 -19
  44. data/lib/jss/api_object/categorizable.rb +1 -1
  45. data/lib/jss/api_object/configuration_profile.rb +34 -3
  46. data/lib/jss/api_object/directory_binding_type.rb +66 -60
  47. data/lib/jss/api_object/directory_binding_type/active_directory.rb +71 -34
  48. data/lib/jss/api_object/directory_binding_type/admitmac.rb +536 -467
  49. data/lib/jss/api_object/directory_binding_type/centrify.rb +21 -7
  50. data/lib/jss/api_object/directory_binding_type/open_directory.rb +4 -4
  51. data/lib/jss/api_object/distribution_point.rb +2 -2
  52. data/lib/jss/api_object/dock_item.rb +102 -96
  53. data/lib/jss/api_object/extendable.rb +1 -1
  54. data/lib/jss/api_object/group.rb +33 -2
  55. data/lib/jss/api_object/network_segment.rb +43 -12
  56. data/lib/jss/api_object/patch_source.rb +10 -9
  57. data/lib/jss/api_object/printer.rb +10 -4
  58. data/lib/jss/api_object/scopable.rb +10 -15
  59. data/lib/jss/exceptions.rb +3 -0
  60. data/lib/jss/server.rb +15 -0
  61. data/lib/jss/version.rb +1 -1
  62. metadata +37 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef0484427943d39089bb11eb73af827c5137d79550c34ea5fdf7ca683c7c69f0
4
- data.tar.gz: '09abd63abb133ee6c34aa2fcb9ae8ccc3e11b3248061ee410223bd8f25f5a7ae'
3
+ metadata.gz: 7c229fe64d5af063db9f075fd9345d34447160817a125fb147f72e4603d56d1c
4
+ data.tar.gz: a4642b384ae4bd853563928d6c399597404935c5cdce329a3f485e5ad2d92095
5
5
  SHA512:
6
- metadata.gz: 962236f6ca861fbfa3f706df0d8d977f15fb1436238f7d9c061ae8043c4148b956fc187a9858f9444b8461cc7d707067278e6a9030c676af26febdc565b0cd0f
7
- data.tar.gz: 7f7027d207de8ea3fd3d1c010483a9e261d4962857ec48ac6f4df0ba4d38526720ff3700078900e1df2182f191a0aaf7c3284ef3438714e1fbc9118f3aeb03ca
6
+ metadata.gz: dbabf94f300745834e65c574cc1b9918b03540d81f9c54a004803913452147f63e7eb0df20694ea092c05b31c0700144a55df99bc57d4f9fbbaf3c67dab1d034
7
+ data.tar.gz: 6aa566e804422b49bdcd258ae08386a080a567e14b1cd47e936c8098a307ca6529ec2da4963f6f801d2586a04cd9edb570e6f101e865d19861213b93ea062128
data/CHANGES.md CHANGED
@@ -4,6 +4,44 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## \[1.5.1] - 2020-11-16
8
+
9
+ IMPORTANT: New minimum require ruby version is 2.3.0
10
+
11
+ Big thanks to @cybertunnel for many enhancements and fixes.
12
+
13
+ ### Added
14
+
15
+ - The .all method for subclasses of Jamf::CollectionResource now fully supports server-side paging, sorting and filtering (for endpoints that support RSQL filters). See the docs/comments for Jamf::CollectionResource.all for details
16
+
17
+ - JSS::ConfigurationProfile subclasses now have a #payload_content=(new_content) method, which takes an Array of Hashes to replace the PayloadContent of the Payload of the profile. All converstion to an XML plist (which is then embedded into the API XML) is handled automatically. WARNING: This is experimental and can easily break your profile if you aren't careful.
18
+
19
+ - JSS::Server#update_activation_code method was added
20
+
21
+ - Group#set_static and #set_smart can convert smart groups to static and static to smart
22
+
23
+ ### Changed
24
+
25
+ - Minimum required ruby version is 2.3.0
26
+
27
+ - The JSS Module now uses the faraday gem, rather than rest-client, as the underlying REST/HTTP engine for communicating with the Classic API. This brings it in line with the Jamf module which has always used faraday for connecting to the Jamf Pro API. Faraday has fewer dependencies, none of which need to be compiled. This means that installing ruby-jss on a Mac no longer requires the XCode command-line tools.
28
+
29
+ - The Jamf module, for accessing the Jamf Pro API, now requires Jamf Pro 10.25 or higher. While still in 'beta', the Jamf Pro API is becoming more stable and in compliance with standards. The Jamf module continues to be updated to work with the modernized endpoints of the JP API. Some related changes:
30
+ - The ids of JP API collection objects are Strings containing Integers.
31
+ - Boolean property names no longer start with 'is', tho aliases ending with '?' are still automatically created.
32
+
33
+ - Removed dependency on net-ldap, which hasn't been used in a while
34
+
35
+ - Removed the redundant JSS::APIConnection instance methods that were just wrappers for various APIObject subclass Class methods, e.g. `JSS.api.valid_id :computers, 'compName'`. Please use the class method directly, e.g. `JSS::Computer.valid_id 'compName'`
36
+
37
+ ### Fixed
38
+
39
+ - PatchSource.fetch was totally broken, now fixed
40
+
41
+ - Category object's parse_category not properly referencing API object during execution
42
+
43
+ - Many small bugs and typos.
44
+
7
45
  ## \[1.4.1] - 2020-10-01
8
46
 
9
47
  ### Added
@@ -41,9 +41,7 @@ require 'shellwords'
41
41
  require 'digest'
42
42
  require 'open3'
43
43
 
44
-
45
44
  ### Gems
46
- require 'rest-client'
47
45
  require 'plist'
48
46
 
49
47
  # Used, among other places, in the Connection::APIError class
@@ -113,6 +111,10 @@ module Jamf
113
111
  autoload :Immutable, 'jamf/api/mixins/immutable'
114
112
  autoload :UnDeletable, 'jamf/api/mixins/undeletable'
115
113
  autoload :Abstract, 'jamf/api/mixins/abstract'
114
+ autoload :Pageable, 'jamf/api/mixins/pageable'
115
+ autoload :Filterable, 'jamf/api/mixins/filterable'
116
+ autoload :Sortable, 'jamf/api/mixins/sortable'
117
+ autoload :BulkDeletable, 'jamf/api/mixins/bulk_deletable'
116
118
 
117
119
  # Utility modules
118
120
  autoload :Validate, 'jamf/validate'
@@ -126,6 +128,7 @@ module Jamf
126
128
  autoload :Country, 'jamf/api/json_objects/country'
127
129
  autoload :Criterion, 'jamf/api/json_objects/criterion'
128
130
  autoload :DeviceEnrollmentDevice, 'jamf/api/json_objects/device_enrollment_device'
131
+ autoload :DeviceEnrollmentDeviceSyncState, 'jamf/api/json_objects/device_enrollment_device_sync_state'
129
132
  autoload :DeviceEnrollmentSyncStatus, 'jamf/api/json_objects/device_enrollment_sync_status'
130
133
  autoload :ExtensionAttributeValue, 'jamf/api/json_objects/extension_attribute_value'
131
134
  autoload :InstalledApplication, 'jamf/api/json_objects/installed_application'
@@ -135,6 +138,7 @@ module Jamf
135
138
  autoload :InstalledProvisioningProfile, 'jamf/api/json_objects/installed_provisioning_profile'
136
139
  autoload :InventoryPreloadExtensionAttribute, 'jamf/api/json_objects/inventory_preload_extension_attribute'
137
140
  autoload :IosDetails, 'jamf/api/json_objects/ios_details'
141
+ autoload :Locale, 'jamf/api/json_objects/locale'
138
142
  autoload :Location, 'jamf/api/json_objects/location'
139
143
  autoload :PrestageLocation, 'jamf/api/json_objects/prestage_location'
140
144
  autoload :PrestageSyncStatus, 'jamf/api/json_objects/prestage_sync_status'
@@ -147,16 +151,20 @@ module Jamf
147
151
  autoload :PrestagePurchasingData, 'jamf/api/json_objects/prestage_purchasing_data'
148
152
  autoload :PrestageScope, 'jamf/api/json_objects/prestage_scope'
149
153
  autoload :PrestageAssignment, 'jamf/api/json_objects/prestage_assignment'
154
+ autoload :TimeZone, 'jamf/api/json_objects/time_zone'
150
155
 
151
156
  # Subclasses of SingletonResource
152
157
  autoload :ClientCheckInSettings, 'jamf/api/resources/singleton_resources/client_checkin_settings'
153
158
  autoload :ReEnrollmentSettings, 'jamf/api/resources/singleton_resources/reenrollment_settings'
154
159
  autoload :AppStoreCountryCodes, 'jamf/api/resources/singleton_resources/app_store_country_codes'
160
+ autoload :TimeZones, 'jamf/api/resources/singleton_resources/time_zones'
161
+ autoload :Locales, 'jamf/api/resources/singleton_resources/locales'
155
162
 
156
163
  # Subclasses of CollectionResource
157
164
  autoload :AdvancedMobileDeviceSearch, 'jamf/api/resources/collection_resources/advanced_mobile_device_search'
158
165
  autoload :AdvancedUserSearch, 'jamf/api/resources/collection_resources/advanced_user_search'
159
166
  autoload :Attachment, 'jamf/api/resources/collection_resources/attachment'
167
+ autoload :Category, 'jamf/api/resources/collection_resources/category'
160
168
  autoload :Building, 'jamf/api/resources/collection_resources/building'
161
169
  autoload :Computer, 'jamf/api/resources/collection_resources/computer'
162
170
  autoload :ComputerPrestage, 'jamf/api/resources/collection_resources/computer_prestage'
@@ -168,7 +176,6 @@ module Jamf
168
176
  autoload :MobileDevicePrestage, 'jamf/api/resources/collection_resources/mobile_device_prestage'
169
177
  autoload :Site, 'jamf/api/resources/collection_resources/site'
170
178
  autoload :Script, 'jamf/api/resources/collection_resources/script'
171
- autoload :TimeZone, 'jamf/api/resources/collection_resources/time_zone'
172
179
 
173
180
  # other classes used as attributes inside the resource classes
174
181
  autoload :IPAddress, 'jamf/api/attribute_classes/ip_address'
@@ -32,8 +32,9 @@ module Jamf
32
32
  #
33
33
  # Collection resources have more than one resource within them, and those
34
34
  # can (usually) be created and deleted as well as fetched and updated.
35
- # The entire collection (or a part of it) can also be fetched as an Array.
36
- # When the whole collection is fetched, the result is cached for future use.
35
+ # The entire collection (or a part of it) can also be retrieved as an Array.
36
+ # When the whole collection is retrieved, the result may be cached for future
37
+ # use.
37
38
  #
38
39
  # # Subclassing
39
40
  #
@@ -63,6 +64,10 @@ module Jamf
63
64
  class CollectionResource < Jamf::Resource
64
65
 
65
66
  extend Jamf::Abstract
67
+ extend Jamf::Pageable
68
+ extend Jamf::Sortable
69
+ extend Jamf::Filterable
70
+
66
71
  include Comparable
67
72
 
68
73
  # Public Class Methods
@@ -74,63 +79,195 @@ module Jamf
74
79
  self::OBJECT_MODEL.select { |_attr, deets| deets[:identifier] }.keys
75
80
  end
76
81
 
77
- # The Collection members Array for this class, retrieved from
78
- # the RSRC_PATH as Parsed JSON, but not instantiated into instances
79
- # unless instantiate: is truthy.
82
+ def self.count(cnx: Jamf.cnx)
83
+ collection_count(rsrc_path, cnx: Jamf.cnx)
84
+ end
85
+
86
+
87
+ # Get all instances of a CollectionResource, possibly limited by a filter.
88
+ #
89
+ # When called without specifying paged:, sort:, or filter: (see below)
90
+ # this method will return a single Array of all items of its
91
+ # CollectionResouce subclass, in the server's default sort order. This
92
+ # full list is cached for future use (see Caching, below)
93
+ #
94
+ # However, the Array can be sorted by the server, filtered to contain only
95
+ # matching objects, or 'paged', i.e. retrieved in successive Arrays of a
96
+ # certain size.
97
+ #
98
+ # Sorting, filtering, and paging can all be used at the same time.
99
+ #
100
+ # #### Server-side Sorting
101
+ #
102
+ # Sorting criteria can be provided in the String format 'property:direction',
103
+ # where direction is 'asc' or 'desc' E.g.
104
+ # "username:asc"
105
+ #
106
+ # Multiple properties are supported, either as separate strings in an Array,
107
+ # or a single string, comma separated. E.g.
108
+ #
109
+ # "username:asc,timestamp:desc"
110
+ # is the same as
111
+ # ["username:asc", "timestamp:desc"]
112
+ #
113
+ # which will sort by username alphabetically, and within each username,
114
+ # sort by timestamp newest first.
115
+ #
116
+ # Please see the JamfPro API documentation for the resource for details
117
+ # about available sorting properties and default sorting criteria
118
+ #
119
+ # #### Filtering
120
+ #
121
+ # Some CollectionResouces support RSQL filters to limit which objects
122
+ # are returned. These filters can be applied using the filter: parameter,
123
+ # in which case this `all` method will return `all that match the filter`.
124
+ #
125
+ # If the resource doesn't support filters, the filter parameter is ignored.
126
+ #
127
+ # Please see the JamfPro API documentation for the resource to see if
128
+ # filters are supported, and a list of available fields.
129
+ #
130
+ # #### Paging
131
+ #
132
+ # To reduce server load and local memory usage, you can request the results
133
+ # in 'pages', i.e. successivly retrieved Arrays, using the paged: and page_size:
134
+ # parameters.
135
+ #
136
+ # When paged: is truthy, the call to `all` returns the first group of objects
137
+ # containing however many are specified by page_size: The default page size
138
+ # is 100, the minimum is 1, and the maximum is 2000.
139
+ #
140
+ # Once you have made a paged call to `all`, you must use the `next_page_of_all`
141
+ # method to get the next Array of objects. That method merely repeats the last
142
+ # request made by `all` after incrementing the page number by 1.
143
+ # When `next_page_of_all` returns an empty array, you have retrieved all
144
+ # availalble objects.
145
+ #
146
+ # `next_page_of_all` always reflects the last _paged_ call to `all`. Any
147
+ # subsequent paged call to `all` will reset the paging process for that
148
+ # collection class, and any unfinished previous paged calls to `all` will
149
+ # be forgotten
150
+ #
151
+ # #### Instantiation
152
+ #
153
+ # All data from the API comes from the server in JSON format, mostly as
154
+ # JSON 'objects', which are the equivalent of ruby Hashes.
155
+ # When fetching an individual instance of an object from the API, ruby-jss
156
+ # uses the JSON Hash to create the ruby object, i.e. to 'instantiate' it as
157
+ # an instance of its class. Doing this for many objects can slow things down.
158
+ #
159
+ # Because of this, the 'all' method defaults to returning an Array of the
160
+ # minimally-processed JSON Hashes it gets from the API. If you can get your
161
+ # desired data from these Hashes, it's far more efficient to do so.
162
+ #
163
+ # However sometimes you really need the fully instantiated ruby objects for
164
+ # all of them - especially if you're using filters and not actually processing
165
+ # all items of the class. In such cases you can pass a truthy value to the
166
+ # instantiate: parameter, and the Array will contain fully instantiated
167
+ # ruby objects, not Hashes of API data.
80
168
  #
81
- # E.g. for {Jamf::Settings::Building}, this would be the Array of Hashes
82
- # returned by GETing the resource .../settings/obj/building
169
+ # #### Caching
83
170
  #
84
- # This Array is cached in the {Jamf::Connection} instance used to
85
- # retrieve it, and future calls to .all will return the cached Array
86
- # unless refresh is truthy.
171
+ # When called without specifying paged:, sort:, or filter:
172
+ # this method will return a single Array of all items of its
173
+ # CollectionResouce subclass, in the server's default sort order.
87
174
  #
88
- # TODO: Investigate https://www.rubydoc.info/github/mloughran/api_cache
175
+ # This Array is cached in ruby-jss, and future calls to this method without
176
+ # those parameters will return the cached Array. Use `refresh: true` to
177
+ # re-request that Array from the server. Note that the cache is of the raw
178
+ # JSON Hash data. Using 'instantiate:' will still be slower as each item in
179
+ # the cache is instantiated. See 'Instantiation' below.
89
180
  #
90
- # @param refresh[Boolean] re-read the data from the API?
181
+ # Some other methods, e.g. .all_names, will generate or use this cached Array
182
+ # to derive their values.
91
183
  #
92
- # @param cnx[Jamf::Connection] an API connection to use for the query.
93
- # Defaults to the corrently active connection. See {Jamf::Connection}
184
+ # If any of the parameters paged:, sort:, or filter: are used, an API
185
+ # request is made every time, and no caches are used or stored.
94
186
  #
95
- # @param instantiate[Boolean] The Array contains instances of this class
96
- # rather than the JSON Hashes from the API.
187
+ #######
97
188
  #
98
- # @return [Array<Object>] An Array of all objects of this class in the JSS.
189
+ # @param sort [String, Array<String>] Server-side sorting criteria in the format:
190
+ # property:direction, where direction is 'asc' or 'desc'. Multiple
191
+ # properties are supported, either as separate strings in an Array, or
192
+ # a single string, comma separated.
99
193
  #
100
- def self.all(refresh = false, cnx: Jamf.cnx, instantiate: false)
194
+ # @param filter [String] An RSQL filter string. Not all collection resources
195
+ # currently support filters, and if they don't, this will be ignored.
196
+ #
197
+ # @param paged [Boolean] Defaults to false. Returns only the first page of
198
+ # `page_size` objects. Use {.next_page_of_all} to retrieve each successive
199
+ # page.
200
+ #
201
+ # @param page_size [Integer] How many items are returned per page? Minimum
202
+ # is 1, maximum is 2000, default is 100. Ignored unless paged: is truthy.
203
+ # Note: the final page may contain fewer items than the page_size
204
+ #
205
+ # @param refresh [Boolean] re-fetch and re-cache the full list of all instances.
206
+ # Ignored if paged:, page_size:, sort:, filter: or instantiate: are used.
207
+ #
208
+ # @param instantiate [Boolean] Defaults to false. Should the items in the
209
+ # returned Array(s) be ruby instances of the CollectionObject subclass, or
210
+ # plain Hashes of data as returned by the API?
211
+ #
212
+ # @param cnx [Jamf::Connection] The API connection to use, default: Jamf.cnx
213
+ #
214
+ # @return [Array<Hash, Jamf::CollectionResource>] The objects in the collection
215
+ #
216
+ def self.all(sort: nil, filter: nil, paged: nil, page_size: nil, refresh: false, instantiate: false, cnx: Jamf.cnx)
101
217
  validate_not_abstract
102
- cnx.collection_cache[self] = nil if refresh
103
- if cnx.collection_cache[self]
104
- return cnx.collection_cache[self] unless instantiate
105
218
 
106
- return cnx.collection_cache[self].map { |m| new m }
107
- end
219
+ # use the cache if not paging, filtering or sorting
220
+ return cached_all(refresh, instantiate, cnx) if !paged && !sort && !filter
108
221
 
109
- # TODO: make sure all collection resources use this format
110
- # for paging. Also -ask Jamf about a url that returns
111
- # ALL objects in one query, regardless of number.
112
- page = 0
113
- raw = cnx.get "#{rsrc_path}?page=#{page}&size=1000000&sort=id%3Aasc"
114
- results = raw[:results]
115
-
116
- until results.size >= raw[:totalCount]
117
- page += 1
118
- raw = cnx.get "#{rsrc_path}?page=#{page}&size=1000000&sort=id%3Aasc"
119
- results += raw[:results]
120
- end
222
+ # we are sorting, filtering or paging
223
+ sort = parse_collection_sort(sort)
224
+ filter = parse_collection_filter(filter)
121
225
 
226
+ result =
227
+ if paged
228
+ first_collection_page(rsrc_path, page_size, sort, filter, cnx)
229
+ else
230
+ fetch_all_collection_pages(rsrc_path, sort, filter, cnx)
231
+ end
232
+ instantiate ? result.map { |m| new m } : result
233
+ end
122
234
 
123
- cnx.collection_cache[self] = results
235
+ # PRIVATE
236
+ # return the cached/cachable version of .all, possibly instantiated
237
+ #
238
+ # @param refresh [Boolean] refetch the cache from the server?
239
+ #
240
+ # @param instantiate [Boolean] Return an array of instantiated objects, vs
241
+ # JSON hashes?
242
+ #
243
+ # @param cnx [Jamf::Connection] The Connection to use
244
+ #
245
+ # @return [Array<Hash,Object>] All the objects in the collection
246
+ #
247
+ def self.cached_all(refresh, instantiate, cnx)
248
+ cnx.collection_cache[self] = nil if refresh
249
+ unless cnx.collection_cache[self]
250
+ sort = nil
251
+ filter = nil
252
+ cnx.collection_cache[self] = fetch_all_collection_pages(rsrc_path, sort, filter, cnx)
253
+ end
254
+ instantiate ? cnx.collection_cache[self].map { |m| new m } : cnx.collection_cache[self]
255
+ end
256
+ private_class_method :cached_all
124
257
 
125
- return cnx.collection_cache[self] unless instantiate
126
258
 
127
- cnx.collection_cache[self].map { |m| new m }
259
+ # Fetch the next page of a paged .all request. See
260
+ # {Jamf::Pagable.next_collection_page}
261
+ def self.next_page_of_all
262
+ next_collection_page
128
263
  end
129
264
 
130
265
  # An array of the ids for all collection members. According to the
131
266
  # specs ALL collection resources must have an ID, which is used in the
132
267
  # resource path.
133
268
  #
269
+ # NOTE: This method uses the cached version of .all
270
+ #
134
271
  # @param refresh (see .all)
135
272
  #
136
273
  # @param cnx (see .all)
@@ -138,14 +275,14 @@ module Jamf
138
275
  # @return [Array<Integer>]
139
276
  #
140
277
  def self.all_ids(refresh = false, cnx: Jamf.cnx)
141
- all(refresh, cnx: cnx).map { |m| m[:id] }
278
+ all(refresh: refresh, cnx: cnx).map { |m| m[:id] }
142
279
  end
143
280
 
144
- # rubocop:disable Naming/UncommunicativeMethodParamName
145
-
146
281
  # A Hash of all members of this collection where the keys are some
147
282
  # identifier and values are any other attribute.
148
283
  #
284
+ # NOTE: This method uses the cached version of .all
285
+ #
149
286
  # @param ident [Symbol] An identifier of this Class, used as the key
150
287
  # for the mapping Hash. Aliases are acceptable, e.g. :sn for :serialNumber
151
288
  #
@@ -166,76 +303,149 @@ module Jamf
166
303
  real_to = attr_key_for_alias to
167
304
  raise Jamf::NoSuchItemError, "No attribute #{to} for class #{self}" unless self::OBJECT_MODEL.key? real_to
168
305
 
169
- ident = real_ident
170
- to = real_to
171
- list = all refresh, cnx: cnx
172
- to_class = self::OBJECT_MODEL[to][:class]
306
+ list = all refresh: refresh, cnx: cnx
307
+ to_class = self::OBJECT_MODEL[real_to][:class]
173
308
  mapped = list.map do |i|
174
309
  [
175
- i[ident],
176
- to_class.is_a?(Symbol) ? i[to] : to_class.new(i[to])
310
+ i[real_ident],
311
+ to_class.is_a?(Symbol) ? i[real_to] : to_class.new(i[real_to])
177
312
  ]
178
313
  end # do i
179
314
  mapped.to_h
180
315
  end
181
- # rubocop:enable Naming/UncommunicativeMethodParamName
182
316
 
183
- # Given any identfier value for this collection, return the id of the
184
- # object that has such an identifier.
317
+ # Given a key (identifier) and value for this collection, return the raw data
318
+ # Hash (the JSON object) for the matching API object or nil if there's no
319
+ # match for the given value.
185
320
  #
186
- # Return nil if there's no match for the given value.
321
+ # In general you should use this if the form:
187
322
  #
188
- # If you know the value is a certain identifier, e.g. a serialNumber,
189
- # then you can specify the identifier for a faster search:
323
+ # raw_data identifier: value
190
324
  #
191
- # valid_id serialNumber: 'AB12DE34' # => Int or nil
325
+ # where identifier is one of the available identifiers for this class
326
+ # like id:, name:, serialNumber: etc.
192
327
  #
193
- # If you don't know wich identifier you have, just pass the value and
194
- # all identifiers are searched
328
+ # In the unlikely event that you dont know which identifier a value is for
329
+ # or want to be able to take any of them without specifying, then
330
+ # you can use
195
331
  #
196
- # valid_id 'AB12DE34' # => Int or nil
197
- # valid_id 'SomeComputerName' # => Int or nil
332
+ # raw_data some_value
198
333
  #
199
- # When the value is a string, the seach is case-insensitive
334
+ # If some_value is an integer or a string containing an integer, it
335
+ # is assumed to be an :id otherwise all the available identifers
336
+ # are searched, in the order you see them when you call <class>.identifiers
200
337
  #
201
- # TODO: When 'Searchability' is more dialed in via the searchable
202
- # mixin, which implements enpoints like 'POST /v1/search-mobile-devices'
203
- # then use that before using the 'all' list.
338
+ # If no matching object is found, nil is returned.
204
339
  #
205
- # @param value [Object] A value to search for as an identifier.
340
+ # Everything except :id is treated as a case-insensitive String
206
341
  #
207
- # @param refresh[Boolean] Reload the list data from the API
342
+ # @param value [String, Integer] The identifier value to search fors
208
343
  #
209
- # @param ident: [Symbol] Restrict the search to this identifier.
210
- # E.g. if :serialNumber, then the value must be
211
- # a known serial number, it is not checked against other identifiers
344
+ # @param key: [Symbol] The identifier being used for the search.
345
+ # E.g. if :serialNumber, then the value must be a known serial number, it
346
+ # is not checked against other identifiers. Defaults to :id
212
347
  #
213
348
  # @param cnx: (see .all)
214
349
  #
215
- # @return [Object, nil] the primary identifier of the matching object,
350
+ # @return [Hash, nil] the basic dataset of the matching object,
216
351
  # or nil if it doesn't exist
217
352
  #
218
- def self.valid_id(value = nil, refresh: true, cnx: Jamf.cnx, **ident_hash)
219
- unless ident_hash.empty?
220
- ident, value = ident_hash.first
221
- return id_from_other_ident ident, value, refresh, cnx: cnx
222
- end
353
+ def self.raw_data(value = nil, cnx: Jamf.cnx, **ident_and_val)
354
+ validate_not_abstract
355
+
356
+ # given a value with no ident key
357
+ return raw_data_by_value_only(value, cnx: Jamf.cnx) if value
358
+
359
+ # if we're here, we should know our ident key and value
360
+ ident, value = ident_and_val.first
361
+ raise ArgumentError, 'Required parameter "identifier: value", where identifier is id:, name: etc.' unless ident && value
362
+
363
+ return raw_data_by_id(value, cnx: cnx) if ident == :id
364
+ return unless identifiers.include? ident
365
+
366
+ raw_data_by_other_identifier(ident, value, cnx: cnx)
367
+ end
368
+
369
+ # Match the given value in all possibly identifiers
370
+ def self.raw_data_by_value_only(value, cnx: Jamf.cnx)
371
+ return raw_data_by_id(value, cnx: cnx) if value.to_s.j_integer?
223
372
 
224
- # check the id itself first
225
- return value if all_ids(refresh, cnx: cnx).include? value
373
+ identifiers.each do |ident|
374
+ next if ident == :id
226
375
 
227
- idents = identifiers - [:id]
228
- val_is_str = value.is_a? String
376
+ id = raw_data_by_other_identifier(ident, value, cnx: cnx)
377
+ return id if id
378
+ end # identifiers.each
379
+ return
380
+ end
381
+ private_class_method :raw_data_by_value_only
382
+
383
+ # get the basic dataset by id, with optional
384
+ # request params to get more than basic data
385
+ def self.raw_data_by_id(id, request_params: nil, cnx: Jamf.cnx)
386
+ cnx.get "#{rsrc_path}/#{id}#{request_params}"
387
+ rescue => e
388
+ return if e.httpStatus == 404
389
+
390
+ raise e
391
+ end
392
+ private_class_method :raw_data_by_id
393
+
394
+ # Given an indentier attr. key, and a value,
395
+ # return the id where that ident has that value, or nil
396
+ #
397
+ def self.raw_data_by_other_identifier(identifier, value, refresh: false, cnx: Jamf.cnx)
398
+ # if the API supports filtering by this identifier, just use that
399
+ return all(filter: "#{identifier}=='#{value}'", paged: true, page_size: 1, cnx: cnx).first if self::OBJECT_MODEL[identifier][:filter_key]
229
400
 
230
- idents.each do |ident|
231
- match = all(refresh, cnx: cnx).select do |m|
232
- val_is_str ? m[ident].to_s.casecmp?(value) : m[ident] == value
233
- end.first
234
- return match[:id] if match
235
- end # identifiers.each do |ident|
401
+ # otherwise we have to loop thru all the objects looking for the value
402
+ all(refresh: refresh, cnx: cnx).each { |data| return data if data[identifier].to_s.casecmp? value.to_s }
236
403
 
237
404
  nil
238
405
  end
406
+ private_class_method :raw_data_by_other_identifier
407
+
408
+ # Look up the valid ID for any arbitrary identifier.
409
+ # In general you should use this if the form:
410
+ #
411
+ # valid_id identifier: value
412
+ #
413
+ # where identifier is one of the available identifiers for this class
414
+ # like id:, name:, serialNumber: etc.
415
+ #
416
+ # In the unlikely event that you dont know which identifier a value is for
417
+ # or want to be able to take any of them without specifying, then
418
+ # you can use
419
+ #
420
+ # valid_id some_value
421
+ #
422
+ # If some_value is an integer or a string containing an integer, it
423
+ # is assumed to be an id: otherwise all the available identifers
424
+ # are searched, in the order you see them when you call <class>.identifiers
425
+ #
426
+ # If no matching object is found, nil is returned.
427
+ #
428
+ # WARNING: Do not use this to look up ids for getting the
429
+ # raw API data for an object. Since this calls .raw_data
430
+ # itself, it is redundant to use .valid_id to get an id
431
+ # to then pass on to .raw_data
432
+ # Use raw_data directly like this:
433
+ # data = raw_data(ident: val)
434
+ #
435
+ #
436
+ # @param value [String,Integer] A value for an arbitrary identifier
437
+ #
438
+ # @param cnx [Jamf::Connection] The connection to use. default: Jamf.cnx
439
+ #
440
+ # @param ident_and_val [Hash{Symbol: String}] The identifier key and the value
441
+ # to look for in that key, e.g. name: 'foo' or serialNumber: 'ASDFGH'
442
+ #
443
+ # @return [String, nil] The id (integer-in-string) of the object, or nil
444
+ # if no match found
445
+ #
446
+ def self.valid_id(value = nil, cnx: Jamf.cnx, **ident_and_val)
447
+ raw_data(value, cnx: cnx, **ident_and_val)&.dig(:id)
448
+ end
239
449
 
240
450
  # Bu default, subclasses are creatable, i.e. new instances can be created
241
451
  # with .create, and added to the JSS with .save
@@ -253,15 +463,19 @@ module Jamf
253
463
  validate_not_abstract
254
464
  raise Jamf::UnsupportedError, "#{self}'s are not currently creatable via the API" unless creatable?
255
465
 
466
+ # Which connection to use
256
467
  cnx = params.delete :cnx
257
468
  cnx ||= Jamf.cnx
258
469
 
259
470
  params.delete :id # no such animal when .creating
260
-
261
471
  params.keys.each do |param|
262
472
  raise ArgumentError, "Unknown parameter: #{param}" unless self::OBJECT_MODEL.key? param
263
473
 
264
- params[param] = validate_attr param, params[param], cnx: cnx
474
+ if params[param].is_a? Array
475
+ params[param].map! { |val| validate_attr param, val, cnx: cnx }
476
+ else
477
+ params[param] = validate_attr param, params[param], cnx: cnx
478
+ end
265
479
  end
266
480
 
267
481
  params[:creating_from_create] = true
@@ -273,41 +487,27 @@ module Jamf
273
487
  # To create new members to be added to the JSS, use
274
488
  # {Jamf::CollectionResource.create}
275
489
  #
276
- # If you know the specific identifier attribute you're looking up, e.g.
490
+ # You must know the specific identifier attribute you're looking up, e.g.
277
491
  # :id or :name or :udid, (or an aliase thereof) then you can specify it like
278
492
  # `.fetch name: 'somename'`, or `.fetch udid: 'someudid'`
279
493
  #
280
- # If you don't know if (or don't want to type it) you can just use
281
- # `.fetch 'somename'`, or `.fetch 'someudid'` and all identifiers will be
282
- # searched for a match.
283
- #
284
- # @param ident_value[Object] A value for any identifier for this subclass.
285
- # All identifier attributes will be searched for a match.
286
- #
287
494
  # @param cnx[Jamf::Connection] the connection to use to fetch the object
288
495
  #
289
- # @param ident_hash[Hash] an identifier attribute key and a search value
496
+ # @param ident_and_val[Hash] an identifier attribute key and a search value
290
497
  #
291
498
  # @return [CollectionResource] The ruby-instance of a Jamf object
292
499
  #
293
- def self.fetch(ident_value = nil, cnx: Jamf.cnx, **ident_hash)
500
+ def self.fetch(random = nil, cnx: Jamf.cnx, **ident_and_val)
294
501
  validate_not_abstract
295
-
296
- id =
297
- if ident_value == :random
298
- all_ids.sample
299
- elsif ident_value
300
- valid_id ident_value, cnx: cnx
301
- elsif ident_hash.empty?
302
- nil
303
- else
304
- ident, lookup_value = ident_hash.first
305
- valid_id ident => lookup_value, cnx: cnx
502
+ ident, value = ident_and_val.first
503
+ data =
504
+ if random
505
+ all.sample
506
+ elsif ident && value
507
+ raw_data(cnx: cnx, **ident_and_val)
306
508
  end
509
+ raise Jamf::NoSuchItemError, "No matching #{self}" unless data
307
510
 
308
- raise Jamf::NoSuchItemError, "No matching #{self}" unless id
309
-
310
- data = cnx.get "#{rsrc_path}/#{id}"
311
511
  new data, cnx: cnx
312
512
  end # fetch
313
513
 
@@ -318,36 +518,31 @@ module Jamf
318
518
  true
319
519
  end
320
520
 
321
- # Delete one or more objects by identifier
322
- # Any valid identifier for the class can be used (id, name, udid, etc)
323
- # Identifiers can be provided as an array or as separate parameters
324
- #
325
- # e.g. .delete [1,3, 34, 4]
326
- # or .delete 'myComputer', 'that-computer', 'OtherComputer'
521
+ # Delete one or more objects by id
327
522
  #
328
- # @param idents[Array<integer>, Integer]
523
+ # @param ids [Array<String,Integer>] The ids to delete
329
524
  #
330
- # @param cnx[Jamf::Connection]
525
+ # @param cnx [Jamf::Connection] The connection to use, default: Jamf.cnx
331
526
  #
332
- # @return [Array] the identifiers that were not found, so couldn't be deleted
527
+ # @return [Array<Jamf::Connection::APIError::ErrorInfo] Info about any ids
528
+ # that failed to be deleted.
333
529
  #
334
- def self.delete(*idents, cnx: Jamf.cnx)
530
+ def self.delete(*ids, cnx: Jamf.cnx)
335
531
  raise Jamf::UnsupportedError, "Deleting #{self} objects is not currently supported" unless deletable?
336
532
 
337
- idents.flatten!
338
- no_valid_ids = []
533
+ return bulk_delete(ids, cnx: Jamf.cnx) if ancestors.include? Jamf::BulkDeletable
339
534
 
340
- idents.map do |ident|
341
- id = valid_id ident
342
- no_valid_ids << ident unless id
343
- id
344
- end
345
- idents.compact!
346
-
347
- # TODO: some rsrcs have a 'bulk delete' version...
348
- idents.each { |id| cnx.delete "#{rsrc_path}/#{id}" }
535
+ errs = []
536
+ ids.each do |id_to_delete|
537
+ begin
538
+ cnx.delete "#{rsrc_path}/#{id_to_delete}"
539
+ rescue Jamf::Connection::APIError => e
540
+ raise e unless e.httpStatus == 404
349
541
 
350
- no_valid_ids
542
+ errs += e.errors
543
+ end # begin
544
+ end # ids.each
545
+ errs
351
546
  end
352
547
 
353
548
  # Private Class Methods
@@ -359,7 +554,7 @@ module Jamf
359
554
  list_method_name = "all_#{attr_name}s"
360
555
 
361
556
  define_singleton_method(list_method_name) do |refresh = false, cnx: Jamf.cnx|
362
- all_list = all(refresh, cnx: cnx)
557
+ all_list = all(refresh: refresh, cnx: cnx)
363
558
  if attr_def[:class].is_a? Symbol
364
559
  all_list.map { |i| i[attr_name] }.uniq
365
560
  else
@@ -378,24 +573,6 @@ module Jamf
378
573
  end # create_list_methods
379
574
  private_class_method :create_list_methods
380
575
 
381
- # Given an indentier attr. key, and a value,
382
- # return the id where that ident has that value, or nil
383
- #
384
- def self.id_from_other_ident(ident, value, refresh = true, cnx: Jamf.cnx)
385
- raise ArgumentError, "Unknown identifier '#{ident}' for #{self}" unless identifiers.include? ident
386
-
387
- # check the id itself first
388
- return value if ident == :id && all_ids(refresh, cnx: cnx).include?(value)
389
-
390
- # all ident values => ids
391
- ident_map = map_all(ident, to: :id, cnx: cnx, refresh: refresh)
392
-
393
- # case-insensitivity for string values
394
- value = ident_map.keys.j_ci_fetch(value) if value.is_a? String
395
-
396
- ident_map[value]
397
- end
398
- private_class_method :id_from_other_ident
399
576
 
400
577
  # Instance Methods
401
578
  #####################################
@@ -406,11 +583,13 @@ module Jamf
406
583
 
407
584
  def rsrc_path
408
585
  return unless exist?
586
+
409
587
  "#{self.class.rsrc_path}/#{@id}"
410
588
  end
411
589
 
412
590
  def delete
413
591
  raise Jamf::UnsupportedError, "Deleting #{self} objects is not currently supported" unless self.class.deletable?
592
+
414
593
  @cnx.delete rsrc_path
415
594
  end
416
595