ruby-jss 1.0.4 → 1.1.0b1
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.
- checksums.yaml +4 -4
- data/CHANGES.md +26 -0
- data/lib/jss.rb +47 -24
- data/lib/jss/api_connection.rb +39 -7
- data/lib/jss/api_object.rb +651 -319
- data/lib/jss/api_object/account.rb +19 -5
- data/lib/jss/api_object/advanced_search/advanced_computer_search.rb +0 -3
- data/lib/jss/api_object/advanced_search/advanced_mobile_device_search.rb +0 -3
- data/lib/jss/api_object/advanced_search/advanced_user_search.rb +0 -3
- data/lib/jss/api_object/building.rb +0 -3
- data/lib/jss/api_object/category.rb +0 -3
- data/lib/jss/api_object/computer.rb +83 -28
- data/lib/jss/api_object/computer_invitation.rb +1 -11
- data/lib/jss/api_object/configuration_profile/osx_configuration_profile.rb +0 -3
- data/lib/jss/api_object/department.rb +0 -3
- data/lib/jss/api_object/distribution_point.rb +0 -3
- data/lib/jss/api_object/extendable.rb +113 -57
- data/lib/jss/api_object/extension_attribute.rb +46 -13
- data/lib/jss/api_object/extension_attribute/computer_extension_attribute.rb +0 -3
- data/lib/jss/api_object/extension_attribute/mobile_device_extension_attribute.rb +56 -19
- data/lib/jss/api_object/extension_attribute/user_extension_attribute.rb +0 -3
- data/lib/jss/api_object/group/computer_group.rb +0 -3
- data/lib/jss/api_object/group/mobile_device_group.rb +0 -3
- data/lib/jss/api_object/group/user_group.rb +0 -3
- data/lib/jss/api_object/ldap_server.rb +0 -3
- data/lib/jss/api_object/mobile_device.rb +25 -29
- data/lib/jss/api_object/mobile_device_application.rb +1 -9
- data/lib/jss/api_object/netboot_server.rb +0 -3
- data/lib/jss/api_object/network_segment.rb +0 -3
- data/lib/jss/api_object/package.rb +0 -3
- data/lib/jss/api_object/patch_source/patch_external_source.rb +0 -2
- data/lib/jss/api_object/patch_source/patch_internal_source.rb +0 -3
- data/lib/jss/api_object/patch_title.rb +1 -2
- data/lib/jss/api_object/peripheral.rb +0 -3
- data/lib/jss/api_object/peripheral_type.rb +0 -2
- data/lib/jss/api_object/policy.rb +20 -2
- data/lib/jss/api_object/removable_macaddr.rb +0 -3
- data/lib/jss/api_object/restricted_software.rb +0 -3
- data/lib/jss/api_object/scopable.rb +0 -12
- data/lib/jss/api_object/scopable/scope.rb +1 -1
- data/lib/jss/api_object/script.rb +0 -3
- data/lib/jss/api_object/self_servable.rb +3 -1
- data/lib/jss/api_object/site.rb +0 -3
- data/lib/jss/api_object/software_update_server.rb +0 -3
- data/lib/jss/api_object/user.rb +0 -3
- data/lib/jss/api_object/webhook.rb +0 -3
- data/lib/jss/compatibility.rb +74 -53
- data/lib/jss/exceptions.rb +5 -0
- data/lib/jss/ruby_extensions/string.rb +4 -48
- data/lib/jss/ruby_extensions/string/conversions.rb +69 -0
- data/lib/jss/ruby_extensions/string/predicates.rb +47 -0
- data/lib/jss/version.rb +1 -1
- data/test/README.md +2 -4
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18ed86e89f9b9994f582c691ac460e1ee6262e67cb02fcc42352f84b6d2b1e8a
|
4
|
+
data.tar.gz: f5b535f7cf9f778b42b2cc7367f2e5ddce6cbc08e766f75f5ed8413cc42efc78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 88e9391e8d5d5c8af60176599dd86303e8a48c96ba9a643664c7cf7a8d0ac257340a4d561a426ff5cd4e8c73fbfc102a0f735703fa462222fa95f4cc75a4078d
|
7
|
+
data.tar.gz: 87ef3da3ad033c0f17b9511a4a265ddcf5479aee5e71d385c880a0e298627e881bb73f90814ebb4c7feac08455c321036d69802a74ef24eca74e092737d33054
|
data/CHANGES.md
CHANGED
@@ -6,6 +6,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## \[Unreleased]
|
8
8
|
### Added
|
9
|
+
- MobileDeviceExtensionAttribute now has a `.history` class method matching that of ComputerExtensionAttribute. Requires direct MySQL database access. Thanks @aurica!
|
10
|
+
- JSS::AmbiguousError exception class
|
11
|
+
- More caching of API data to improve general speed
|
12
|
+
- The hashes created by `APIObject.map_all_ids_to(blah)`
|
13
|
+
- ExtensionAttribute definitions when used by extendable classes
|
14
|
+
- Implemented Ruby2.4's `String#casecmp?` in older Rubies
|
15
|
+
- APIObject.fetch can take the search term `:random` and you'll get a randomly selected object. Example: `a_random_computer = JSS::Computer.fetch :random`
|
16
|
+
- Keys of the hash returned by `Computer#hardware` are now available as instance methods on Computer objects. So instead of `a_computer.hardware[:total_ram]` you can also do `a_computer.total_ram`
|
17
|
+
|
18
|
+
|
19
|
+
### Fixed
|
20
|
+
- Can't Modify Frozen Hash error when instantiating JSS::Scopbable::Scope. Thanks to @shahn for reporting this one.
|
21
|
+
- MobileDeviceExtensionAttribute now handles empty `as_of` timestamp. Thanks @aurica!
|
22
|
+
- A couple of typos. Thanks to @cybertunnel for finding one.
|
23
|
+
- A bug when parsing the `server_path` parameter to `API::Connection.new`
|
24
|
+
|
25
|
+
### Changed
|
26
|
+
- Monkey Patches are being moved to a better, more traceable technique, see https://www.justinweiss.com/articles/3-ways-to-monkey-patch-without-making-a-mess/
|
27
|
+
- MobileDevices and Computers now raise JSS::AmbiguousError when being fetched by explicitly by name, e.g. `JSS::Computer.fetch name: 'foo'` and that name is not unique in the JSS. Previously, you'd get an object back, but no guarantee as to which one it was. You'll still get an undefined object if you use a bare searchterm, e.g. `JSS::Computer.fetch 'foo'`
|
28
|
+
- Documentation for subclassing APIObject is updated & expanded. See the comments above the class definition in api_object.rb
|
29
|
+
- `APIObject.valid_id` is now case-insensitive
|
30
|
+
- Removed deprecated VALID_DATA_KEYS constants from APIObject subclasses
|
31
|
+
- Various changes in APIObject and its subclasses to try making `.fetch` and other lookup-methods faster.
|
32
|
+
|
33
|
+
## \[1.0.4] - 2019-05-06
|
34
|
+
### Added
|
9
35
|
- JSS::Group (and its subclasses) now have a `change_membership` class and instance method for static groups.
|
10
36
|
- The class method allows adding and removing members without fetching an instance of the group.
|
11
37
|
- The instance method adds and/or removes members immediately, without needing to call #update or #save
|
data/lib/jss.rb
CHANGED
@@ -92,6 +92,9 @@ module JSS
|
|
92
92
|
### Module Methods
|
93
93
|
#####################################
|
94
94
|
|
95
|
+
# TODO: Find a better way to do all this - possibly with
|
96
|
+
# autoloading.
|
97
|
+
|
95
98
|
### Define classes and submodules here so that they don't
|
96
99
|
### generate errors when referenced during the loading of
|
97
100
|
### the library.
|
@@ -102,31 +105,68 @@ module JSS
|
|
102
105
|
|
103
106
|
module Composer; end
|
104
107
|
|
108
|
+
### Mix-in Sub Modules
|
109
|
+
|
110
|
+
module Creatable; end
|
111
|
+
module FileUpload; end
|
112
|
+
module Locatable; end
|
113
|
+
module Matchable; end
|
114
|
+
module Purchasable; end
|
115
|
+
module Updatable; end
|
116
|
+
module Extendable; end
|
117
|
+
module SelfServable; end
|
118
|
+
module Categorizable; end
|
119
|
+
module VPPable; end
|
120
|
+
module Sitable; end
|
121
|
+
module MDM; end
|
122
|
+
module ManagementHistory; end
|
123
|
+
|
105
124
|
### Mix-in Sub Modules with Classes
|
106
125
|
|
107
126
|
module Criteriable
|
127
|
+
|
108
128
|
class Criteria; end
|
109
129
|
class Criterion; end
|
130
|
+
|
110
131
|
end
|
132
|
+
|
111
133
|
module Scopable
|
112
|
-
|
134
|
+
|
135
|
+
class Scope; end
|
136
|
+
|
113
137
|
end
|
114
138
|
|
115
139
|
### Classes
|
116
140
|
#####################################
|
117
141
|
|
118
|
-
class APIObject; end
|
119
142
|
class APIConnection; end
|
120
143
|
class DBConnection; end
|
121
144
|
class Server; end
|
122
145
|
class Icon; end
|
123
146
|
class Preferences; end
|
124
|
-
|
125
|
-
|
126
|
-
|
147
|
+
# TODO: see if this can be made into a module:
|
148
|
+
class Client; end
|
149
|
+
|
150
|
+
# Parent of all fetchable objects.
|
151
|
+
#
|
152
|
+
class APIObject
|
153
|
+
|
154
|
+
# Builtin ruby callback, whenver a subclass is created.
|
155
|
+
#
|
156
|
+
# Just store the subclass name, at the end of all the requires, we'll
|
157
|
+
# call define_identifier_list_methods on everything we stored here.
|
158
|
+
#
|
159
|
+
def self.inherited(subclass)
|
160
|
+
@subclasses ||= []
|
161
|
+
@subclasses << subclass
|
162
|
+
end
|
163
|
+
|
164
|
+
end # class APIObject
|
165
|
+
|
166
|
+
### APIObject SubClasses
|
127
167
|
#####################################
|
128
168
|
|
129
|
-
### APIObject
|
169
|
+
### APIObject SubClasses with SubClasses
|
130
170
|
|
131
171
|
class AdvancedSearch < JSS::APIObject; end
|
132
172
|
class AdvancedComputerSearch < JSS::AdvancedSearch; end
|
@@ -147,7 +187,7 @@ module JSS
|
|
147
187
|
class OSXConfigurationProfile < JSS::ConfigurationProfile; end
|
148
188
|
class MobileDeviceConfigurationProfile < JSS::ConfigurationProfile; end
|
149
189
|
|
150
|
-
### APIObject
|
190
|
+
### APIObject SubClasses without SubClasses
|
151
191
|
|
152
192
|
class Account < JSS::APIObject; end
|
153
193
|
class Building < JSS::APIObject; end
|
@@ -175,23 +215,6 @@ module JSS
|
|
175
215
|
class User < JSS::APIObject; end
|
176
216
|
class WebHook < JSS::APIObject; end
|
177
217
|
|
178
|
-
### Mix-in Sub Modules
|
179
|
-
|
180
|
-
module Creatable; end
|
181
|
-
module FileUpload; end
|
182
|
-
module Locatable; end
|
183
|
-
module Matchable; end
|
184
|
-
module Purchasable; end
|
185
|
-
module Updatable; end
|
186
|
-
module Extendable; end
|
187
|
-
module SelfServable; end
|
188
|
-
module Categorizable; end
|
189
|
-
module VPPable; end
|
190
|
-
module Sitable; end
|
191
|
-
module MDM; end
|
192
|
-
module ManagementHistory; end
|
193
|
-
|
194
|
-
|
195
218
|
end # module JSS
|
196
219
|
|
197
220
|
### Load the rest of the module
|
data/lib/jss/api_connection.rb
CHANGED
@@ -365,16 +365,46 @@ module JSS
|
|
365
365
|
attr_reader :name
|
366
366
|
|
367
367
|
# @return [Hash]
|
368
|
-
# This Hash
|
369
|
-
#
|
368
|
+
# This Hash caches the result of the the first API query for an APIObject
|
369
|
+
# subclass's .all summary list, keyed by the subclass's RSRC_LIST_KEY.
|
370
370
|
# See the APIObject.all class method.
|
371
371
|
#
|
372
|
-
#
|
372
|
+
# It also holds related data items for speedier processing:
|
373
|
+
#
|
374
|
+
# - The Hashes created by APIObject.map_all_ids_to(foo), keyed by
|
375
|
+
# "#{RSRC_LIST_KEY}_map_#{other_key}".to_sym
|
376
|
+
#
|
377
|
+
# - This hash also holds a cache of the rarely-used APIObject.all_objects
|
378
|
+
# hash, keyed by "#{RSRC_LIST_KEY}_objects".to_sym
|
379
|
+
#
|
380
|
+
#
|
381
|
+
# When APIObject.all, and related methods are called without an argument,
|
373
382
|
# and this hash has a matching value, the value is returned, rather than
|
374
383
|
# requerying the API. The first time a class calls .all, or whnever refresh
|
375
384
|
# is not false, the API is queried and the value in this hash is updated.
|
376
385
|
attr_reader :object_list_cache
|
377
386
|
|
387
|
+
# @return [Hash{Class: Hash{String => JSS::ExtensionAttribute}}]
|
388
|
+
# This Hash caches the Extension Attribute
|
389
|
+
# definition objects for the three types of ext. attribs:
|
390
|
+
# ComputerExtensionAttribute, MobileDeviceExtensionAttribute, and
|
391
|
+
# UserExtensionAttribute, whenever they are fetched for parsing or
|
392
|
+
# validating extention attribute data.
|
393
|
+
#
|
394
|
+
# The top-level keys are the EA classes themselves:
|
395
|
+
# - ComputerExtensionAttribute
|
396
|
+
# - MobileDeviceExtensionAttribute
|
397
|
+
# - UserExtensionAttribute
|
398
|
+
#
|
399
|
+
# These each point to a Hash of their instances, keyed by name, e.g.
|
400
|
+
# {
|
401
|
+
# "A Computer EA" => <JSS::ComputerExtensionAttribute...>,
|
402
|
+
# "A different Computer EA" => <JSS::ComputerExtensionAttribute...>,
|
403
|
+
# ...
|
404
|
+
# }
|
405
|
+
#
|
406
|
+
attr_reader :ext_attr_definition_cache
|
407
|
+
|
378
408
|
# Constructor
|
379
409
|
#####################################
|
380
410
|
|
@@ -393,6 +423,7 @@ module JSS
|
|
393
423
|
@name ||= :disconnected
|
394
424
|
@connected = false
|
395
425
|
@object_list_cache = {}
|
426
|
+
@ext_attr_definition_cache = {}
|
396
427
|
connect args unless args.empty?
|
397
428
|
end # init
|
398
429
|
|
@@ -936,6 +967,7 @@ module JSS
|
|
936
967
|
vars.delete :@network_ranges
|
937
968
|
vars.delete :@my_distribution_point
|
938
969
|
vars.delete :@master_distribution_point
|
970
|
+
vars.delete :@ext_attr_definition_cache
|
939
971
|
vars
|
940
972
|
end
|
941
973
|
|
@@ -1070,11 +1102,11 @@ module JSS
|
|
1070
1102
|
@server_host = args[:server]
|
1071
1103
|
@port = args[:port].to_i
|
1072
1104
|
|
1105
|
+
# trim any potential leading slash on server_path, ensure a trailing slash
|
1073
1106
|
if args[:server_path]
|
1074
|
-
|
1075
|
-
@server_path =
|
1076
|
-
|
1077
|
-
@server_path << '/'
|
1107
|
+
@server_path = args[:server_path]
|
1108
|
+
@server_path = @server_path[1..-1] if @server_path.start_with? '/'
|
1109
|
+
@server_path << '/' unless @server_path.end_with? '/'
|
1078
1110
|
end
|
1079
1111
|
|
1080
1112
|
# we're using ssl if:
|
data/lib/jss/api_object.rb
CHANGED
@@ -26,44 +26,46 @@
|
|
26
26
|
###
|
27
27
|
module JSS
|
28
28
|
|
29
|
-
# Module Variables
|
30
|
-
#####################################
|
31
|
-
|
32
|
-
# Module Methods
|
33
|
-
#####################################
|
34
|
-
|
35
29
|
# Classes
|
36
30
|
#####################################
|
37
31
|
|
38
|
-
# This class is the parent to all JSS API objects. It provides standard
|
39
|
-
# that apply to all API resouces.
|
32
|
+
# This class is the parent to all JSS API objects. It provides standard
|
33
|
+
# methods and constants that apply to all API resouces.
|
40
34
|
#
|
41
|
-
# See the README.md file for general info about using subclasses of
|
35
|
+
# See the README.md file for general info about using subclasses of
|
36
|
+
# JSS::APIObject
|
42
37
|
#
|
43
38
|
# == Subclassing
|
44
39
|
#
|
45
|
-
# === Constructor
|
40
|
+
# === Initilize / Constructor
|
41
|
+
#
|
42
|
+
# All subclasses must call `super` in their initialize method, which will
|
43
|
+
# call the method defined here in APIObject. Not only does this retrieve the
|
44
|
+
# data from the API, it parses the raw JSON data into a Hash, & stores it in
|
45
|
+
# @init_data.
|
46
46
|
#
|
47
47
|
# In general, subclasses should do any class-specific argument checking before
|
48
|
-
# calling super, and then afterwards
|
49
|
-
# any class-specific attributes. @id, @name, @rest_rsrc,
|
48
|
+
# calling super, and then afterwards use the contents of @init_data to
|
49
|
+
# populate any class-specific attributes. Populating @id, @name, @rest_rsrc,
|
50
|
+
# and @in_jss are handled here.
|
50
51
|
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
52
|
+
# This class also handles parsing @init_data for any mixed-in modules, e.g.
|
53
|
+
# Scopable, Categorizable or Extendable. See those modules for any
|
54
|
+
# requirements they have when including them.
|
54
55
|
#
|
55
56
|
# === Object Creation
|
56
57
|
#
|
57
|
-
# If a subclass should be able to be created in the JSS be sure to include
|
58
|
+
# If a subclass should be able to be created in the JSS be sure to include
|
59
|
+
# {JSS::Creatable}
|
58
60
|
#
|
59
|
-
# The constructor should verify any extra required data
|
60
|
-
# calling super.
|
61
|
+
# The constructor should verify any extra required data in the args
|
61
62
|
#
|
62
63
|
# See {JSS::Creatable} for more details.
|
63
64
|
#
|
64
65
|
# === Object Modification
|
65
66
|
#
|
66
|
-
# If a subclass should be modifiable in the JSS, include {JSS::Updatable},
|
67
|
+
# If a subclass should be modifiable in the JSS, include {JSS::Updatable},
|
68
|
+
# q.v. for details.
|
67
69
|
#
|
68
70
|
# === Object Deletion
|
69
71
|
#
|
@@ -71,90 +73,370 @@ module JSS
|
|
71
73
|
#
|
72
74
|
# === Required Constants
|
73
75
|
#
|
74
|
-
# Subclasses *must* provide certain
|
75
|
-
# communicate with the API
|
76
|
+
# Subclasses *must* provide certain constants in order to correctly interpret
|
77
|
+
# API data and communicate with the API:
|
78
|
+
#
|
79
|
+
# ==== RSRC_BASE [String]
|
80
|
+
#
|
81
|
+
# The base for REST resources of this class
|
82
|
+
#
|
83
|
+
# e.g. 'computergroups' in
|
84
|
+
# https://casper.mycompany.com:8443/JSSResource/computergroups/id/12
|
76
85
|
#
|
77
|
-
# ====
|
86
|
+
# ==== RSRC_LIST_KEY [Symbol]
|
78
87
|
#
|
79
|
-
#
|
88
|
+
# When GETting the RSRC_BASE for a subclass, an Array of Hashes is returned
|
89
|
+
# with one Hash of basic info for each object of that type in the JSS. All
|
90
|
+
# objects have their JSS id and name in that Hash, some have other data as
|
91
|
+
# well. This Array is used for a variety of purposes when using ruby-jss,
|
92
|
+
# since it gives you basic info about all objects, without having to fetch
|
93
|
+
# each one individually.
|
80
94
|
#
|
81
|
-
#
|
95
|
+
# Here's the top of the output from the 'computergroups' RSRC_BASE:
|
82
96
|
#
|
83
|
-
#
|
84
|
-
#
|
97
|
+
# {:computer_groups=>
|
98
|
+
# [{:id=>1020, :name=>"config-no-turnstile", :is_smart=>true},
|
99
|
+
# {:id=>1357, :name=>"10.8 Laptops", :is_smart=>true},
|
100
|
+
# {:id=>1094, :name=>"wifi_cert-expired", :is_smart=>true},
|
101
|
+
# {:id=>1144, :name=>"mytestgroup", :is_smart=>false},
|
102
|
+
# ...
|
85
103
|
#
|
86
|
-
#
|
87
|
-
#
|
104
|
+
# Notice that the Array we want is embedded in a one-item Hash, and the
|
105
|
+
# key in that Hash for the desired Array is the Symbol :computer_groups.
|
88
106
|
#
|
89
|
-
#
|
90
|
-
# a hash with one item (another hash with details of one computergroup).
|
91
|
-
# That item's key is the Symbol :computer_group
|
107
|
+
# That symbol is the value needed in the RSRC_LIST_KEY constant.
|
92
108
|
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
# to
|
109
|
+
# The '.all_ids', '.all_names' and other '.all_*' class methods use the
|
110
|
+
# list-resource Array to extract other Arrays of the desired values - which
|
111
|
+
# can be used to check for existance without retrieving an entire object,
|
112
|
+
# among other uses.
|
96
113
|
#
|
97
|
-
#
|
98
|
-
# If any of these don't exist in the hash's keys, then the :data is
|
99
|
-
# not valid and an exception is raised.
|
114
|
+
# ==== RSRC_OBJECT_KEY [Symbol]
|
100
115
|
#
|
101
|
-
# The
|
102
|
-
#
|
116
|
+
# The one-item Hash key used for individual JSON object output. It's also
|
117
|
+
# used in various error messages
|
103
118
|
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
119
|
+
# As with the list-resource output mentioned above, when GETting a specific
|
120
|
+
# object resource, there's an extra layer of encapsulation in a one-item Hash.
|
121
|
+
# Here's the top of the JSON for a single computer group fetched
|
122
|
+
# from '...computergroups/id/1043'
|
107
123
|
#
|
108
|
-
#
|
109
|
-
#
|
124
|
+
# {:computer_group=>
|
125
|
+
# {:id=>1043,
|
126
|
+
# :name=>"tmp-no-d3",
|
127
|
+
# :is_smart=>false,
|
128
|
+
# :site=>{:id=>-1, :name=>"None"},
|
129
|
+
# :criteria=>[],
|
130
|
+
# :computers=>[
|
131
|
+
# ...
|
110
132
|
#
|
133
|
+
# The data for the group itself is the inner Hash.
|
134
|
+
#
|
135
|
+
# The RSRC_OBJECT_KEY in this case is set to :computer_group - the key
|
136
|
+
# in the top-level, one-item Hash that we need to get the real Hash about the
|
137
|
+
# object.
|
111
138
|
#
|
112
139
|
# === Optional Constants
|
113
140
|
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
141
|
+
# === OTHER_LOOKUP_KEYS
|
142
|
+
#
|
143
|
+
# Fetching individual objects from the API is usuallly done via the object's
|
144
|
+
# unique JSS id, via a resrouce URL like so:
|
145
|
+
#
|
146
|
+
# ...JSSResource/<RSRC_BASE>/id/<idnumber>
|
147
|
+
#
|
148
|
+
# Most objects can also be looked-up by name, because the API also has
|
149
|
+
# and endpoint ..JSSResource/<RSRC_BASE>/name/<name>
|
150
|
+
# (See {NON_UNIQUE_NAMES} below)
|
151
|
+
#
|
152
|
+
# Some objects, like Computers and MobileDevices, have other values that
|
153
|
+
# serve as unique identifiers and can also be used as 'lookup keys' for
|
154
|
+
# fetching individual objects. When this is the case, those values always
|
155
|
+
# appear in the objects list-resource data (See {RSRC_LIST_KEY} above).
|
156
|
+
#
|
157
|
+
# For example, here's a summary-hash for a single MobileDevice from the
|
158
|
+
# list-resource '...JSSResource/mobiledevices', which you might get in the
|
159
|
+
# Array returned by JSS::MobileDevice.all:
|
160
|
+
#
|
161
|
+
# {
|
162
|
+
# :id=>3964,
|
163
|
+
# :name=>"Bear",
|
164
|
+
# :device_name=>"Bear",
|
165
|
+
# :udid=>"XXX",
|
166
|
+
# :serial_number=>"YYY2244MM60",
|
167
|
+
# :phone_number=>"510-555-1212",
|
168
|
+
# :wifi_mac_address=>"00:00:00:00:00:00",
|
169
|
+
# :managed=>true,
|
170
|
+
# :supervised=>false,
|
171
|
+
# :model=>"iPad Pro (9.7-inch Cellular)",
|
172
|
+
# :model_identifier=>"iPad6,4",
|
173
|
+
# :modelDisplay=>"iPad Pro (9.7-inch Cellular)",
|
174
|
+
# :model_display=>"iPad Pro (9.7-inch Cellular)",
|
175
|
+
# :username=>"fred"
|
176
|
+
# }
|
177
|
+
#
|
178
|
+
# For MobileDevices, serial_number, udid, and wifi_mac_address are also
|
179
|
+
# all unique identifiers for an individual device, and can be used to
|
180
|
+
# fetch them.
|
181
|
+
#
|
182
|
+
# To specify other identifiers for an APIObject subclass, create the constant
|
183
|
+
# OTHER_LOOKUP_KEYS containing a Hash of Hashes, like so:
|
184
|
+
#
|
185
|
+
# OTHER_LOOKUP_KEYS = {
|
186
|
+
# serial_number: {
|
187
|
+
# aliases: [:serialnumber, :sn],
|
188
|
+
# fetch_rsrc_key: :serialnumber
|
189
|
+
# },
|
190
|
+
# udid: {
|
191
|
+
# fetch_rsrc_key: :udid
|
192
|
+
# },
|
193
|
+
# wifi_mac_address: {
|
194
|
+
# aliases: [:macaddress, :macaddr],
|
195
|
+
# fetch_rsrc_key: :macaddress
|
196
|
+
# }
|
197
|
+
# }.freeze
|
198
|
+
#
|
199
|
+
# The keys in OTHER_LOOKUP_KEYS are the keys in a summary-hash data from .all
|
200
|
+
# that hold a unique identifier. Each value is a Hash with one or two keys:
|
201
|
+
#
|
202
|
+
# - aliases: [Array<Symbol>]
|
203
|
+
# Aliases for that identifier, i.e. abbreviations or spelling variants.
|
204
|
+
# These aliases can be used in fetching, and they also have
|
205
|
+
# matching `.all_<aliase>s` methods.
|
206
|
+
#
|
207
|
+
# If no aliases are needed, don't specify anything, as with the udid:
|
208
|
+
# in the example above
|
209
|
+
#
|
210
|
+
# - fetch_rsrc_key: [Symbol]
|
211
|
+
# Often a unique identifier can be used to build a URL for fetching (or
|
212
|
+
# updating or deleteing) an object with that value, rather than with id.
|
213
|
+
# For example, while the MobileDevice in the example data above would
|
214
|
+
# normally be fetched at the resource 'JSSResource/mobiledevices/id/3964'
|
215
|
+
# it can also be fetched at
|
216
|
+
# 'JSSResource/mobiledevices/serialnumber/YYY2244MM60'.
|
217
|
+
# Since the URL is built using 'serialnumber', the symbol :serialnumber
|
218
|
+
# is used as the fetch_rsrc_key.
|
219
|
+
#
|
220
|
+
# Setting a fetch_rsrc_key: for one of the OTHER_LOOKUP_KEYS tells ruby-jss
|
221
|
+
# that such a URL is available, and fetching by that lookup key will be
|
222
|
+
# faster when using that URL.
|
223
|
+
#
|
224
|
+
# If a fetch_rsrc_key is not set, fetching will be slower, since the fetch
|
225
|
+
# method must first refresh the list of all available objects to find the
|
226
|
+
# id to use for building the resource URL.
|
227
|
+
# This is also true when fetching without specifying which lookup key to
|
228
|
+
# use, e.g. `.fetch 'foo'` vs. `.fetch sn: 'foo'`
|
229
|
+
#
|
230
|
+
# The OTHER_LOOKUP_KEYS, if defined, are merged with the DEFAULT_LOOKUP_KEYS
|
231
|
+
# defined below via the {APIObject.lookup_keys} class method, They are used for:
|
232
|
+
#
|
233
|
+
# - creating list-methods:
|
234
|
+
# For each lookup key, a class method `.all_<key>s` is created
|
235
|
+
# automatically, e.g. `.all_serial_numbers`. The aliases are used to
|
236
|
+
# make alises of those methods, e.g. `.all_sns`
|
237
|
+
#
|
238
|
+
# - finding valid ids:
|
239
|
+
# The {APIObject.valid_id} class method looks at the known lookup keys to
|
240
|
+
# find an object's id.
|
241
|
+
#
|
242
|
+
# - fetching:
|
243
|
+
# When an indentifier is given to `.fetch`, the fetch_rsrc_key is used to
|
244
|
+
# build the resource URL for fetching the object. If there is no
|
245
|
+
# fetch_rsrc_key, the lookup_keys and aliases are used to find the matching
|
246
|
+
# id, which is used to build the URL.
|
247
|
+
#
|
248
|
+
# When no identifier is specified, .fetch uses .valid_id, described above.
|
249
|
+
#
|
250
|
+
# ==== NON_UNIQUE_NAMES
|
251
|
+
#
|
252
|
+
# Some JSS objects, like Computers and MobileDevices, do not treat names
|
253
|
+
# as unique in the JSS, but they can still be used for fetching objects.
|
254
|
+
# The API itself will return data for a non-unique name lookup, but there's
|
255
|
+
# no way to guarantee which object you get back.
|
256
|
+
#
|
257
|
+
# In those subclasses, set NON_UNIQUE_NAMES to any value, and a
|
258
|
+
# JSS::AmbiguousError exception will be raised when trying to fetch by name
|
259
|
+
# and the name isn't unique.
|
260
|
+
#
|
261
|
+
# Because of the extra processing, the check for this state will only happen
|
262
|
+
# when NON_UNIQUE_NAMES is set. If not set at all, the check doesn't happen
|
263
|
+
# and if multiple objects have the same name, which one is returned is
|
264
|
+
# undefined.
|
265
|
+
#
|
266
|
+
# When that's the case, fetching explicitly by name, or when fetching with a
|
267
|
+
# plain search term that matches a non-unique name, will raise a
|
268
|
+
# JSS::AmbiguousError exception,when the name isn't unique. If that happens,
|
269
|
+
# you'll have to use some other identifier to fetch the desired object.
|
270
|
+
#
|
271
|
+
# Note: Fetching, finding valid id, and name collisions are case-insensitive.
|
120
272
|
#
|
121
273
|
class APIObject
|
122
274
|
|
123
275
|
# Constants
|
124
276
|
####################################
|
125
277
|
|
278
|
+
# '.new' can only be called from these methods:
|
126
279
|
OK_INSTANTIATORS = ['make', 'fetch', 'block in fetch'].freeze
|
127
280
|
|
281
|
+
# See the discussion of 'Lookup Keys' in the comments/docs
|
282
|
+
# for {JSS::APIObject}
|
283
|
+
#
|
284
|
+
DEFAULT_LOOKUP_KEYS = {
|
285
|
+
id: { fetch_rsrc_key: :id },
|
286
|
+
name: { fetch_rsrc_key: :name }
|
287
|
+
}.freeze
|
288
|
+
|
289
|
+
# This table holds the object history for JSS objects.
|
290
|
+
# Object history is not available via the API,
|
291
|
+
# only MySQL.
|
292
|
+
OBJECT_HISTORY_TABLE = 'object_history'.freeze
|
293
|
+
|
128
294
|
# Class Methods
|
129
295
|
#####################################
|
130
296
|
|
297
|
+
# What are all the lookup keys available for this class, with
|
298
|
+
# all their aliases (or optionally not) or with their fetch_rsrc_keys
|
299
|
+
#
|
300
|
+
# This method combines the DEFAULT_LOOOKUP_KEYS defined above, with the
|
301
|
+
# optional OTHER_LOOKUP_KEYS from a subclass (See 'Lookup Keys' in the
|
302
|
+
# class comments/docs above)
|
303
|
+
#
|
304
|
+
# The hash returned flattens and inverts the two source hashes, so that
|
305
|
+
# all possible lookup keys (the keys and their aliases) are hash keys
|
306
|
+
# and the non-aliased lookup key is the value.
|
307
|
+
#
|
308
|
+
# For example, when
|
309
|
+
#
|
310
|
+
# OTHER_LOOKUP_KEYS = {
|
311
|
+
# serial_number: { aliases: [:serialnumber, :sn], fetch_rsrc_key: :serialnumber },
|
312
|
+
# udid: { fetch_rsrc_key: :udid },
|
313
|
+
# wifi_mac_address: { aliases: [:macaddress, :macaddr], fetch_rsrc_key: :macaddress }
|
314
|
+
# }
|
315
|
+
#
|
316
|
+
# It is combined with DEFAULT_LOOKUP_KEYS to produce:
|
317
|
+
#
|
318
|
+
# {
|
319
|
+
# id: :id,
|
320
|
+
# name: :name,
|
321
|
+
# serial_number: :serial_number,
|
322
|
+
# serialnumber: :serial_number,
|
323
|
+
# sn: :serial_number,
|
324
|
+
# udid: :udid,
|
325
|
+
# wifi_mac_address: :wifi_mac_address,
|
326
|
+
# macaddress: :wifi_mac_address,
|
327
|
+
# macaddr: :wifi_mac_address
|
328
|
+
# }
|
329
|
+
#
|
330
|
+
# If the optional parameter no_aliases: is truthy, only the real keynames
|
331
|
+
# are returned in an array, so the above would become
|
332
|
+
#
|
333
|
+
# [:id, :name, :serial_number, :udid, :wifi_mac_address]
|
334
|
+
#
|
335
|
+
# @param no_aliases [Boolean] Only return the real keys, no aliases.
|
336
|
+
#
|
337
|
+
# @return [Hash {Symbol: Symbol}] when no_aliases is falsey, the lookup keys
|
338
|
+
# and aliases for this subclass.
|
339
|
+
#
|
340
|
+
# @return [Array<Symbol>] when no_aliases is truthy, the lookup keys for this
|
341
|
+
# subclass
|
342
|
+
#
|
343
|
+
def self.lookup_keys(no_aliases: false, fetch_rsrc_keys: false)
|
344
|
+
parse_lookup_keys unless @lookup_keys
|
345
|
+
no_aliases ? @lookup_keys.values.uniq : @lookup_keys
|
346
|
+
end
|
347
|
+
|
348
|
+
# Given a lookup or, or an alias of one, return the matching fetch_rsrc_key
|
349
|
+
# for building a fetch/GET resource URL, or nil if no fetch_rsrc_key is defined.
|
350
|
+
#
|
351
|
+
# See {OTHER_LOOKUP_KEYS} in the APIObject class comments/docs above for details.
|
352
|
+
#
|
353
|
+
# @param lookup_key [Symbol] A lookup key, or an aliases of one, for this
|
354
|
+
# subclass.
|
355
|
+
#
|
356
|
+
# @return [Symbol, nil] the fetch_rsrc_key for that lookup key.
|
357
|
+
#
|
358
|
+
def self.fetch_rsrc_key(lookup_key)
|
359
|
+
parse_lookup_keys unless @fetch_rsrc_keys
|
360
|
+
@fetch_rsrc_keys[lookup_key]
|
361
|
+
end
|
362
|
+
|
363
|
+
# Used by .lookup_keys
|
364
|
+
#
|
365
|
+
def self.parse_lookup_keys
|
366
|
+
@lookup_keys = {}
|
367
|
+
@fetch_rsrc_keys = {}
|
368
|
+
|
369
|
+
hsh = DEFAULT_LOOKUP_KEYS.dup
|
370
|
+
hsh.merge!(self::OTHER_LOOKUP_KEYS) if defined? self::OTHER_LOOKUP_KEYS
|
371
|
+
|
372
|
+
hsh.each do |key, info|
|
373
|
+
@lookup_keys[key] = key
|
374
|
+
@fetch_rsrc_keys[key] = info[:fetch_rsrc_key]
|
375
|
+
next unless info[:aliases]
|
376
|
+
|
377
|
+
info[:aliases].each do |a|
|
378
|
+
@lookup_keys[a] = key
|
379
|
+
@fetch_rsrc_keys[a] = info[:fetch_rsrc_key]
|
380
|
+
end
|
381
|
+
end # self::OTHER_LOOKUP_KEYS.each
|
382
|
+
end
|
383
|
+
private_class_method :parse_lookup_keys
|
384
|
+
|
385
|
+
# get the real lookup key frm a given alias
|
386
|
+
#
|
387
|
+
# @param key[Symbol] the key or an aliase of the key
|
388
|
+
#
|
389
|
+
# @return [Symbol] the real key for the given key
|
390
|
+
#
|
391
|
+
def self.real_lookup_key(key)
|
392
|
+
real_key = lookup_keys[key]
|
393
|
+
raise ArgumentError, "Unknown lookup key '#{key}' for #{self}" unless real_key
|
394
|
+
|
395
|
+
real_key
|
396
|
+
end
|
397
|
+
|
131
398
|
# Return an Array of Hashes for all objects of this subclass in the JSS.
|
132
399
|
#
|
133
400
|
# This method is only valid in subclasses of JSS::APIObject, and is
|
134
|
-
# the parsed JSON output of an API query for the resource defined in the
|
401
|
+
# the parsed JSON output of an API query for the resource defined in the
|
402
|
+
# subclass's RSRC_BASE
|
403
|
+
#
|
135
404
|
# e.g. for JSS::Computer, with the RSRC_BASE of :computers,
|
136
405
|
# This method retuens the output of the 'JSSResource/computers' resource,
|
137
406
|
# which is a list of all computers in the JSS.
|
138
407
|
#
|
139
408
|
# Each item in the Array is a Hash with at least two keys, :id and :name.
|
140
|
-
# The class methods .all_ids and .all_names provide easier access to those
|
141
|
-
#
|
409
|
+
# The class methods .all_ids and .all_names provide easier access to those
|
410
|
+
# dataas mapped Arrays.
|
142
411
|
#
|
143
|
-
# Some API classes provide other
|
144
|
-
# and mobile devices) or :is_smart (for groups).
|
412
|
+
# Some API classes provide other keys in each Hash, e.g. :udid (for
|
413
|
+
# computers and mobile devices) or :is_smart (for groups).
|
145
414
|
#
|
146
|
-
#
|
147
|
-
#
|
148
|
-
#
|
415
|
+
# For those keys that are listed in a subclass's lookup_keys method,
|
416
|
+
# there are matching methods `.all_(key)s` which return an array
|
417
|
+
# just of those values, from the values of this hash. For example,
|
418
|
+
# `.all_udids` will use the .all array to return an array of just udids,
|
419
|
+
# if the subclass defines :udid in its OTHER_LOOKUP_KEYS (See 'Lookup Keys'
|
420
|
+
# in the class comments/docs above)
|
149
421
|
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
# to not requery the server every time.
|
422
|
+
# Subclasses should provide appropriate .all_xxx class methods for accessing
|
423
|
+
# any other other values as Arrays, e.g. JSS::Computer.all_managed
|
153
424
|
#
|
154
|
-
#
|
425
|
+
# -- Caching
|
426
|
+
#
|
427
|
+
# The results of the first call to .all for each subclass is cached in the
|
428
|
+
# .object_list_cache of the given {JSS::APIConnection} and that cache is
|
429
|
+
# used for all future calls, so as to not requery the server every time.
|
430
|
+
#
|
431
|
+
# To force requerying to get updated data, provided a truthy argument.
|
155
432
|
# I usually use :refresh, so that it's obvious what I'm doing, but true, 1,
|
156
433
|
# or anything besides false or nil will work.
|
157
434
|
#
|
435
|
+
# The various methods that use the output of this method also take the
|
436
|
+
# refresh parameter which will be passed here as needed.
|
437
|
+
#
|
438
|
+
# -- Alternate API connections
|
439
|
+
#
|
158
440
|
# To query an APIConnection other than the currently active one,
|
159
441
|
# provide one via the api: named parameter.
|
160
442
|
#
|
@@ -166,69 +448,68 @@ module JSS
|
|
166
448
|
# @return [Array<Hash{:name=>String, :id=> Integer}>]
|
167
449
|
#
|
168
450
|
def self.all(refresh = false, api: JSS.api)
|
169
|
-
|
170
|
-
api.object_list_cache[self::RSRC_LIST_KEY] = nil if refresh
|
171
|
-
return api.object_list_cache[self::RSRC_LIST_KEY] if api.object_list_cache[self::RSRC_LIST_KEY]
|
172
|
-
api.object_list_cache[self::RSRC_LIST_KEY] = api.get_rsrc(self::RSRC_BASE)[self::RSRC_LIST_KEY]
|
173
|
-
end
|
451
|
+
validate_not_metaclass(self)
|
174
452
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
# @param refresh[Boolean] should the data be re-queried from the API?
|
182
|
-
#
|
183
|
-
# @param api[JSS::APIConnection] an API connection to use for the query.
|
184
|
-
# Defaults to the corrently active API. See {JSS::APIConnection}
|
185
|
-
#
|
186
|
-
# @return [Array<Integer>] the ids of all it1ems of this subclass in the JSS
|
187
|
-
#
|
188
|
-
def self.all_ids(refresh = false, api: JSS.api)
|
189
|
-
all(refresh, api: api).map { |i| i[:id] }
|
453
|
+
cache = api.object_list_cache
|
454
|
+
cache_key = self::RSRC_LIST_KEY
|
455
|
+
cache[cache_key] = nil if refresh
|
456
|
+
return cache[cache_key] if cache[cache_key]
|
457
|
+
|
458
|
+
cache[cache_key] = api.get_rsrc(self::RSRC_BASE)[cache_key]
|
190
459
|
end
|
191
460
|
|
192
|
-
#
|
193
|
-
# of the subclass.
|
194
|
-
#
|
195
|
-
# e.g. When called from subclass JSS::Computer,
|
196
|
-
# returns the names of all computers in the JSS
|
461
|
+
# @return [Hash {String => Integer}] name => number of occurances
|
197
462
|
#
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
463
|
+
def self.duplicate_names(refresh = false, api: JSS.api)
|
464
|
+
return {} unless defined? self::NON_UNIQUE_NAMES
|
465
|
+
|
466
|
+
dups = {}
|
467
|
+
all(refresh, api: api).each do |obj|
|
468
|
+
if dups[obj[:name]]
|
469
|
+
dups[obj[:name]] += 1
|
470
|
+
else
|
471
|
+
dups[obj[:name]] = 1
|
472
|
+
end # if
|
473
|
+
end # all(refresh, api: api).each
|
474
|
+
dups.delete_if { |k,v| v == 1 }
|
475
|
+
dups
|
207
476
|
end
|
208
477
|
|
209
478
|
# Return a hash of all objects of this subclass
|
210
479
|
# in the JSS where the key is the id, and the value
|
211
480
|
# is some other key in the data items returned by the JSS::APIObject.all.
|
212
481
|
#
|
213
|
-
# If the other key doesn't exist in the API
|
214
|
-
#
|
482
|
+
# If the other key doesn't exist in the API summary data from .all
|
483
|
+
# (eg :udid for JSS::Department) the values will be nil.
|
215
484
|
#
|
216
485
|
# Use this method to map ID numbers to other identifiers returned
|
217
486
|
# by the API list resources. Invert its result to map the other
|
218
487
|
# identfier to ids.
|
219
488
|
#
|
220
489
|
# @example
|
221
|
-
# JSS::Computer.map_all_ids_to(:
|
490
|
+
# JSS::Computer.map_all_ids_to(:serial_number)
|
222
491
|
#
|
223
|
-
# # Returns, eg {2 => "
|
492
|
+
# # Returns, eg {2 => "C02YD3U8JHD3", 5 => "VMMz7xgg8lYZ"}
|
224
493
|
#
|
225
|
-
# JSS::Computer.map_all_ids_to(:
|
494
|
+
# JSS::Computer.map_all_ids_to(:serial_number).invert
|
226
495
|
#
|
227
|
-
# # Returns, eg {"
|
496
|
+
# # Returns, eg {"C02YD3U8JHD3" => 2, "VMMz7xgg8lYZ" => 5}
|
497
|
+
#
|
498
|
+
# These hashes are cached separately from the .all data, and when
|
499
|
+
# the refresh parameter is truthy, both will be refreshed.
|
500
|
+
#
|
501
|
+
# WARNING: Some values in the output of .all are not guaranteed to be unique
|
502
|
+
# in Jamf Pro. This is fine in the direct output of this method, each id
|
503
|
+
# will be the key for some value and many ids might have the same value.
|
504
|
+
# However if you invert that hash, the values become keys, and the ids
|
505
|
+
# become the values, and there can be only one id per each new key. Which
|
506
|
+
# id becomes associated with a value is undefined, and data about the others
|
507
|
+
# is lost. This is especially important if you `.map_all_ids_to :name`,
|
508
|
+
# since, for some objects, names are not unique.
|
228
509
|
#
|
229
510
|
# @param other_key[Symbol] the other data key with which to associate each id
|
230
511
|
#
|
231
|
-
# @param refresh[Boolean] should the data
|
512
|
+
# @param refresh[Boolean] should the data re-queried from the API?
|
232
513
|
#
|
233
514
|
# @param api[JSS::APIConnection] an API connection to use for the query.
|
234
515
|
# Defaults to the corrently active API. See {JSS::APIConnection}
|
@@ -236,16 +517,26 @@ module JSS
|
|
236
517
|
# @return [Hash{Integer => Oject}] the associated ids and data
|
237
518
|
#
|
238
519
|
def self.map_all_ids_to(other_key, refresh = false, api: JSS.api)
|
239
|
-
|
240
|
-
all
|
241
|
-
|
520
|
+
# we will accept any key, it'll just return nil if not in the
|
521
|
+
# .all hashes. However if we're given an alias of a lookup key
|
522
|
+
# we need to convert it to its real name.
|
523
|
+
other_key = lookup_keys[other_key] if lookup_keys[other_key]
|
524
|
+
|
525
|
+
cache_key = "#{self::RSRC_LIST_KEY}_map_#{other_key}".to_sym
|
526
|
+
cache = api.object_list_cache
|
527
|
+
cache[cache_key] = nil if refresh
|
528
|
+
return cache[cache_key] if cache[cache_key]
|
529
|
+
|
530
|
+
map = {}
|
531
|
+
all(refresh, api: api).each { |i| map[i[:id]] = i[other_key] }
|
532
|
+
cache[cache_key] = map
|
242
533
|
end
|
243
534
|
|
244
535
|
# Return an Array of JSS::APIObject subclass instances
|
245
|
-
# e.g when called on JSS::Package, return
|
246
|
-
#
|
536
|
+
# e.g when called on JSS::Package, return a hash of JSS::Package instancesa
|
537
|
+
# for every package in the JSS.
|
247
538
|
#
|
248
|
-
#
|
539
|
+
# WARNING: This may be slow as it has to look up each object individually!
|
249
540
|
# use it wisely.
|
250
541
|
#
|
251
542
|
# @param refresh[Boolean] should the data re-queried from the API?
|
@@ -253,16 +544,32 @@ module JSS
|
|
253
544
|
# @param api[JSS::APIConnection] an API connection to use for the query.
|
254
545
|
# Defaults to the corrently active API. See {JSS::APIConnection}
|
255
546
|
#
|
256
|
-
# @return [
|
547
|
+
# @return [Array<APIObject>] the objects requested
|
257
548
|
#
|
258
549
|
def self.all_objects(refresh = false, api: JSS.api)
|
259
|
-
|
260
|
-
|
261
|
-
|
550
|
+
objects_cache_key ||= "#{self::RSRC_LIST_KEY}_objects".to_sym
|
551
|
+
api_cache = api.object_list_cache
|
552
|
+
api_cache[objects_cache_key] = nil if refresh
|
553
|
+
|
554
|
+
return api_cache[objects_cache_key] if api_cache[objects_cache_key]
|
555
|
+
all = all(refresh, api: api)
|
556
|
+
api_cache[objects_cache_key] = all.map do |o|
|
557
|
+
fetch id: o[:id], api: api, refresh: false
|
558
|
+
end
|
262
559
|
end
|
263
560
|
|
264
|
-
|
265
|
-
#
|
561
|
+
|
562
|
+
# Return the id of the object of this subclass with the given identifier.
|
563
|
+
#
|
564
|
+
# Return nil if no object has an identifier that matches.
|
565
|
+
#
|
566
|
+
# For all objects the 'name' is an identifier. Some objects have more, e.g.
|
567
|
+
# udid, mac_address & serial_number. Matches are case-insensitive.
|
568
|
+
#
|
569
|
+
# NOTE: while name is an identifier, for Computers and MobileDevices, it
|
570
|
+
# need not be unique in Jamf. If name is matched, which one gets returned
|
571
|
+
# is undefined. In short - dont' use names here unless you know they are
|
572
|
+
# unique.
|
266
573
|
#
|
267
574
|
# @param identfier [String,Integer] An identifier for an object, a value for
|
268
575
|
# one of the available lookup_keys
|
@@ -272,14 +579,94 @@ module JSS
|
|
272
579
|
# @param api[JSS::APIConnection] an API connection to use for the query.
|
273
580
|
# Defaults to the corrently active API. See {JSS::APIConnection}
|
274
581
|
#
|
275
|
-
# @return [
|
582
|
+
# @return [Integer, nil] the id of the matching object, or nil if it doesn't exist
|
276
583
|
#
|
277
|
-
def self.
|
278
|
-
|
584
|
+
def self.valid_id(identifier, refresh = false, api: JSS.api)
|
585
|
+
# refresh if needed
|
586
|
+
all(refresh, api: api) if refresh
|
587
|
+
|
588
|
+
# it its a valid id, return it
|
589
|
+
return identifier if all_ids.include? identifier
|
590
|
+
|
591
|
+
keys_to_check = lookup_keys(no_aliases: true)
|
592
|
+
keys_to_check.delete :id # we've already checked :id
|
593
|
+
|
594
|
+
# downcase for speedy case-insensitivity -
|
595
|
+
# include?, and I assume value?, is faster with downcasing. See
|
596
|
+
# https://stackoverflow.com/questions/9333952/case-insensitive-arrayinclude/9334066#9334066
|
597
|
+
identifier.downcase! if identifier.is_a? String
|
598
|
+
|
599
|
+
keys_to_check.each do |key|
|
600
|
+
mapped_ids = map_all_ids_to key, api: api
|
601
|
+
# downcase - see comment above
|
602
|
+
mapped_ids.each { |_k, v| v.downcase! if v.is_a? String }
|
603
|
+
|
604
|
+
# if name is not unique, skip to the next key, there is no
|
605
|
+
# valid id for a non-unique name
|
606
|
+
if key == :name
|
607
|
+
num_name_matches = mapped_ids.values.select { |n| n == identifier }.size
|
608
|
+
next unless num_name_matches == 1
|
609
|
+
else
|
610
|
+
next unless mapped_ids.value? identifier
|
611
|
+
end
|
612
|
+
|
613
|
+
return mapped_ids.invert[identifier]
|
614
|
+
end
|
615
|
+
|
616
|
+
nil
|
617
|
+
end
|
618
|
+
|
619
|
+
# Return the id of the object of this subclass with the given
|
620
|
+
# lookup key == a given identifier.
|
621
|
+
#
|
622
|
+
# Return nil if no object has that value in that key
|
623
|
+
#
|
624
|
+
# @example
|
625
|
+
# # get the id for the computer with serialnum 'xyxyxyxy'
|
626
|
+
# JSS::Computer.id_for_identifier :serial_number, 'xyxyxyxy'
|
627
|
+
#
|
628
|
+
# # => the Integer id, or nil if no such serial number
|
629
|
+
#
|
630
|
+
# Raises a JSS::Ambiguous error if NON_UNIQUE_NAMES is set and
|
631
|
+
# a :name isn't unique
|
632
|
+
#
|
633
|
+
# @param key [Symbol] they key in which to look for the identifier. Must be
|
634
|
+
# a valid lookup key for this subclass.
|
635
|
+
#
|
636
|
+
# @param identfier [String,Integer] An identifier for an object, a value for
|
637
|
+
# one of the available lookup_keys
|
638
|
+
#
|
639
|
+
# @param refresh [Boolean] Should the cached summary data be re-read from
|
640
|
+
# the server first?
|
641
|
+
#
|
642
|
+
# @param api[JSS::APIConnection] an API connection to use for the query.
|
643
|
+
# Defaults to the corrently active API. See {JSS::APIConnection}
|
644
|
+
#
|
645
|
+
# @return [Integer, nil] the id of the matching object, or nil if it
|
646
|
+
# doesn't exist
|
647
|
+
#
|
648
|
+
def self.id_for_identifier(key, ident, refresh = false, api: JSS.api)
|
649
|
+
# refresh if needed
|
650
|
+
all(refresh, api: api) if refresh
|
651
|
+
|
652
|
+
# get the real key if an alias was used
|
653
|
+
key = real_lookup_key key
|
654
|
+
|
655
|
+
return all_ids.include?(ident) ? ident : nil if key == :id
|
656
|
+
|
657
|
+
validate_unique_name(ident) if key == :name
|
658
|
+
|
659
|
+
# downcase for speed
|
660
|
+
ident.downcase! if ident.is_a? String
|
661
|
+
mapped_ids = map_all_ids_to key, api: api
|
662
|
+
mapped_ids.each { |_k, v| v.downcase! if v.is_a? String }
|
663
|
+
return nil unless mapped_ids.value? ident
|
664
|
+
|
665
|
+
mapped_ids.invert[ident]
|
279
666
|
end
|
280
667
|
|
281
|
-
# Return
|
282
|
-
# with the given
|
668
|
+
# Return true or false if an object of this subclass
|
669
|
+
# with the given Identifier exists on the server
|
283
670
|
#
|
284
671
|
# @param identfier [String,Integer] An identifier for an object, a value for
|
285
672
|
# one of the available lookup_keys
|
@@ -289,16 +676,10 @@ module JSS
|
|
289
676
|
# @param api[JSS::APIConnection] an API connection to use for the query.
|
290
677
|
# Defaults to the corrently active API. See {JSS::APIConnection}
|
291
678
|
#
|
292
|
-
# @return [
|
679
|
+
# @return [Boolean] does an object with the given identifier exist?
|
293
680
|
#
|
294
|
-
def self.
|
295
|
-
|
296
|
-
all_lookup_keys.keys.each do |key|
|
297
|
-
next if key == :id
|
298
|
-
id = map_all_ids_to(key, api: api).invert[identifier]
|
299
|
-
return id if id
|
300
|
-
end # do key
|
301
|
-
nil
|
681
|
+
def self.exist?(identifier, refresh = false, api: JSS.api)
|
682
|
+
!valid_id(identifier, refresh, api: api).nil?
|
302
683
|
end
|
303
684
|
|
304
685
|
# Convert an Array of Hashes of API object data to a
|
@@ -383,83 +764,90 @@ module JSS
|
|
383
764
|
end
|
384
765
|
end
|
385
766
|
|
386
|
-
#
|
767
|
+
# Retrieve an object from the API and return an instance of this APIObject
|
768
|
+
# subclass.
|
387
769
|
#
|
388
|
-
# @
|
389
|
-
#
|
770
|
+
# @example
|
771
|
+
# # computer where 'xyxyxyxy' is in any of the lookup key fields
|
772
|
+
# JSS::Computer.fetch 'xyxyxyxy'
|
390
773
|
#
|
391
|
-
|
392
|
-
|
393
|
-
DEFAULT_LOOKUP_KEYS.keys + self::OTHER_LOOKUP_KEYS.keys
|
394
|
-
end
|
395
|
-
|
396
|
-
# @return [Hash] the available lookup keys mapped to the appropriate
|
397
|
-
# resource key for building a REST url to retrieve an object.
|
774
|
+
# # computer where 'xyxyxyxy' is the serial number
|
775
|
+
# JSS::Computer.fetch serial_number: 'xyxyxyxy'
|
398
776
|
#
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
hash
|
403
|
-
end
|
404
|
-
|
405
|
-
# the available list methods for an APIObject sublcass
|
777
|
+
# Fetching is faster when specifying a lookup key, and that key has a
|
778
|
+
# fetch_rsrc_key defined in its OTHER_LOOKUP_KEYS constant, as in the second
|
779
|
+
# example above.
|
406
780
|
#
|
407
|
-
#
|
408
|
-
#
|
781
|
+
# When no lookup key is given, as in the first example above, or when that
|
782
|
+
# key doesn't have a defined fetch_rsrc_key, ruby-jss uses the currently cached
|
783
|
+
# list resource data to find the id matching the value given, and that id
|
784
|
+
# is used to fetch the object. (see 'List Resources and Lookup Keys' in the
|
785
|
+
# APIObject comments/docs above)
|
409
786
|
#
|
410
|
-
|
411
|
-
#
|
412
|
-
#
|
413
|
-
#
|
414
|
-
# @return [Hash] See DEFAULT_LOOKUP_KEYS constant
|
787
|
+
# Since that cached list data may be out of date, you can provide the param
|
788
|
+
# `refrsh: true`, to reload the list from the server. This will cause the
|
789
|
+
# fetch to be slower still, so use with caution.
|
415
790
|
#
|
416
|
-
|
417
|
-
return DEFAULT_LOOKUP_KEYS.merge(self::OTHER_LOOKUP_KEYS) if defined? self::OTHER_LOOKUP_KEYS
|
418
|
-
DEFAULT_LOOKUP_KEYS
|
419
|
-
end
|
420
|
-
|
421
|
-
# @return [Hash] the available lookup keys mapped to the appropriate
|
422
|
-
# list class method (e.g. id: :all_ids )
|
791
|
+
# For creating new objects in the JSS, use {APIObject.make}
|
423
792
|
#
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
hash
|
428
|
-
end
|
429
|
-
|
430
|
-
# Retrieve an object from the API.
|
793
|
+
# @param searchterm[String, Integer] An single value to
|
794
|
+
# search for in all the lookup keys for this clsss. This is slower
|
795
|
+
# than specifying a lookup key
|
431
796
|
#
|
432
|
-
#
|
433
|
-
#
|
434
|
-
#
|
797
|
+
# @param args[Hash] the remaining options for fetching an object.
|
798
|
+
# If no searchterm is provided, one of the args must be a valid
|
799
|
+
# lookup key and value to find in that key, e.g. `serial_number: '1234567'`
|
435
800
|
#
|
436
|
-
#
|
801
|
+
# @option args api[JSS::APIConnection] an API connection to use for the query.
|
802
|
+
# Defaults to the corrently active API. See {JSS::APIConnection}
|
437
803
|
#
|
438
|
-
# @
|
439
|
-
#
|
804
|
+
# @option args refresh[Boolean] should the summary list of all objects be
|
805
|
+
# reloaded from the API before being used to look for this object.
|
440
806
|
#
|
441
807
|
# @return [APIObject] The ruby-instance of a JSS object
|
442
808
|
#
|
443
|
-
def self.fetch(
|
444
|
-
|
809
|
+
def self.fetch(searchterm = nil, **args)
|
810
|
+
validate_not_metaclass(self)
|
811
|
+
|
812
|
+
# which connection?
|
813
|
+
api = args.delete :api
|
814
|
+
api ||= JSS.api
|
815
|
+
|
816
|
+
# refresh the .all list if needed
|
817
|
+
all(:refresh, api: api) if args.delete :refresh
|
818
|
+
|
819
|
+
# a random object?
|
820
|
+
if searchterm == :random
|
821
|
+
return new id: all_ids.sample, api: api
|
822
|
+
end
|
823
|
+
|
824
|
+
# get the lookup key and value, if given
|
825
|
+
fetch_key, fetch_val = args.to_a.first
|
445
826
|
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
return new
|
827
|
+
if fetch_key
|
828
|
+
validate_unique_name(fetch_val) if fetch_key == :name
|
829
|
+
|
830
|
+
# does this lookup key have a fetch_rsrc_key?
|
831
|
+
fetch_rsrc_key = fetch_rsrc_key(fetch_key)
|
832
|
+
return new fetch_rsrc: "#{self::RSRC_BASE}/#{fetch_rsrc_key}/#{fetch_val}", api: api if fetch_rsrc_key
|
452
833
|
end
|
453
834
|
|
454
|
-
#
|
455
|
-
#
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
835
|
+
# if we'ere here, we need to get the id from either the lookup key/val or
|
836
|
+
# the searchterm
|
837
|
+
if fetch_key
|
838
|
+
# it has an OTHER_LOOKUP_KEY but that key doesn't have a fetch_rsrc
|
839
|
+
# so we look in the .map_all_ids_to_* hash for it.
|
840
|
+
id = id_for_identifier fetch_key, fetch_val, api: api
|
841
|
+
err_detail = "where #{fetch_key} = #{fetch_val}"
|
842
|
+
elsif searchterm
|
843
|
+
id = valid_id searchterm, api: api
|
844
|
+
err_detail = "matching #{searchterm}"
|
845
|
+
else
|
846
|
+
raise ArgumentError, 'Missing searchterm or fetch key'
|
847
|
+
end
|
848
|
+
raise JSS::NoSuchItemError, "No #{self::RSRC_OBJECT_KEY} found #{err_detail}" unless id
|
460
849
|
|
461
|
-
|
462
|
-
raise NoSuchItemError, "No matching #{self::RSRC_OBJECT_KEY} found"
|
850
|
+
new id: id, api: api
|
463
851
|
end # fetch
|
464
852
|
|
465
853
|
# Make a ruby instance of a not-yet-existing APIObject.
|
@@ -479,9 +867,10 @@ module JSS
|
|
479
867
|
# @return [APIObject] The un-created ruby-instance of a JSS object
|
480
868
|
#
|
481
869
|
def self.make(**args)
|
482
|
-
|
483
|
-
raise JSS::UnsupportedError, 'JSS::APIObject cannot be instantiated' if self.class == JSS::APIObject
|
870
|
+
validate_not_metaclass(self)
|
484
871
|
raise ArgumentError, "Use '#{self.class}.fetch id: xx' to retrieve existing JSS objects" if args[:id]
|
872
|
+
|
873
|
+
args[:api] ||= JSS.api
|
485
874
|
args[:id] = :new
|
486
875
|
new args
|
487
876
|
end
|
@@ -489,9 +878,13 @@ module JSS
|
|
489
878
|
# Disallow direct use of ruby's .new class method for creating instances.
|
490
879
|
# Require use of .fetch or .make
|
491
880
|
def self.new(**args)
|
881
|
+
validate_not_metaclass(self)
|
882
|
+
|
492
883
|
calling_method = caller_locations(1..1).first.label
|
493
|
-
|
494
|
-
|
884
|
+
unless OK_INSTANTIATORS.include? calling_method
|
885
|
+
raise JSS::UnsupportedError, 'Use .fetch or .make to instantiate APIObject classes'
|
886
|
+
end
|
887
|
+
|
495
888
|
super
|
496
889
|
end
|
497
890
|
|
@@ -509,13 +902,12 @@ module JSS
|
|
509
902
|
# @return [Array<Integer>] The id's that didn't exist when we tried to
|
510
903
|
# delete them.
|
511
904
|
#
|
512
|
-
def self.delete(victims, api: JSS.api)
|
513
|
-
|
905
|
+
def self.delete(victims, refresh = true, api: JSS.api)
|
906
|
+
validate_not_metaclass(self)
|
907
|
+
|
514
908
|
raise JSS::InvalidDataError, 'Parameter must be an Integer ID or an Array of them' unless victims.is_a?(Integer) || victims.is_a?(Array)
|
515
909
|
|
516
910
|
case victims
|
517
|
-
when Integer
|
518
|
-
victims = [victims]
|
519
911
|
when Integer
|
520
912
|
victims = [victims]
|
521
913
|
when Array
|
@@ -523,7 +915,7 @@ module JSS
|
|
523
915
|
end
|
524
916
|
|
525
917
|
skipped = []
|
526
|
-
current_ids = all_ids
|
918
|
+
current_ids = all_ids refresh, api: api
|
527
919
|
victims.each do |vid|
|
528
920
|
if current_ids.include? vid
|
529
921
|
api.delete_rsrc "#{self::RSRC_BASE}/id/#{vid}"
|
@@ -535,51 +927,21 @@ module JSS
|
|
535
927
|
skipped
|
536
928
|
end # self.delete
|
537
929
|
|
538
|
-
|
539
|
-
|
930
|
+
# Can't use APIObject directly.
|
931
|
+
def self.validate_not_metaclass(klass)
|
932
|
+
raise JSS::UnsupportedError, 'JSS::APIObject is a metaclass. Do not use it directly' if klass == JSS::APIObject
|
933
|
+
end
|
540
934
|
|
541
|
-
#
|
542
|
-
#
|
543
|
-
|
544
|
-
|
935
|
+
# Raise an exception if a name is being used for fetching and it isn't
|
936
|
+
# unique. Case Insensitive
|
937
|
+
def self.validate_unique_name(name, refresh = false)
|
938
|
+
return unless defined? self::NON_UNIQUE_NAMES
|
545
939
|
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
# which has the same format, described here:
|
551
|
-
#
|
552
|
-
# The merged Hashes DEFAULT_LOOKUP_KEYS and OTHER_LOOKUP_KEYS
|
553
|
-
# (as provided by the .all_lookup_keys Class method)
|
554
|
-
# define what unique identifiers can be passed as parameters to the
|
555
|
-
# fetch method for retrieving an object from the API.
|
556
|
-
# They also define the class methods that return a list (Array) of all such
|
557
|
-
# identifiers for the class (e.g. the :all_ids class method returns an array
|
558
|
-
# of all id's for an APIObject subclass)
|
559
|
-
#
|
560
|
-
# Since there's often a discrepency between the name of the identifier as
|
561
|
-
# an attribute (e.g. serial_number) and the REST resource key for
|
562
|
-
# retrieving that object (e.g. ../computers/serialnumber/xxxxx) this hash
|
563
|
-
# also explicitly provides the REST resource key for a given lookup key, so
|
564
|
-
# e.g. both serialnumber and serial_number can be used, and both will have
|
565
|
-
# the resource key 'serialnumber' and the list method ':all_serial_numbers'
|
566
|
-
#
|
567
|
-
# Here's how the Hash is structured, using serialnumber as an example:
|
568
|
-
#
|
569
|
-
# LOOKUP_KEYS = {
|
570
|
-
# serialnumber: {rsrc_key: :serialnumber, list: :all_serial_numbers},
|
571
|
-
# serial_number: {rsrc_key: :serialnumber, list: :all_serial_numbers}
|
572
|
-
# }
|
573
|
-
#
|
574
|
-
DEFAULT_LOOKUP_KEYS = {
|
575
|
-
id: { rsrc_key: :id, list: :all_ids },
|
576
|
-
name: { rsrc_key: :name, list: :all_names }
|
577
|
-
}.freeze
|
940
|
+
name.downcase!
|
941
|
+
matches = all_names(refresh).map(&:downcase).select { |n| n == name }
|
942
|
+
raise JSS::AmbiguousError, "Name '#{name}' is not unique for #{self}" if matches.size > 1
|
943
|
+
end
|
578
944
|
|
579
|
-
# This table holds the object history for JSS objects.
|
580
|
-
# Object history is not available via the API,
|
581
|
-
# only MySQL.
|
582
|
-
OBJECT_HISTORY_TABLE = 'object_history'.freeze
|
583
945
|
|
584
946
|
# Attributes
|
585
947
|
#####################################
|
@@ -604,6 +966,11 @@ module JSS
|
|
604
966
|
# @return [String] the Rest resource for API access (the part after "JSSResource/" )
|
605
967
|
attr_reader :rest_rsrc
|
606
968
|
|
969
|
+
# Attibute Aliases
|
970
|
+
#####################################
|
971
|
+
|
972
|
+
alias in_jss? in_jss
|
973
|
+
|
607
974
|
# Constructor
|
608
975
|
#####################################
|
609
976
|
|
@@ -625,10 +992,9 @@ module JSS
|
|
625
992
|
# API data e.g. to limit the data returned
|
626
993
|
#
|
627
994
|
#
|
628
|
-
def initialize(args
|
629
|
-
args[:api] ||= JSS.api
|
995
|
+
def initialize(**args)
|
630
996
|
@api = args[:api]
|
631
|
-
|
997
|
+
@api ||= JSS.api
|
632
998
|
|
633
999
|
# we're making a new one in the JSS
|
634
1000
|
if args[:id] == :new
|
@@ -892,32 +1258,6 @@ module JSS
|
|
892
1258
|
raise JSS::UnsupportedError, "Object History access is not supported for #{self.class} objects at this time" unless defined? self.class::OBJECT_HISTORY_OBJECT_TYPE
|
893
1259
|
end
|
894
1260
|
|
895
|
-
# If we were passed pre-lookedup API data, validate it,
|
896
|
-
# raising exceptions if not valid.
|
897
|
-
#
|
898
|
-
# DEPRECATED: pre-lookedup data is never used
|
899
|
-
# and support for it will be going away.
|
900
|
-
#
|
901
|
-
# TODO: delete this and all defined VALID_DATA_KEYS
|
902
|
-
#
|
903
|
-
# @return [void]
|
904
|
-
#
|
905
|
-
def validate_external_init_data
|
906
|
-
# data must include all they keys in REQUIRED_DATA_KEYS + VALID_DATA_KEYS
|
907
|
-
# in either the main hash keys or the :general sub-hash, if it exists
|
908
|
-
hash_to_check = @init_data[:general] ? @init_data[:general] : @init_data
|
909
|
-
combined_valid_keys = self.class::REQUIRED_DATA_KEYS + self.class::VALID_DATA_KEYS
|
910
|
-
keys_ok = (hash_to_check.keys & combined_valid_keys).count == combined_valid_keys.count
|
911
|
-
unless keys_ok
|
912
|
-
raise(
|
913
|
-
JSS::InvalidDataError,
|
914
|
-
":data is not valid JSON for a #{self.class::RSRC_OBJECT_KEY} from the API. It needs at least the keys :#{combined_valid_keys.join ', :'}"
|
915
|
-
)
|
916
|
-
end
|
917
|
-
# and the id must be in the jss
|
918
|
-
raise NoSuchItemError, "No #{self.class::RSRC_OBJECT_KEY} with JSS id: #{@init_data[:id]}" unless \
|
919
|
-
self.class.all_ids(api: @api).include? hash_to_check[:id]
|
920
|
-
end # validate_init_data
|
921
1261
|
|
922
1262
|
# If we're making a new object in the JSS, make sure we were given
|
923
1263
|
# valid data to do so, raise exceptions otherwise.
|
@@ -940,20 +1280,14 @@ module JSS
|
|
940
1280
|
|
941
1281
|
# Given initialization args, perform an API lookup for an object.
|
942
1282
|
#
|
943
|
-
# @param args[Hash] The args passed to #initialize
|
1283
|
+
# @param args[Hash] The args passed to #initialize, which must have either
|
1284
|
+
# key :id or key :fetch_rsrc
|
944
1285
|
#
|
945
1286
|
# @return [Hash] The parsed JSON data for the object from the API
|
946
1287
|
#
|
947
1288
|
def look_up_object_data(args)
|
948
|
-
rsrc =
|
949
|
-
|
950
|
-
args[:fetch_rsrc]
|
951
|
-
else
|
952
|
-
# what lookup key are we using?
|
953
|
-
# TODO: simplify this, see the notes at #find_rsrc_keys
|
954
|
-
rsrc_key, lookup_value = find_rsrc_keys(args)
|
955
|
-
"#{self.class::RSRC_BASE}/#{rsrc_key}/#{lookup_value}"
|
956
|
-
end
|
1289
|
+
rsrc = args[:fetch_rsrc]
|
1290
|
+
rsrc ||= "#{self.class::RSRC_BASE}/id/#{args[:id]}"
|
957
1291
|
|
958
1292
|
# if needed, a non-standard object key can be passed by a subclass.
|
959
1293
|
# e.g. User when loookup is by email.
|
@@ -967,44 +1301,12 @@ module JSS
|
|
967
1301
|
# otherwise
|
968
1302
|
@api.get_rsrc(rsrc)
|
969
1303
|
end
|
1304
|
+
|
970
1305
|
raw_json[args[:rsrc_object_key]]
|
971
1306
|
rescue RestClient::ResourceNotFound
|
972
1307
|
raise NoSuchItemError, "No #{self.class::RSRC_OBJECT_KEY} found matching resource #{rsrc}"
|
973
1308
|
end
|
974
1309
|
|
975
|
-
# Given initialization args, determine the rsrc key and
|
976
|
-
# lookup value to be used in building the GET resource.
|
977
|
-
# E.g. for looking up something with id 345,
|
978
|
-
# return the rsrc_key :id, and the value 345, which
|
979
|
-
# can be used to create the resrouce
|
980
|
-
# '/things/id/345'
|
981
|
-
#
|
982
|
-
# CHANGE: some the new patch-related objects don't have
|
983
|
-
# GET resources by name, only id. So this method now always
|
984
|
-
# returns the id-based resource.
|
985
|
-
#
|
986
|
-
# TODO: clean up this and the above methods, since the
|
987
|
-
# id-only get rsrcs actually should simplify the code.
|
988
|
-
#
|
989
|
-
# @param args[Hash] The args passed to #initialize
|
990
|
-
#
|
991
|
-
# @return [Array] Two item array: [ rsrc_key, lookup_value]
|
992
|
-
#
|
993
|
-
def find_rsrc_keys(args)
|
994
|
-
lookup_keys = self.class.lookup_keys
|
995
|
-
lookup_key = (self.class.lookup_keys & args.keys)[0]
|
996
|
-
|
997
|
-
raise JSS::MissingDataError, "Args must include a lookup key, one of: :#{lookup_keys.join(', :')}" unless lookup_key
|
998
|
-
|
999
|
-
vid = self.class.valid_id args[lookup_key], :refresh, api: args[:api]
|
1000
|
-
|
1001
|
-
raise NoSuchItemError, "No #{self.class::RSRC_OBJECT_KEY} found with #{lookup_key} '#{args[lookup_key]}'" unless vid
|
1002
|
-
|
1003
|
-
[:id, vid]
|
1004
|
-
# rsrc_key = self.class.rsrc_keys[lookup_key]
|
1005
|
-
# [rsrc_key, args[lookup_key]]
|
1006
|
-
end
|
1007
|
-
|
1008
1310
|
# Start examining the @init_data recieved from the API
|
1009
1311
|
#
|
1010
1312
|
# @return [void]
|
@@ -1028,11 +1330,6 @@ module JSS
|
|
1028
1330
|
|
1029
1331
|
@rest_rsrc = "#{self.class::RSRC_BASE}/id/#{@id}"
|
1030
1332
|
|
1031
|
-
# many things have a :site
|
1032
|
-
# TODO: Implement a Sitable mixin module
|
1033
|
-
#
|
1034
|
-
# @site = JSS::APIObject.get_name(@main_subset[:site]) if @main_subset[:site]
|
1035
|
-
|
1036
1333
|
##### Handle Mix-ins
|
1037
1334
|
initialize_category
|
1038
1335
|
initialize_site
|
@@ -1164,9 +1461,42 @@ module JSS
|
|
1164
1461
|
doc.to_s
|
1165
1462
|
end
|
1166
1463
|
|
1167
|
-
#
|
1464
|
+
# Meta Programming
|
1168
1465
|
|
1169
|
-
|
1466
|
+
# Loop through the defined lookup keys and make
|
1467
|
+
# .all_<key>s methods for each one, with
|
1468
|
+
# alises as needed.
|
1469
|
+
#
|
1470
|
+
# This is called automatically in api_object.rb
|
1471
|
+
# after all subclasses are loaded.
|
1472
|
+
#
|
1473
|
+
def self.define_identifier_list_methods
|
1474
|
+
return unless @subclasses
|
1475
|
+
|
1476
|
+
@subclasses.each do |subclass|
|
1477
|
+
subclass.lookup_keys.each do |als, key|
|
1478
|
+
meth_name = "all_#{key}s"
|
1479
|
+
|
1480
|
+
if als == key
|
1481
|
+
# the all_ method - skip if defined in the class
|
1482
|
+
next if subclass.instance_methods.include? meth_name
|
1483
|
+
|
1484
|
+
subclass.define_singleton_method meth_name do |refresh = false, api: JSS.api|
|
1485
|
+
all(refresh, api: api).map { |i| i[key] }
|
1486
|
+
end
|
1487
|
+
|
1488
|
+
else
|
1489
|
+
# an alias - skip if defined in the class
|
1490
|
+
als_name = "all_#{als}s"
|
1491
|
+
next if subclass.instance_methods.include? als_name
|
1492
|
+
|
1493
|
+
subclass.define_singleton_method als_name do |refresh = false, api: JSS.api|
|
1494
|
+
send meth_name, refresh, api: api
|
1495
|
+
end
|
1496
|
+
end # if
|
1497
|
+
end # lookup_keys.each
|
1498
|
+
end # @subclasses.each
|
1499
|
+
end # self.define_identifier_list_methods
|
1170
1500
|
|
1171
1501
|
end # class APIObject
|
1172
1502
|
|
@@ -1226,3 +1556,5 @@ require 'jss/api_object/site'
|
|
1226
1556
|
require 'jss/api_object/software_update_server'
|
1227
1557
|
require 'jss/api_object/user'
|
1228
1558
|
require 'jss/api_object/webhook'
|
1559
|
+
|
1560
|
+
JSS::APIObject.define_identifier_list_methods
|