ruby-jss 0.6.3
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 +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
|
+
|