ruby-jss 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ruby-jss might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.yardopts +7 -0
- data/CHANGES.md +112 -0
- data/LICENSE.txt +174 -0
- data/README.md +426 -0
- data/THANKS.md +6 -0
- data/bin/cgrouper +485 -0
- data/bin/subnet-update +400 -0
- data/lib/jss-api.rb +2 -0
- data/lib/jss.rb +190 -0
- data/lib/jss/api_connection.rb +410 -0
- data/lib/jss/api_object.rb +616 -0
- data/lib/jss/api_object/advanced_search.rb +389 -0
- data/lib/jss/api_object/advanced_search/advanced_computer_search.rb +95 -0
- data/lib/jss/api_object/advanced_search/advanced_mobile_device_search.rb +96 -0
- data/lib/jss/api_object/advanced_search/advanced_user_search.rb +95 -0
- data/lib/jss/api_object/building.rb +92 -0
- data/lib/jss/api_object/category.rb +147 -0
- data/lib/jss/api_object/computer.rb +852 -0
- data/lib/jss/api_object/creatable.rb +98 -0
- data/lib/jss/api_object/criteriable.rb +189 -0
- data/lib/jss/api_object/criteriable/criteria.rb +231 -0
- data/lib/jss/api_object/criteriable/criterion.rb +228 -0
- data/lib/jss/api_object/department.rb +93 -0
- data/lib/jss/api_object/distribution_point.rb +560 -0
- data/lib/jss/api_object/extendable.rb +221 -0
- data/lib/jss/api_object/extension_attribute.rb +466 -0
- data/lib/jss/api_object/extension_attribute/computer_extension_attribute.rb +362 -0
- data/lib/jss/api_object/extension_attribute/mobile_device_extension_attribute.rb +189 -0
- data/lib/jss/api_object/extension_attribute/user_extension_attribute.rb +117 -0
- data/lib/jss/api_object/group.rb +380 -0
- data/lib/jss/api_object/group/computer_group.rb +124 -0
- data/lib/jss/api_object/group/mobile_device_group.rb +139 -0
- data/lib/jss/api_object/group/user_group.rb +139 -0
- data/lib/jss/api_object/ldap_server.rb +535 -0
- data/lib/jss/api_object/locatable.rb +286 -0
- data/lib/jss/api_object/matchable.rb +97 -0
- data/lib/jss/api_object/mobile_device.rb +556 -0
- data/lib/jss/api_object/netboot_server.rb +148 -0
- data/lib/jss/api_object/network_segment.rb +414 -0
- data/lib/jss/api_object/osx_configuration_profile.rb +262 -0
- data/lib/jss/api_object/package.rb +839 -0
- data/lib/jss/api_object/peripheral.rb +335 -0
- data/lib/jss/api_object/peripheral_type.rb +295 -0
- data/lib/jss/api_object/policy.rb +898 -0
- data/lib/jss/api_object/purchasable.rb +316 -0
- data/lib/jss/api_object/removable_macaddr.rb +98 -0
- data/lib/jss/api_object/scopable.rb +136 -0
- data/lib/jss/api_object/scopable/scope.rb +621 -0
- data/lib/jss/api_object/script.rb +631 -0
- data/lib/jss/api_object/self_servable.rb +356 -0
- data/lib/jss/api_object/site.rb +93 -0
- data/lib/jss/api_object/software_update_server.rb +109 -0
- data/lib/jss/api_object/updatable.rb +117 -0
- data/lib/jss/api_object/uploadable.rb +138 -0
- data/lib/jss/api_object/user.rb +272 -0
- data/lib/jss/client.rb +504 -0
- data/lib/jss/compatibility.rb +66 -0
- data/lib/jss/composer.rb +185 -0
- data/lib/jss/configuration.rb +306 -0
- data/lib/jss/db_connection.rb +298 -0
- data/lib/jss/exceptions.rb +95 -0
- data/lib/jss/ruby_extensions.rb +35 -0
- data/lib/jss/ruby_extensions/filetest.rb +43 -0
- data/lib/jss/ruby_extensions/hash.rb +79 -0
- data/lib/jss/ruby_extensions/ipaddr.rb +91 -0
- data/lib/jss/ruby_extensions/pathname.rb +77 -0
- data/lib/jss/ruby_extensions/string.rb +59 -0
- data/lib/jss/ruby_extensions/time.rb +63 -0
- data/lib/jss/server.rb +108 -0
- data/lib/jss/utility.rb +478 -0
- data/lib/jss/version.rb +31 -0
- metadata +187 -0
@@ -0,0 +1,410 @@
|
|
1
|
+
### Copyright 2016 Pixar
|
2
|
+
###
|
3
|
+
### Licensed under the Apache License, Version 2.0 (the "Apache License")
|
4
|
+
### with the following modification; you may not use this file except in
|
5
|
+
### compliance with the Apache License and the following modification to it:
|
6
|
+
### Section 6. Trademarks. is deleted and replaced with:
|
7
|
+
###
|
8
|
+
### 6. Trademarks. This License does not grant permission to use the trade
|
9
|
+
### names, trademarks, service marks, or product names of the Licensor
|
10
|
+
### and its affiliates, except as required to comply with Section 4(c) of
|
11
|
+
### the License and to reproduce the content of the NOTICE file.
|
12
|
+
###
|
13
|
+
### You may obtain a copy of the Apache License at
|
14
|
+
###
|
15
|
+
### http://www.apache.org/licenses/LICENSE-2.0
|
16
|
+
###
|
17
|
+
### Unless required by applicable law or agreed to in writing, software
|
18
|
+
### distributed under the Apache License with the above modification is
|
19
|
+
### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
20
|
+
### KIND, either express or implied. See the Apache License for the specific
|
21
|
+
### language governing permissions and limitations under the Apache License.
|
22
|
+
###
|
23
|
+
###
|
24
|
+
|
25
|
+
###
|
26
|
+
module JSS
|
27
|
+
|
28
|
+
#####################################
|
29
|
+
### Constants
|
30
|
+
#####################################
|
31
|
+
|
32
|
+
#####################################
|
33
|
+
### Module Variables
|
34
|
+
#####################################
|
35
|
+
|
36
|
+
#####################################
|
37
|
+
### Module Methods
|
38
|
+
#####################################
|
39
|
+
|
40
|
+
#####################################
|
41
|
+
### Module Classes
|
42
|
+
#####################################
|
43
|
+
|
44
|
+
###
|
45
|
+
### An API connection to the JSS.
|
46
|
+
###
|
47
|
+
### This is a singleton class, only one can exist at a time.
|
48
|
+
### Its one instance is created automatically when the module loads, but it
|
49
|
+
### isn't connected to anything at that time.
|
50
|
+
###
|
51
|
+
### Use it via the {JSS::API} constant to call the #connect
|
52
|
+
### method, and the {#get_rsrc}, {#put_rsrc}, {#post_rsrc}, & {#delete_rsrc}
|
53
|
+
### methods, q.v. below.
|
54
|
+
###
|
55
|
+
### To access the underlying RestClient::Resource instance,
|
56
|
+
### use JSS::API.cnx
|
57
|
+
###
|
58
|
+
class APIConnection
|
59
|
+
include Singleton
|
60
|
+
|
61
|
+
#####################################
|
62
|
+
### Class Constants
|
63
|
+
#####################################
|
64
|
+
|
65
|
+
### The base API path in the jss URL
|
66
|
+
RSRC_BASE = "JSSResource"
|
67
|
+
|
68
|
+
### A url path to load to see if there's an API available at a host.
|
69
|
+
### This just loads the API resource docs page
|
70
|
+
TEST_PATH = "#{RSRC_BASE}/accounts"
|
71
|
+
|
72
|
+
### If the test path loads correctly from a casper server, it'll contain
|
73
|
+
### this text (this is what we get when we make an unauthenticated
|
74
|
+
### API call.)
|
75
|
+
TEST_CONTENT = "<p>The request requires user authentication</p>"
|
76
|
+
|
77
|
+
### The Default port
|
78
|
+
HTTP_PORT = 9006
|
79
|
+
|
80
|
+
### The SSL port
|
81
|
+
SSL_PORT = 8443
|
82
|
+
|
83
|
+
### The top line of an XML doc for submitting data via API
|
84
|
+
XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'
|
85
|
+
|
86
|
+
### Default timeouts in seconds
|
87
|
+
DFT_OPEN_TIMEOUT = 60
|
88
|
+
DFT_TIMEOUT = 60
|
89
|
+
|
90
|
+
### The Default SSL Version
|
91
|
+
DFT_SSL_VERSION = 'TLSv1'
|
92
|
+
|
93
|
+
#####################################
|
94
|
+
### Attributes
|
95
|
+
#####################################
|
96
|
+
|
97
|
+
### @return [String] the username who's connected to the JSS API
|
98
|
+
attr_reader :jss_user
|
99
|
+
|
100
|
+
### @return [RestClient::Resource] the underlying connection resource
|
101
|
+
attr_reader :cnx
|
102
|
+
|
103
|
+
### @return [Boolean] are we connected right now?
|
104
|
+
attr_reader :connected
|
105
|
+
|
106
|
+
### @return [JSS::Server] the details of the JSS to which we're connected.
|
107
|
+
attr_reader :server
|
108
|
+
|
109
|
+
### @return [String] the hostname of the JSS to which we're connected.
|
110
|
+
attr_reader :server_host
|
111
|
+
|
112
|
+
#####################################
|
113
|
+
### Constructor
|
114
|
+
#####################################
|
115
|
+
|
116
|
+
###
|
117
|
+
### To connect, use JSS::APIConnection.instance.connect
|
118
|
+
### or a shortcut, JSS::API.connect
|
119
|
+
###
|
120
|
+
def initialize ()
|
121
|
+
@connected = false
|
122
|
+
end # init
|
123
|
+
|
124
|
+
#####################################
|
125
|
+
### Class Methods
|
126
|
+
#####################################
|
127
|
+
|
128
|
+
###
|
129
|
+
### Connect to the JSS API.
|
130
|
+
###
|
131
|
+
### @param args[Hash] the keyed arguments for connection.
|
132
|
+
###
|
133
|
+
### @option args :server[String] the hostname of the JSS API server, required if not defined in JSS::CONFIG
|
134
|
+
###
|
135
|
+
### @option args :port[Integer] the port number to connect with, defaults to 8443
|
136
|
+
###
|
137
|
+
### @option args :use_ssl[Boolean] should the connection be made over SSL? Defaults to true.
|
138
|
+
###
|
139
|
+
### @option args :verify_cert[Boolean] should HTTPS SSL certificates be verified. Defaults to true.
|
140
|
+
### If your connection raises RestClient::SSLCertificateNotVerified, and you don't care about the
|
141
|
+
### validity of the SSL cert. just set this explicitly to false.
|
142
|
+
###
|
143
|
+
### @option args :user[String] a JSS user who has API privs, required if not defined in JSS::CONFIG
|
144
|
+
###
|
145
|
+
### @option args :pw[String,Symbol] Required, the password for that user, or :prompt, or :stdin
|
146
|
+
### If :prompt, the user is promted on the commandline to enter the password for the :user.
|
147
|
+
### If :stdin#, the password is read from a line of std in represented by the digit at #,
|
148
|
+
### so :stdin3 reads the passwd from the third line of standard input. defaults to line 1,
|
149
|
+
### if no digit is supplied. see {JSS.stdin}
|
150
|
+
###
|
151
|
+
### @option args :open_timeout[Integer] the number of seconds to wait for an initial response, defaults to 60
|
152
|
+
###
|
153
|
+
### @option args :timeout[Integer] the number of seconds before an API call times out, defaults to 60
|
154
|
+
###
|
155
|
+
### @return [true]
|
156
|
+
###
|
157
|
+
def connect (args = {})
|
158
|
+
|
159
|
+
# the server, if not specified, might come from a couple places.
|
160
|
+
# see #hostname
|
161
|
+
args[:server] ||= hostname
|
162
|
+
|
163
|
+
# settings from config if they aren't in the args
|
164
|
+
args[:server] ||= JSS::CONFIG.api_server_name
|
165
|
+
args[:port] ||= JSS::CONFIG.api_server_port
|
166
|
+
args[:user] ||= JSS::CONFIG.api_username
|
167
|
+
args[:timeout] ||= JSS::CONFIG.api_timeout
|
168
|
+
args[:open_timeout] ||= JSS::CONFIG.api_timeout_open
|
169
|
+
args[:ssl_version] ||= JSS::CONFIG.api_ssl_version
|
170
|
+
|
171
|
+
# if verify cert given was NOT in the args....
|
172
|
+
if args[:verify_cert].nil?
|
173
|
+
# set it from the prefs
|
174
|
+
args[:verify_cert] = JSS::CONFIG.api_verify_cert
|
175
|
+
end
|
176
|
+
|
177
|
+
# settings from client jamf plist if needed
|
178
|
+
args[:port] ||= JSS::Client.jss_port
|
179
|
+
|
180
|
+
# default settings if needed
|
181
|
+
args[:port] ||= SSL_PORT
|
182
|
+
args[:timeout] ||= DFT_TIMEOUT
|
183
|
+
args[:open_timeout] ||= DFT_OPEN_TIMEOUT
|
184
|
+
|
185
|
+
# As of Casper 9.61 we can't use SSL, must use TLS, since SSLv3 was susceptible to poodles.
|
186
|
+
# NOTE - this requires rest-client v 1.7.0 or higher
|
187
|
+
# which requires mime-types 2.0 or higher, which requires ruby 1.9.2 or higher!
|
188
|
+
# That means that support for ruby 1.8.7 stops with Casper 9.6
|
189
|
+
args[:ssl_version] ||= DFT_SSL_VERSION
|
190
|
+
|
191
|
+
|
192
|
+
# must have server, user, and pw
|
193
|
+
raise JSS::MissingDataError, "No JSS :server specified, or in configuration." unless args[:server]
|
194
|
+
raise JSS::MissingDataError, "No JSS :user specified, or in configuration." unless args[:user]
|
195
|
+
raise JSS::MissingDataError, "Missing :pw for user '#{args[:user]}'" unless args[:pw]
|
196
|
+
|
197
|
+
# we're using ssl if 1) args[:use_ssl] is anything but false
|
198
|
+
# or 2) the port is the default casper ssl port.
|
199
|
+
args[:use_ssl] = (not args[:use_ssl] == false) or (args[:port] == SSL_PORT)
|
200
|
+
|
201
|
+
# and here's the URL
|
202
|
+
ssl = args[:use_ssl] ? "s" : ''
|
203
|
+
@rest_url = URI::encode "http#{ssl}://#{args[:server]}:#{args[:port]}/#{RSRC_BASE}"
|
204
|
+
|
205
|
+
|
206
|
+
# prep the args for passing to RestClient::Resource
|
207
|
+
# if verify_cert is anything but false, we will verify
|
208
|
+
args[:verify_ssl] = (args[:verify_cert] == false) ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
|
209
|
+
|
210
|
+
args[:password] = if args[:pw] == :prompt
|
211
|
+
JSS.prompt_for_password "Enter the password for JSS user #{args[:user]}@#{args[:server]}:"
|
212
|
+
elsif args[:pw].is_a?(Symbol) and args[:pw].to_s.start_with?('stdin')
|
213
|
+
args[:pw].to_s =~ /^stdin(\d+)$/
|
214
|
+
line = $1
|
215
|
+
line ||= 1
|
216
|
+
JSS.stdin line
|
217
|
+
else
|
218
|
+
args[:pw]
|
219
|
+
end
|
220
|
+
|
221
|
+
# heres our connection
|
222
|
+
@cnx = RestClient::Resource.new("#{@rest_url}", args)
|
223
|
+
|
224
|
+
@jss_user = args[:user]
|
225
|
+
@server_host = args[:server]
|
226
|
+
@connected = true
|
227
|
+
@server = JSS::Server.new
|
228
|
+
|
229
|
+
if @server.version < JSS.parse_jss_version(JSS::MINIMUM_SERVER_VERSION)[:version]
|
230
|
+
raise JSS::UnsupportedError, "Your JSS Server version, #{@server.raw_version}, is to low. Must be #{JSS::MINIMUM_SERVER_VERSION} or higher."
|
231
|
+
end
|
232
|
+
|
233
|
+
return @connected ? @server_host : nil
|
234
|
+
end # connect
|
235
|
+
|
236
|
+
###
|
237
|
+
### Reset the response timeout for the rest connection
|
238
|
+
###
|
239
|
+
### @param timeout[Integer] the new timeout in seconds
|
240
|
+
###
|
241
|
+
### @return [void]
|
242
|
+
###
|
243
|
+
def timeout= (timeout)
|
244
|
+
@cnx.options[:timeout] = timeout
|
245
|
+
end
|
246
|
+
|
247
|
+
###
|
248
|
+
### Reset the open-connection timeout for the rest connection
|
249
|
+
###
|
250
|
+
### @param timeout[Integer] the new timeout in seconds
|
251
|
+
###
|
252
|
+
### @return [void]
|
253
|
+
###
|
254
|
+
def open_timeout= (timeout)
|
255
|
+
@cnx.options[:open_timeout] = timeout
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
###
|
260
|
+
### With a REST connection, there isn't any real "connection" to disconnect from
|
261
|
+
### So to disconnect, we just unset all our credentials.
|
262
|
+
###
|
263
|
+
### @return [void]
|
264
|
+
###
|
265
|
+
def disconnect
|
266
|
+
@jss_user = nil
|
267
|
+
@rest_url = nil
|
268
|
+
@server_host = nil
|
269
|
+
@cnx = nil
|
270
|
+
@connected = false
|
271
|
+
end # disconnect
|
272
|
+
|
273
|
+
###
|
274
|
+
### Get an arbitrary JSS resource
|
275
|
+
###
|
276
|
+
### The first argument is the resource to get (the part of the API url
|
277
|
+
### after the 'JSSResource/' )
|
278
|
+
###
|
279
|
+
### By default we get the data in JSON, and parse it
|
280
|
+
### into a ruby data structure (arrays, hashes, strings, etc)
|
281
|
+
### with symbolized Hash keys.
|
282
|
+
###
|
283
|
+
### @param rsrc[String] the resource to get
|
284
|
+
### (the part of the API url after the 'JSSResource/' )
|
285
|
+
###
|
286
|
+
### @param format[Symbol] either ;json or :xml
|
287
|
+
### If the second argument is :xml, the XML data is returned as a String.
|
288
|
+
###
|
289
|
+
### @return [Hash,String] the result of the get
|
290
|
+
###
|
291
|
+
def get_rsrc (rsrc, format = :json)
|
292
|
+
raise JSS::InvalidConnectionError, "Not Connected. Use JSS::API.connect first." unless @connected
|
293
|
+
rsrc = URI::encode rsrc
|
294
|
+
data = @cnx[rsrc].get(:accept => format)
|
295
|
+
return JSON.parse(data, :symbolize_names => true) if format == :json
|
296
|
+
data
|
297
|
+
end
|
298
|
+
|
299
|
+
###
|
300
|
+
### Change an existing JSS resource
|
301
|
+
###
|
302
|
+
### @param rsrc[String] the API resource being changed, the URL part after 'JSSResource/'
|
303
|
+
###
|
304
|
+
### @param xml[String] the xml specifying the changes.
|
305
|
+
###
|
306
|
+
### @return [String] the xml response from the server.
|
307
|
+
###
|
308
|
+
def put_rsrc(rsrc,xml)
|
309
|
+
raise JSS::InvalidConnectionError, "Not Connected. Use JSS::API.connect first." unless @connected
|
310
|
+
|
311
|
+
### convert CRs & to
|
312
|
+
xml.gsub!(/\r/, ' ')
|
313
|
+
|
314
|
+
### send the data
|
315
|
+
@cnx[rsrc].put(xml, :content_type => 'text/xml')
|
316
|
+
end
|
317
|
+
|
318
|
+
###
|
319
|
+
### Create a new JSS resource
|
320
|
+
###
|
321
|
+
### @param rsrc[String] the API resource being created, the URL part after 'JSSResource/'
|
322
|
+
###
|
323
|
+
### @param xml[String] the xml specifying the new object.
|
324
|
+
###
|
325
|
+
### @return [String] the xml response from the server.
|
326
|
+
###
|
327
|
+
def post_rsrc(rsrc,xml)
|
328
|
+
raise JSS::InvalidConnectionError, "Not Connected. Use JSS::API.connect first." unless @connected
|
329
|
+
|
330
|
+
### convert CRs & to
|
331
|
+
xml.gsub!(/\r/, ' ')
|
332
|
+
|
333
|
+
### send the data
|
334
|
+
@cnx[rsrc].post xml, :content_type => 'text/xml', :accept => :json
|
335
|
+
end #post_rsrc
|
336
|
+
|
337
|
+
### Delete a resource from the JSS
|
338
|
+
###
|
339
|
+
### @param rsrc[String] the resource to create, the URL part after 'JSSResource/'
|
340
|
+
###
|
341
|
+
### @return [String] the xml response from the server.
|
342
|
+
###
|
343
|
+
def delete_rsrc(rsrc)
|
344
|
+
raise JSS::InvalidConnectionError, "Not Connected. Use JSS::API.connect first." unless @connected
|
345
|
+
raise MissingDataError, "Missing :rsrc" if rsrc.nil?
|
346
|
+
|
347
|
+
### delete the resource
|
348
|
+
@cnx[rsrc].delete
|
349
|
+
|
350
|
+
end #delete_rsrc
|
351
|
+
|
352
|
+
|
353
|
+
### Test that a given hostname & port is a JSS API server
|
354
|
+
###
|
355
|
+
### @param server[String] The hostname to test,
|
356
|
+
###
|
357
|
+
### @param port[Integer] The port to try connecting on
|
358
|
+
###
|
359
|
+
### @return [Boolean] does the server host a JSS API?
|
360
|
+
###
|
361
|
+
def valid_server? (server, port = SSL_PORT)
|
362
|
+
# cheating by shelling out to curl, because getting open-uri, or even net/http to use
|
363
|
+
# ssl_options like :OP_NO_SSLv2 and :OP_NO_SSLv3 will take time to figure out..
|
364
|
+
return true if `/usr/bin/curl -s 'https://#{server}:#{port}/#{TEST_PATH}'`.include? TEST_CONTENT
|
365
|
+
return true if `/usr/bin/curl -s 'http://#{server}:#{port}/#{TEST_PATH}'`.include? TEST_CONTENT
|
366
|
+
return false
|
367
|
+
|
368
|
+
# try ssl first
|
369
|
+
# NOTE: doesn't work if we can't disallow SSLv3 or force TLSv1
|
370
|
+
# See cheat above.
|
371
|
+
begin
|
372
|
+
return true if open("https://#{server}:#{port}/#{TEST_PATH}", ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE).read.include? TEST_CONTENT
|
373
|
+
|
374
|
+
rescue
|
375
|
+
# then regular http
|
376
|
+
begin
|
377
|
+
return true if open("http://#{server}:#{port}/#{TEST_PATH}").read.include? TEST_CONTENT
|
378
|
+
rescue
|
379
|
+
# any errors = no API
|
380
|
+
return false
|
381
|
+
end # begin
|
382
|
+
end #begin
|
383
|
+
# if we're here, no API
|
384
|
+
return false
|
385
|
+
end
|
386
|
+
|
387
|
+
### The server to which we are connected, or will
|
388
|
+
### try connecting to if none is specified with the
|
389
|
+
### call to #connect
|
390
|
+
###
|
391
|
+
### @return [String] the hostname of the server
|
392
|
+
###
|
393
|
+
def hostname
|
394
|
+
return @server_host if @server_host
|
395
|
+
srvr = JSS::CONFIG.api_server_name
|
396
|
+
srvr ||= JSS::Client.jss_server
|
397
|
+
return srvr
|
398
|
+
end
|
399
|
+
|
400
|
+
### aliases
|
401
|
+
alias connected? connected
|
402
|
+
|
403
|
+
|
404
|
+
end # class JSSAPIConnection
|
405
|
+
|
406
|
+
### The single instance of the APIConnection
|
407
|
+
API = APIConnection.instance
|
408
|
+
|
409
|
+
|
410
|
+
end # module
|
@@ -0,0 +1,616 @@
|
|
1
|
+
### Copyright 2016 Pixar
|
2
|
+
###
|
3
|
+
### Licensed under the Apache License, Version 2.0 (the "Apache License")
|
4
|
+
### with the following modification; you may not use this file except in
|
5
|
+
### compliance with the Apache License and the following modification to it:
|
6
|
+
### Section 6. Trademarks. is deleted and replaced with:
|
7
|
+
###
|
8
|
+
### 6. Trademarks. This License does not grant permission to use the trade
|
9
|
+
### names, trademarks, service marks, or product names of the Licensor
|
10
|
+
### and its affiliates, except as required to comply with Section 4(c) of
|
11
|
+
### the License and to reproduce the content of the NOTICE file.
|
12
|
+
###
|
13
|
+
### You may obtain a copy of the Apache License at
|
14
|
+
###
|
15
|
+
### http://www.apache.org/licenses/LICENSE-2.0
|
16
|
+
###
|
17
|
+
### Unless required by applicable law or agreed to in writing, software
|
18
|
+
### distributed under the Apache License with the above modification is
|
19
|
+
### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
20
|
+
### KIND, either express or implied. See the Apache License for the specific
|
21
|
+
### language governing permissions and limitations under the Apache License.
|
22
|
+
###
|
23
|
+
###
|
24
|
+
|
25
|
+
###
|
26
|
+
module JSS
|
27
|
+
|
28
|
+
#####################################
|
29
|
+
### Module Variables
|
30
|
+
#####################################
|
31
|
+
|
32
|
+
#####################################
|
33
|
+
### Module Methods
|
34
|
+
#####################################
|
35
|
+
|
36
|
+
#####################################
|
37
|
+
### Classes
|
38
|
+
#####################################
|
39
|
+
|
40
|
+
###
|
41
|
+
### This class is the parent to all JSS API objects. It provides standard methods and structures
|
42
|
+
### that apply to all API resouces.
|
43
|
+
###
|
44
|
+
### See the README.md file for general info about using subclasses of JSS::APIObject
|
45
|
+
###
|
46
|
+
### == Subclassing
|
47
|
+
###
|
48
|
+
### === Constructor
|
49
|
+
###
|
50
|
+
### In general, subclasses should do any class-specific argument checking before
|
51
|
+
### calling super, and then afterwards, use the contents of @init_data to populate
|
52
|
+
### any class-specific attributes. @id, @name, @rest_rsrc, and @in_jss are handled here.
|
53
|
+
###
|
54
|
+
### If a subclass can be looked up by some key other than :name or :id, the subclass must
|
55
|
+
### pass the keys as an Array in the second argument when calling super from #initialize.
|
56
|
+
### See {JSS::Computer#initialize} for an example of how to implement this feature.
|
57
|
+
###
|
58
|
+
### === Object Creation
|
59
|
+
###
|
60
|
+
### If a subclass should be able to be created in the JSS be sure to include {JSS::Creatable}
|
61
|
+
###
|
62
|
+
### The constructor should verify any extra required data (aside from :name) in the args before or after
|
63
|
+
### calling super.
|
64
|
+
###
|
65
|
+
### See {JSS::Creatable} for more details.
|
66
|
+
###
|
67
|
+
### === Object Modification
|
68
|
+
###
|
69
|
+
### If a subclass should be modifiable in the JSS, include {JSS::Updatable}, q.v. for details.
|
70
|
+
###
|
71
|
+
### === Object Deletion
|
72
|
+
###
|
73
|
+
### All subclasses can be deleted in the JSS.
|
74
|
+
###
|
75
|
+
### === Required Constants
|
76
|
+
###
|
77
|
+
### Subclasses *must* provide certain Constants in order to correctly interpret API data and
|
78
|
+
### communicate with the API.
|
79
|
+
###
|
80
|
+
### ==== RSRC_BASE = [String], The base for REST resources of this class
|
81
|
+
###
|
82
|
+
### e.g. 'computergroups' in "https://casper.mycompany.com:8443/JSSResource/computergroups/id/12"
|
83
|
+
###
|
84
|
+
### ==== RSRC_LIST_KEY = [Symbol] The Hash key for the JSON list output of all objects of this class in the JSS.
|
85
|
+
###
|
86
|
+
### e.g. the JSON output of resource "JSSResource/computergroups" is a hash
|
87
|
+
### with one item (an Array of computergroups). That item's key is the Symbol :computer_groups
|
88
|
+
###
|
89
|
+
### ==== RSRC_OBJECT_KEY = [Symbol] The Hash key used for individual JSON object output.
|
90
|
+
### It's also used in various error messages
|
91
|
+
###
|
92
|
+
### e.g. the JSON output of the resource "JSSResource/computergroups/id/436" is
|
93
|
+
### a hash with one item (another hash with details of one computergroup).
|
94
|
+
### That item's key is the Symbol :computer_group
|
95
|
+
###
|
96
|
+
### ==== VALID_DATA_KEYS = [Array<Symbol>] The Hash keys used to verify validity of :data
|
97
|
+
### When instantiating a subclass using :data => somehash, some minimal checks are performed
|
98
|
+
### to ensure the data is valid for the subclass
|
99
|
+
###
|
100
|
+
### The Symbols in this Array are compared to the keys of the hash provided.
|
101
|
+
### If any of these don't exist in the hash's keys, then the :data is
|
102
|
+
### not valid and an exception is raised.
|
103
|
+
###
|
104
|
+
### The keys :id and :name must always exist in the hash.
|
105
|
+
### If only :id and :name are valid, VALID_DATA_KEYS should be an empty array.
|
106
|
+
###
|
107
|
+
### e.g. for a department, only :id and :name are valid, so VALID_DATA_KEYS is an empty Array ([])
|
108
|
+
### but for a computer group, the keys :computers and :is_smart must be present as well.
|
109
|
+
### so VALID_DATA_KEYS will be [:computers, :is_smart]
|
110
|
+
###
|
111
|
+
### *NOTE* Some API objects have data broken into subsections, in which case the
|
112
|
+
### VALID_DATA_KEYS are expected in the section :general.
|
113
|
+
###
|
114
|
+
class APIObject
|
115
|
+
|
116
|
+
#####################################
|
117
|
+
### Mix-Ins
|
118
|
+
#####################################
|
119
|
+
|
120
|
+
#####################################
|
121
|
+
### Class Variables
|
122
|
+
#####################################
|
123
|
+
|
124
|
+
### This Hash holds the most recent API query for a list of all items in any subclass,
|
125
|
+
### keyed by the subclass's RSRC_LIST_KEY. See the self.all class method.
|
126
|
+
###
|
127
|
+
### When the .all method is called without an argument, and this hash has
|
128
|
+
### a matching value, the value is returned, rather than requerying the
|
129
|
+
### API. The first time a class calls .all, or whnever refresh is
|
130
|
+
### not false, the API is queried and the value in this hash is updated.
|
131
|
+
###
|
132
|
+
@@all_items = {}
|
133
|
+
|
134
|
+
#####################################
|
135
|
+
### Class Methods
|
136
|
+
#####################################
|
137
|
+
|
138
|
+
###
|
139
|
+
### Return an Array of Hashes for all objects of this subclass in the JSS.
|
140
|
+
###
|
141
|
+
### This method is only valid in subclasses of JSS::APIObject, and is
|
142
|
+
### the parsed JSON output of an API query for the resource defined in the subclass's RSRC_BASE,
|
143
|
+
### e.g. for JSS::Computer, with the RSRC_BASE of :computers,
|
144
|
+
### This method retuens the output of the 'JSSResource/computers' resource,
|
145
|
+
### which is a list of all computers in the JSS.
|
146
|
+
###
|
147
|
+
### Each item in the Array is a Hash with at least two keys, :id and :name.
|
148
|
+
### The class methods .all_ids and .all_names provide easier access to those data
|
149
|
+
### as mapped Arrays.
|
150
|
+
###
|
151
|
+
### Some API classes provide other data in each Hash, e.g. :udid (for computers
|
152
|
+
### and mobile devices) or :is_smart (for groups).
|
153
|
+
###
|
154
|
+
### Subclasses implementing those API classes should provide .all_xxx
|
155
|
+
### class methods for accessing those other values as mapped Arrays,
|
156
|
+
### e.g. JSS::Computer.all_udids
|
157
|
+
###
|
158
|
+
### The results of the first query for each subclass is stored in @@all_items
|
159
|
+
### and returned at every future call, so as to not requery the server every time.
|
160
|
+
###
|
161
|
+
### To force requerying to get updated data, provided a non-false argument.
|
162
|
+
### I usually use :refresh, so that it's obvious what I'm doing, but true, 1,
|
163
|
+
### or anything besides false or nil will work.
|
164
|
+
###
|
165
|
+
### @param refresh[Boolean] should the data be re-queried from the API?
|
166
|
+
###
|
167
|
+
### @return [Array<Hash{:name=>String, :id=> Integer}>]
|
168
|
+
###
|
169
|
+
def self.all(refresh = false)
|
170
|
+
raise JSS::UnsupportedError, ".all can only be called on subclasses of JSS::APIObject" if self == JSS::APIObject
|
171
|
+
@@all_items[self::RSRC_LIST_KEY] = nil if refresh
|
172
|
+
return @@all_items[self::RSRC_LIST_KEY] if @@all_items[self::RSRC_LIST_KEY]
|
173
|
+
@@all_items[self::RSRC_LIST_KEY] = JSS::API.get_rsrc(self::RSRC_BASE)[self::RSRC_LIST_KEY]
|
174
|
+
end
|
175
|
+
|
176
|
+
###
|
177
|
+
### Returns an Array of the JSS id numbers of all the members
|
178
|
+
### of the subclass.
|
179
|
+
###
|
180
|
+
### e.g. When called from subclass JSS::Computer,
|
181
|
+
### returns the id's of all computers in the JSS
|
182
|
+
###
|
183
|
+
### @param refresh[Boolean] should the data be re-queried from the API?
|
184
|
+
###
|
185
|
+
### @return [Array<Integer>] the ids of all items of this subclass in the JSS
|
186
|
+
###
|
187
|
+
def self.all_ids(refresh = false)
|
188
|
+
self.all(refresh).map{|i| i[:id]}
|
189
|
+
end
|
190
|
+
|
191
|
+
###
|
192
|
+
### Returns an Array of the JSS names of all the members
|
193
|
+
### of the subclass.
|
194
|
+
###
|
195
|
+
### e.g. When called from subclass JSS::Computer,
|
196
|
+
### returns the names of all computers in the JSS
|
197
|
+
###
|
198
|
+
### @param refresh[Boolean] should the data be re-queried from the API?
|
199
|
+
###
|
200
|
+
### @return [Array<String>] the names of all item of this subclass in the JSS
|
201
|
+
###
|
202
|
+
def self.all_names(refresh = false)
|
203
|
+
self.all(refresh).map{|i| i[:name]}
|
204
|
+
end
|
205
|
+
|
206
|
+
###
|
207
|
+
### Return a hash of all objects of this subclass
|
208
|
+
### in the JSS where the key is the id, and the value
|
209
|
+
### is some other key in the data items returned by the JSS::APIObject.all.
|
210
|
+
###
|
211
|
+
### If the other key doesn't exist in the API
|
212
|
+
### data, (eg :udid for JSS::Department) the values will be nil.
|
213
|
+
###
|
214
|
+
### Use this method to map ID numbers to other identifiers returned
|
215
|
+
### by the API list resources. Invert its result to map the other
|
216
|
+
### identfier to ids.
|
217
|
+
###
|
218
|
+
### @example
|
219
|
+
### JSS::Computer.map_all_ids_to(:name)
|
220
|
+
###
|
221
|
+
### # Returns, eg {2 => "kimchi", 5 => "mantis"}
|
222
|
+
###
|
223
|
+
### JSS::Computer.map_all_ids_to(:name).invert
|
224
|
+
###
|
225
|
+
### # Returns, eg {"kimchi" => 2, "mantis" => 5}
|
226
|
+
###
|
227
|
+
### @param other_key[Symbol] the other data key with which to associate each id
|
228
|
+
###
|
229
|
+
### @param refresh[Boolean] should the data re-queried from the API?
|
230
|
+
###
|
231
|
+
### @return [Hash{Integer => Oject}] the associated ids and data
|
232
|
+
###
|
233
|
+
def self.map_all_ids_to(other_key, refresh = false)
|
234
|
+
h = {}
|
235
|
+
self.all(refresh).each{|i| h[i[:id]] = i[other_key]}
|
236
|
+
h
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
### Return an Array of JSS::APIObject subclass instances
|
241
|
+
### e.g when called on JSS::Package, return all JSS::Package
|
242
|
+
### objects in the JSS.
|
243
|
+
###
|
244
|
+
### NOTE: This may be slow as it has to look up each object individually!
|
245
|
+
### use it wisely.
|
246
|
+
###
|
247
|
+
### @param refresh[Boolean] should the data re-queried from the API?
|
248
|
+
###
|
249
|
+
### @return [Hash{Integer => Object}] the objects requested
|
250
|
+
def self.all_objects(refresh = false)
|
251
|
+
objects_key = "#{self::RSRC_LIST_KEY}_objects".to_sym
|
252
|
+
@@all_items[objects_key] = nil if refresh
|
253
|
+
return @@all_items[objects_key] if @@all_items[objects_key]
|
254
|
+
@@all_items[objects_key] = self.all(refresh = false).map{|o| self.new :id => o[:id]}
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
|
259
|
+
###
|
260
|
+
### Convert an Array of Hashes of API object data to a
|
261
|
+
### REXML element.
|
262
|
+
###
|
263
|
+
### Given an Array of Hashes of items in the subclass
|
264
|
+
### where each Hash has at least an :id or a :name key,
|
265
|
+
### (as what comes from the .all class method)
|
266
|
+
### return a REXML <classes> element
|
267
|
+
### with one <class> element per Hash member.
|
268
|
+
###
|
269
|
+
### @example
|
270
|
+
### # for class JSS::Computer
|
271
|
+
### some_comps = [{:id=>2, :name=>"kimchi"},{:id=>5, :name=>"mantis"}]
|
272
|
+
### xml_names = JSS::Computer.xml_list some_comps
|
273
|
+
### puts xml_names # output manually formatted for clarity, xml.to_s has no newlines between elements
|
274
|
+
###
|
275
|
+
### <computers>
|
276
|
+
### <computer>
|
277
|
+
### <name>kimchi</name>
|
278
|
+
### </computer>
|
279
|
+
### <computer>
|
280
|
+
### <name>mantis</name>
|
281
|
+
### </computer>
|
282
|
+
### </computers>
|
283
|
+
###
|
284
|
+
### xml_ids = JSS::Computer.xml_list some_comps, :id
|
285
|
+
### puts xml_names # output manually formatted for clarity, xml.to_s has no newlines between elements
|
286
|
+
###
|
287
|
+
### <computers>
|
288
|
+
### <computer>
|
289
|
+
### <id>2</id>
|
290
|
+
### </computer>
|
291
|
+
### <computer>
|
292
|
+
### <id>5</id>
|
293
|
+
### </computer>
|
294
|
+
### </computers>
|
295
|
+
###
|
296
|
+
### @param array[Array<Hash{:name=>String, :id =>Integer, Symbol=>#to_s}>] the Array of subclass data to convert
|
297
|
+
###
|
298
|
+
### @param content[Symbol] the Hash key to use as the inner element for each member of the Array
|
299
|
+
###
|
300
|
+
### @return [REXML::Element] the XML element representing the data
|
301
|
+
###
|
302
|
+
def self.xml_list(array, content = :name)
|
303
|
+
JSS.item_list_to_rexml_list self::RSRC_LIST_KEY, self::RSRC_OBJECT_KEY, array, content
|
304
|
+
end
|
305
|
+
|
306
|
+
###
|
307
|
+
### Some API objects contain references to other API objects. Usually those
|
308
|
+
### references are a Hash containing the :id and :name of the target. Sometimes,
|
309
|
+
### however the reference is just the name of the target.
|
310
|
+
###
|
311
|
+
### A Script has a property :category, which comes from the API as
|
312
|
+
### a String, the name of the category for that script. e.g. "GoodStuff"
|
313
|
+
###
|
314
|
+
### A Policy also has a property :category, but it comes from the API as a Hash
|
315
|
+
### with both the name and id, e.g. !{:id => 8, :name => "GoodStuff"}
|
316
|
+
###
|
317
|
+
### When that reference is to a single thing (like the category to which something belongs)
|
318
|
+
### APIObject subclasses usually store only the name, and use the name when
|
319
|
+
### returning data to the API.
|
320
|
+
###
|
321
|
+
### When an object references a list of related objects
|
322
|
+
### (like the computers assigned to a user) that list will be and Array of Hashes
|
323
|
+
### as above, with both the :id and :name
|
324
|
+
###
|
325
|
+
###
|
326
|
+
### This method is just a handy way to extract the name regardless of how it comes
|
327
|
+
### from the API. Most APIObject subclasses use it in their #initialize method
|
328
|
+
###
|
329
|
+
### @param a_thing[String,Array] the api data from which we're extracting the name
|
330
|
+
###
|
331
|
+
### @return [String] the name extracted from a_thing
|
332
|
+
###
|
333
|
+
def self.get_name(a_thing)
|
334
|
+
case a_thing
|
335
|
+
when String
|
336
|
+
a_thing
|
337
|
+
when Hash
|
338
|
+
a_thing[:name]
|
339
|
+
when nil
|
340
|
+
nil
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
#####################################
|
345
|
+
### Class Constants
|
346
|
+
#####################################
|
347
|
+
|
348
|
+
###
|
349
|
+
### These Symbols are added to VALID_DATA_KEYS for performing the
|
350
|
+
### :data validity test described above.
|
351
|
+
###
|
352
|
+
REQUIRED_DATA_KEYS = [:id, :name]
|
353
|
+
|
354
|
+
###
|
355
|
+
### By default, these keys are available for object lookups
|
356
|
+
### Others can be added by subclasses using an array of them
|
357
|
+
### as the second argument to super(initialize)
|
358
|
+
### The keys must be Symbols that match the keyname in the resource url.
|
359
|
+
### e.g. :serialnumber for JSSResource/computers/serialnumber/xxxxx
|
360
|
+
###
|
361
|
+
DEFAULT_LOOKUP_KEYS = [:id, :name]
|
362
|
+
|
363
|
+
#####################################
|
364
|
+
### Attributes
|
365
|
+
#####################################
|
366
|
+
|
367
|
+
### @return [Integer] the JSS id number
|
368
|
+
attr_reader :id
|
369
|
+
|
370
|
+
### @return [String] the name
|
371
|
+
attr_reader :name
|
372
|
+
|
373
|
+
### @return [Boolean] is it in the JSS?
|
374
|
+
attr_reader :in_jss
|
375
|
+
|
376
|
+
### @return [String] the Rest resource for API access (the part after "JSSResource/" )
|
377
|
+
attr_reader :rest_rsrc
|
378
|
+
|
379
|
+
#####################################
|
380
|
+
### Constructor
|
381
|
+
#####################################
|
382
|
+
|
383
|
+
###
|
384
|
+
### The args hash must include :id, :name, or :data.
|
385
|
+
### * :id or :name will be looked up via the API
|
386
|
+
### * * if the subclass includes JSS::Creatable, :id can be :new, to create a new object in the JSS.
|
387
|
+
### and :name is required
|
388
|
+
### * :data must be the JSON output of a separate {JSS::APIConnection} query (a Hash of valid object data)
|
389
|
+
###
|
390
|
+
### Some subclasses can accept other options, by pasing their keys in a final Array
|
391
|
+
###
|
392
|
+
### @param args[Hash] the data for looking up, or constructing, a new object.
|
393
|
+
###
|
394
|
+
### @option args :id[Integer] the jss id to look up
|
395
|
+
###
|
396
|
+
### @option args :name[String] the name to look up
|
397
|
+
###
|
398
|
+
### @option args :data[Hash] the JSON output of a separate {JSS::APIConnection} query
|
399
|
+
###
|
400
|
+
### @param other_lookup_keys[Array<Symbol>] Hash keys other than :id and :name, by which an API
|
401
|
+
### lookup may be performed.
|
402
|
+
###
|
403
|
+
def initialize(args = {}, other_lookup_keys = [])
|
404
|
+
|
405
|
+
################################
|
406
|
+
################################
|
407
|
+
####### Previously looked-up JSON data
|
408
|
+
if args[:data]
|
409
|
+
|
410
|
+
@init_data = args[:data]
|
411
|
+
### Does this data come in subsets?
|
412
|
+
@got_subsets = @init_data[:general].kind_of?(Hash)
|
413
|
+
|
414
|
+
### data must include all they keys in REQUIRED_DATA_KEYS + VALID_DATA_KEYS
|
415
|
+
### in either the main hash keys or the :general sub-hash, if it exists
|
416
|
+
hash_to_check = @got_subsets ? @init_data[:general] : @init_data
|
417
|
+
combined_valid_keys = self.class::REQUIRED_DATA_KEYS + self.class::VALID_DATA_KEYS
|
418
|
+
keys_ok = (hash_to_check.keys & combined_valid_keys).count == combined_valid_keys.count
|
419
|
+
raise JSS::InvalidDataError, ":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 ', :'}" unless keys_ok
|
420
|
+
|
421
|
+
### and the id must be in the jss
|
422
|
+
raise NoSuchItemError, "No #{self.class::RSRC_OBJECT_KEY} with JSS id: #{@init_data[:id]}" unless self.class.all_ids.include? hash_to_check[:id]
|
423
|
+
|
424
|
+
|
425
|
+
################################
|
426
|
+
################################
|
427
|
+
###### Make a new one in the JSS, but only if we've included the Creatable module
|
428
|
+
elsif args[:id] == :new
|
429
|
+
|
430
|
+
raise JSS::UnsupportedError, "Creating #{self.class::RSRC_LIST_KEY} isn't yet supported. Please use other Casper workflows." unless defined? self.class::CREATABLE
|
431
|
+
|
432
|
+
raise JSS::MissingDataError, "You must provide a :name for a new #{self.class::RSRC_OBJECT_KEY}." unless args[:name]
|
433
|
+
|
434
|
+
raise JSS::AlreadyExistsError, "A #{self.class::RSRC_OBJECT_KEY} already exists with the name '#{args[:name]}'" if self.class.all_names.include? args[:name]
|
435
|
+
|
436
|
+
|
437
|
+
### NOTE: subclasses may want to pre-populate more keys in @init_data when :id == :new
|
438
|
+
### then parse them into attributes later.
|
439
|
+
@name = args[:name]
|
440
|
+
@init_data = {:name => args[:name]}
|
441
|
+
@in_jss = false
|
442
|
+
@rest_rsrc = "#{self.class::RSRC_BASE}/name/#{URI.escape @name}"
|
443
|
+
@need_to_update = true
|
444
|
+
return
|
445
|
+
|
446
|
+
################################
|
447
|
+
################################
|
448
|
+
###### Look up the data via the API
|
449
|
+
else
|
450
|
+
### what lookup key are we using?
|
451
|
+
combined_lookup_keys = self.class::DEFAULT_LOOKUP_KEYS + other_lookup_keys
|
452
|
+
lookup_key = (combined_lookup_keys & args.keys)[0]
|
453
|
+
|
454
|
+
raise JSS::MissingDataError, "Args must include :#{combined_lookup_keys.join(', :')}, or :data" unless lookup_key
|
455
|
+
|
456
|
+
rsrc = "#{self.class::RSRC_BASE}/#{lookup_key}/#{args[lookup_key]}"
|
457
|
+
|
458
|
+
begin
|
459
|
+
@init_data = JSS::API.get_rsrc(rsrc)[self.class::RSRC_OBJECT_KEY]
|
460
|
+
|
461
|
+
### If we're looking up by id or name and we're here,
|
462
|
+
### then we have it regardless of which subset it's in
|
463
|
+
### otherwise, first assume they are in the init hash
|
464
|
+
@id = lookup_key == :id ? args[lookup_key] : @init_data[:id]
|
465
|
+
@name = lookup_key == :name ? args[lookup_key] : @init_data[:name]
|
466
|
+
|
467
|
+
rescue RestClient::ResourceNotFound
|
468
|
+
raise NoSuchItemError, "No #{self.class::RSRC_OBJECT_KEY} found matching: #{args[:name] ? args[:name] : args[:id]}"
|
469
|
+
end
|
470
|
+
end ## end arg parsing
|
471
|
+
|
472
|
+
# Find the "main" subset which contains :id and :name
|
473
|
+
#
|
474
|
+
# If they aren't at the top-level of the init hash they are in a subset hash,
|
475
|
+
# usually :general, but sometimes someething else,
|
476
|
+
# like ldap servers, which have them in :connection
|
477
|
+
# Whereever both :id and :name are, that's the main subset
|
478
|
+
|
479
|
+
@init_data.keys.each do |subset|
|
480
|
+
@main_subset = @init_data[subset] if @init_data[subset].kind_of? Hash and @init_data[subset][:id] and @init_data[subset][:name]
|
481
|
+
break if @main_subset
|
482
|
+
end
|
483
|
+
@main_subset ||= @init_data
|
484
|
+
|
485
|
+
@id ||= @main_subset[:id]
|
486
|
+
@name ||= @main_subset[:name]
|
487
|
+
|
488
|
+
# many things have a :site
|
489
|
+
if @main_subset[:site]
|
490
|
+
@site = JSS::APIObject.get_name( @main_subset[:site])
|
491
|
+
end
|
492
|
+
|
493
|
+
# many things have a :category
|
494
|
+
if @main_subset[:category]
|
495
|
+
@category = JSS::APIObject.get_name( @main_subset[:category])
|
496
|
+
end
|
497
|
+
|
498
|
+
# set empty strings to nil
|
499
|
+
@init_data.jss_nillify! '', :recurse
|
500
|
+
|
501
|
+
@in_jss = true
|
502
|
+
@rest_rsrc = "#{self.class::RSRC_BASE}/id/#{@id}"
|
503
|
+
@need_to_update = false
|
504
|
+
end # init
|
505
|
+
|
506
|
+
#####################################
|
507
|
+
### Public Instance Methods
|
508
|
+
#####################################
|
509
|
+
|
510
|
+
###
|
511
|
+
### Either Create or Update this object in the JSS
|
512
|
+
###
|
513
|
+
### If this item is creatable or updatable, then
|
514
|
+
### create it if needed, or update it if it already exists.
|
515
|
+
###
|
516
|
+
### @return [Integer] the id of the item created or updated
|
517
|
+
###
|
518
|
+
def save
|
519
|
+
if @in_jss
|
520
|
+
raise JSS::UnsupportedError, "Updating this object in the JSS is currently not supported" \
|
521
|
+
unless defined? self.class::UPDATABLE
|
522
|
+
return self.update
|
523
|
+
else
|
524
|
+
raise JSS::UnsupportedError, "Creating this object in the JSS is currently not supported" \
|
525
|
+
unless defined? self.class::CREATABLE
|
526
|
+
return self.create
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
|
531
|
+
###
|
532
|
+
### Delete this item from the JSS.
|
533
|
+
###
|
534
|
+
### Subclasses may want to redefine this method,
|
535
|
+
### first calling super, then setting other attributes to
|
536
|
+
### nil, false, empty, etc..
|
537
|
+
###
|
538
|
+
### @return [void]
|
539
|
+
###
|
540
|
+
def delete
|
541
|
+
return nil unless @in_jss
|
542
|
+
JSS::API.delete_rsrc @rest_rsrc
|
543
|
+
@rest_rsrc = "#{self.class::RSRC_BASE}/name/#{URI.escape @name}"
|
544
|
+
@id = nil
|
545
|
+
@in_jss = false
|
546
|
+
@need_to_update = false
|
547
|
+
end # delete
|
548
|
+
|
549
|
+
#####################################
|
550
|
+
### Private Instance Methods
|
551
|
+
#####################################
|
552
|
+
private
|
553
|
+
|
554
|
+
###
|
555
|
+
### Return a String with the XML Resource
|
556
|
+
### for submitting creation or changes to the JSS via
|
557
|
+
### the API via the Creatable or Updatable modules
|
558
|
+
###
|
559
|
+
### Most classes will redefine this method.
|
560
|
+
###
|
561
|
+
def rest_xml
|
562
|
+
doc = REXML::Document.new APIConnection::XML_HEADER
|
563
|
+
tmpl = doc.add_element self.class::RSRC_OBJECT_KEY.to_s
|
564
|
+
tmpl.add_element('name').text = @name
|
565
|
+
return doc.to_s
|
566
|
+
end
|
567
|
+
|
568
|
+
|
569
|
+
### Aliases
|
570
|
+
|
571
|
+
alias in_jss? in_jss
|
572
|
+
|
573
|
+
end # class APIObject
|
574
|
+
|
575
|
+
|
576
|
+
|
577
|
+
end # module JSS
|
578
|
+
|
579
|
+
### Mix-in Sub Modules
|
580
|
+
require "jss/api_object/creatable"
|
581
|
+
require "jss/api_object/uploadable"
|
582
|
+
require "jss/api_object/locatable"
|
583
|
+
require "jss/api_object/matchable"
|
584
|
+
require "jss/api_object/purchasable"
|
585
|
+
require "jss/api_object/updatable"
|
586
|
+
require "jss/api_object/extendable"
|
587
|
+
|
588
|
+
### Mix-in Sub Modules with Classes
|
589
|
+
require "jss/api_object/criteriable"
|
590
|
+
require "jss/api_object/scopable"
|
591
|
+
|
592
|
+
### APIObject SubClasses with SubClasses
|
593
|
+
require "jss/api_object/advanced_search"
|
594
|
+
require "jss/api_object/extension_attribute"
|
595
|
+
require "jss/api_object/group"
|
596
|
+
|
597
|
+
### APIObject SubClasses without SubClasses
|
598
|
+
require "jss/api_object/building"
|
599
|
+
require "jss/api_object/category"
|
600
|
+
require "jss/api_object/computer"
|
601
|
+
require "jss/api_object/department"
|
602
|
+
require "jss/api_object/distribution_point"
|
603
|
+
require "jss/api_object/ldap_server"
|
604
|
+
require "jss/api_object/mobile_device"
|
605
|
+
require "jss/api_object/netboot_server"
|
606
|
+
require "jss/api_object/network_segment"
|
607
|
+
require "jss/api_object/package"
|
608
|
+
require "jss/api_object/peripheral_type"
|
609
|
+
require "jss/api_object/peripheral"
|
610
|
+
require "jss/api_object/policy"
|
611
|
+
require "jss/api_object/removable_macaddr"
|
612
|
+
require "jss/api_object/script"
|
613
|
+
require "jss/api_object/site"
|
614
|
+
require "jss/api_object/software_update_server"
|
615
|
+
require "jss/api_object/user"
|
616
|
+
|