jss-api 0.5.4

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