ruby-jss 0.14.0 → 1.0.0b2

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.

@@ -0,0 +1,357 @@
1
+ ### Copyright 2018 Pixar
2
+
3
+ ###
4
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
5
+ ### with the following modification; you may not use this file except in
6
+ ### compliance with the Apache License and the following modification to it:
7
+ ### Section 6. Trademarks. is deleted and replaced with:
8
+ ###
9
+ ### 6. Trademarks. This License does not grant permission to use the trade
10
+ ### names, trademarks, service marks, or product names of the Licensor
11
+ ### and its affiliates, except as required to comply with Section 4(c) of
12
+ ### the License and to reproduce the content of the NOTICE file.
13
+ ###
14
+ ### You may obtain a copy of the Apache License at
15
+ ###
16
+ ### http://www.apache.org/licenses/LICENSE-2.0
17
+ ###
18
+ ### Unless required by applicable law or agreed to in writing, software
19
+ ### distributed under the Apache License with the above modification is
20
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21
+ ### KIND, either express or implied. See the Apache License for the specific
22
+ ### language governing permissions and limitations under the Apache License.
23
+ ###
24
+ ###
25
+ ###
26
+
27
+ module JSS
28
+
29
+ # A patch source. The abstract parent class of {JSS::PatchInternalSource} and
30
+ # {JSS::PatchExternalSource}
31
+ #
32
+ # @see JSS::APIObject
33
+ #
34
+ class PatchSource < JSS::APIObject
35
+
36
+ include JSS::Updatable
37
+
38
+ HTTP = 'http'.freeze
39
+ HTTPS = 'https'.freeze
40
+
41
+ DFT_ENABLED = false
42
+ DFT_SSL = true
43
+ DFT_SSL_PORT = 443
44
+ DFT_NO_SSL_PORT = 80
45
+
46
+ AVAILABLE_TITLES_RSRC = 'patchavailabletitles/sourceid/'.freeze
47
+
48
+ # TODO: remove this and adjust parsing when jamf fixes the JSON
49
+ # Data map for PatchReport XML data parsing cuz Borked JSON
50
+ # @see {JSS::XMLWorkaround} for details
51
+ AVAILABLE_TITLES_DATA_MAP = {
52
+ patch_available_titles: {
53
+ available_titles: [
54
+ {
55
+ name_id: JSS::BLANK,
56
+ current_version: JSS::BLANK,
57
+ publisher: JSS::BLANK,
58
+ last_modified: JSS::BLANK,
59
+ app_name: JSS::BLANK
60
+ }
61
+ ]
62
+ }
63
+ }.freeze
64
+
65
+ # Class Methods
66
+ #
67
+ # These work from this metaclass, as well as from the
68
+ # subclasses. In the metaclass, both subclasses are searched
69
+ # and a :type value is available.
70
+ ############################################
71
+
72
+ # Get names, ids and types for all patch sources
73
+ #
74
+ # @param refresh[Boolean] should the data be re-queried from the API?
75
+ #
76
+ # @param api[JSS::APIConnection] an API connection to use for the query.
77
+ # Defaults to the corrently active API. See {JSS::APIConnection}
78
+ #
79
+ # @return [Array<Hash{:name=>String, :id=> Integer, :type => Symbol}>]
80
+ #
81
+ def self.all(refresh = false, api: JSS.api)
82
+ if self == JSS::PatchSource
83
+ int = JSS::PatchInternalSource.all(refresh, api: api).each { |s| s[:type] = :internal }
84
+ ext = JSS::PatchExternalSource.all(refresh, api: api).each { |s| s[:type] = :external }
85
+ return (int + ext).sort! { |s1, s2| s1[:id] <=> s2[:id] }
86
+ end
87
+ super
88
+ end
89
+
90
+ # Get names, ids for all patch internal sources
91
+ #
92
+ # the same as JSS::PatchInternalSource.all refresh, api: api
93
+ #
94
+ # @see JSS::PatchInternalSource.all
95
+ #
96
+ def self.all_internal(refresh = false, api: JSS.api)
97
+ JSS::PatchInternalSource.all refresh, api: api
98
+ end
99
+
100
+ # Get names, ids for all patch internal sources
101
+ #
102
+ # the same as JSS::PatchExternalSource.all refresh, api: api
103
+ #
104
+ # @see JSS::PatchExternalSource.all
105
+ #
106
+ def self.all_external(refresh = false, api: JSS.api)
107
+ JSS::PatchExternalSource.all refresh, api: api
108
+ end
109
+
110
+ # @see JSS::APIObject.all_objects
111
+ #
112
+ def self.all_objects(refresh = false, api: JSS.api)
113
+ if self == JSS::PatchSource
114
+ int = JSS::PatchInternalSource.all_objects refresh, api: api
115
+ ext = JSS::PatchExternalSource.all_objects refresh, api: api
116
+ return (int + ext).sort! { |s1, s2| s1.id <=> s2.id }
117
+ end
118
+ super
119
+ end
120
+
121
+ # Fetch either an internal or external patch source
122
+ #
123
+ # BUG: there's an API bug: fetching a non-existent ids
124
+ # which is why we rescue internal server errors.
125
+ #
126
+ # @see APIObject.fetch
127
+ #
128
+ def self.fetch(arg, api: JSS.api)
129
+ if self == JSS::PatchSource
130
+ begin
131
+ fetched = JSS::PatchInternalSource.fetch arg, api: api
132
+ rescue RestClient::ResourceNotFound, RestClient::InternalServerError, JSS::NoSuchItemError
133
+ fetched = nil
134
+ end
135
+ unless fetched
136
+ begin
137
+ fetched = JSS::PatchExternalSource.fetch arg, api: api
138
+ rescue RestClient::ResourceNotFound, RestClient::InternalServerError, JSS::NoSuchItemError
139
+ raise JSS::NoSuchItemError, 'No matching PatchSource found'
140
+ end
141
+ end
142
+ return fetched
143
+ end # if self == JSS::PatchSource
144
+ begin
145
+ super
146
+ rescue RestClient::ResourceNotFound, RestClient::InternalServerError, JSS::NoSuchItemError
147
+ raise JSS::NoSuchItemError, "No matching #{self::RSRC_OBJECT_KEY} found"
148
+ end
149
+ end
150
+
151
+ # Only JSS::PatchExternalSources can be created
152
+ #
153
+ # @see APIObject.make
154
+ #
155
+ def self.make(**args)
156
+ case self.name
157
+ when 'JSS::PatchSource'
158
+ JSS::PatchExternalSource.make args
159
+ when 'JSS::PatchExternalSource'
160
+ super
161
+ when 'JSS::PatchInternalSource'
162
+ raise JSS::UnsupportedError, 'PatchInteralSources cannot be created.'
163
+ end
164
+ end
165
+
166
+ # Only JSS::PatchExternalSources can be deleted
167
+ #
168
+ # @see APIObject.delete
169
+ #
170
+ def self.delete(victims, api: JSS.api)
171
+ case self.name
172
+ when 'JSS::PatchSource'
173
+ JSS::PatchExternalSource victims, api: api
174
+ when 'JSS::PatchExternalSource'
175
+ super
176
+ when 'JSS::PatchInternalSource'
177
+ raise JSS::UnsupportedError, 'PatchInteralSources cannot be deleted.'
178
+ end
179
+ end
180
+
181
+ # Get a list of patch titles available from a Patch Source (either
182
+ # internal or external, since they have unique ids )
183
+ #
184
+ # @param source[String,Integer] name or id of the Patch Source for which to
185
+ # get the available titles
186
+ #
187
+ # @param api[JSS::APIConnection] The api connection to use for the query
188
+ # Defaults to the currently active connection
189
+ #
190
+ # @return [Array<Hash{Symbol:String}>] One hash for each available title, with
191
+ # these keys:
192
+ # :name_id String
193
+ # :current_version String
194
+ # :publisher String
195
+ # :last_modified Time
196
+ # :app_name String
197
+ #
198
+ def self.available_titles(source, api: JSS.api)
199
+ src_id = valid_patch_source_id source, api: api
200
+ raise JSS::NoSuchItemError, "No Patch Source found matching: #{source}" unless src_id
201
+
202
+ rsrc_base =
203
+ if valid_patch_source_type(src_id, api: api) == :internal
204
+ JSS::PatchInternalSource::AVAILABLE_TITLES_RSRC
205
+ else
206
+ JSS::PatchExternalSource::AVAILABLE_TITLES_RSRC
207
+ end
208
+
209
+ rsrc = "#{rsrc_base}#{src_id}"
210
+
211
+ begin
212
+ # TODO: remove this and adjust parsing when jamf fixes the JSON
213
+ raw = JSS::XMLWorkaround.data_via_xml(rsrc, AVAILABLE_TITLES_DATA_MAP, api)
214
+ rescue RestClient::ResourceNotFound
215
+ return []
216
+ end
217
+
218
+ titles = raw[:patch_available_titles][:available_titles]
219
+ titles.each { |t| t[:last_modified] = Time.parse t[:last_modified] }
220
+ titles
221
+ end
222
+
223
+ # FOr a given patch source, an array of available 'name_id's
224
+ # which are uniq identifiers for titles available on that source.
225
+ #
226
+ # @see available_titles
227
+ #
228
+ # @return [Array<String>] the name_ids available on the source
229
+ #
230
+ def self.available_name_ids(source, api: JSS.api)
231
+ available_titles(source, api: api).map { |t| t[:name_id] }
232
+ end
233
+
234
+ # Given a name or id for a Patch Source (internal or external)
235
+ # return the id if it exists, or nil if it doesn't.
236
+ #
237
+ # NOTE: does not indicate which kind of source it is, just that it exists
238
+ # and can be used as a source_id for a patch title.
239
+ # @see .valid_patch_source_type
240
+ #
241
+ # @param ident[String,Integer] the name or id to validate
242
+ #
243
+ # @param refresh [Boolean] Should the data be re-read from the server
244
+ #
245
+ # @param api[JSS::APIConnection] an API connection to use for the query.
246
+ # Defaults to the corrently active API. See {JSS::APIConnection}
247
+ #
248
+ # @return [Integer, nil] the valid id or nil if it doesn't exist.
249
+ #
250
+ def self.valid_patch_source_id(ident, refresh = false, api: JSS.api)
251
+ id = JSS::PatchInternalSource.valid_id ident, refresh, api: api
252
+ id ||= JSS::PatchExternalSource.valid_id ident, refresh, api: api
253
+ id
254
+ end
255
+
256
+ # Given a name or id for a Patch Source
257
+ # return :internal or :external if it exists, or nil if it doesnt.
258
+ #
259
+ # @param ident[String,Integer] the name or id to validate
260
+ #
261
+ # @param refresh [Boolean] Should the data be re-read from the server
262
+ #
263
+ # @param api[JSS::APIConnection] an API connection to use for the query.
264
+ # Defaults to the corrently active API. See {JSS::APIConnection}
265
+ #
266
+ # @return [Symbol, nil] :internal, :external, or nil if it doesn't exist.
267
+ #
268
+ def self.valid_patch_source_type(ident, refresh = false, api: JSS.api)
269
+ return :internel if JSS::PatchInternalSource.valid_id ident, refresh, api: api
270
+ return :external if JSS::PatchExternalSource.valid_id ident, refresh, api: api
271
+ nil
272
+ end
273
+
274
+ # Attributes
275
+ #####################################
276
+
277
+ # @return [Boolean] Is this source enabled?
278
+ attr_reader :enabled
279
+ alias enabled? enabled
280
+
281
+ # @return [String] The URL from which patch info is retrieved
282
+ attr_reader :endpoint
283
+ alias url endpoint
284
+
285
+ # @param newname [String] The new host name (external sources only)
286
+ #
287
+ # @return [String] The host name of the patch source
288
+ attr_reader :host_name
289
+ alias hostname host_name
290
+ alias host host_name
291
+
292
+ # @param new_port [Integer] The new port (external sources only)
293
+ #
294
+ # @return [Integer] the TCP port of the patch source
295
+ attr_reader :port
296
+
297
+ # @return [Boolean] Is SSL enabled for the patch source?
298
+ attr_reader :ssl_enabled
299
+ alias ssl_enabled? ssl_enabled
300
+
301
+ # Init
302
+ def initialize(**args)
303
+ raise JSS::UnsupportedError, 'PatchSource is an abstract metaclass. Please use PatchInternalSource or PatchExternalSource' if self.class == JSS::PatchSource
304
+
305
+ super
306
+
307
+ @enabled = @init_data[:enabled].to_s.jss_to_bool
308
+ @enabled ||= false
309
+
310
+ # derive the data not provided for this source type
311
+ if @init_data[:endpoint]
312
+ @endpoint = @init_data[:endpoint]
313
+ url = URI.parse endpoint
314
+ @host_name = url.host
315
+ @port = url.port
316
+ @ssl_enabled = url.scheme == HTTPS
317
+ else
318
+ @host_name = @init_data[:host_name]
319
+ @port = @init_data[:port].to_i
320
+ @port ||= ssl_enabled? ? DFT_SSL_PORT : DFT_NO_SSL_PORT
321
+ @ssl_enabled = @init_data[:ssl_enabled].to_s.jss_to_bool
322
+ @ssl_enabled ||= false
323
+ @endpoint = "#{ssl_enabled ? HTTPS : HTTP}://#{host_name}:#{port}/"
324
+ end
325
+ end # init
326
+
327
+ # Get a list of patch titles available from this Patch Source
328
+ # @see JSS::PatchSource.available_titles
329
+ #
330
+ def available_titles
331
+ self.class.available_titles id, api: api
332
+ end
333
+
334
+ # Get a list of available name_id's for this patch source
335
+ # @see JSS::PatchSource.available_name_ids
336
+ #
337
+ def available_name_ids
338
+ self.class.available_name_ids id, api: api
339
+ end
340
+
341
+ # Delete this instance
342
+ # This method is needed to override APIObject#delete
343
+ def delete
344
+ case self.class.name
345
+ when 'JSS::PatchExternalSource'
346
+ super
347
+ when 'JSS::PatchInternalSource'
348
+ raise JSS::UnsupportedError, 'PatchInteralSources cannot be deleted.'
349
+ end
350
+ end
351
+
352
+ end # class PatchSource
353
+
354
+ end # module JSS
355
+
356
+ require 'jss/api_object/patch_source/patch_internal_source'
357
+ require 'jss/api_object/patch_source/patch_external_source'
@@ -0,0 +1,156 @@
1
+ ### Copyright 2018 Pixar
2
+
3
+ ###
4
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
5
+ ### with the following modification; you may not use this file except in
6
+ ### compliance with the Apache License and the following modification to it:
7
+ ### Section 6. Trademarks. is deleted and replaced with:
8
+ ###
9
+ ### 6. Trademarks. This License does not grant permission to use the trade
10
+ ### names, trademarks, service marks, or product names of the Licensor
11
+ ### and its affiliates, except as required to comply with Section 4(c) of
12
+ ### the License and to reproduce the content of the NOTICE file.
13
+ ###
14
+ ### You may obtain a copy of the Apache License at
15
+ ###
16
+ ### http://www.apache.org/licenses/LICENSE-2.0
17
+ ###
18
+ ### Unless required by applicable law or agreed to in writing, software
19
+ ### distributed under the Apache License with the above modification is
20
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21
+ ### KIND, either express or implied. See the Apache License for the specific
22
+ ### language governing permissions and limitations under the Apache License.
23
+ ###
24
+ ###
25
+
26
+ ###
27
+ module JSS
28
+
29
+ # An 'External' patch source. These sources are defined by Jamf Admins
30
+ # and can be created, modified or deleted.
31
+ #
32
+ # @see JSS::APIObject
33
+ #
34
+ class PatchExternalSource < JSS::PatchSource
35
+
36
+ include JSS::Creatable
37
+
38
+ # Constants
39
+ #####################################
40
+
41
+ ### The base for REST resources of this class
42
+ RSRC_BASE = 'patchexternalsources'.freeze
43
+
44
+ ### the hash key used for the JSON list output of all objects in the JSS
45
+ RSRC_LIST_KEY = :patch_external_sources
46
+
47
+ # The hash key used for the JSON object output.
48
+ # It's also used in various error messages
49
+ RSRC_OBJECT_KEY = :patch_external_source
50
+
51
+ ### these keys, as well as :id and :name, are present in valid API JSON data for this class
52
+ VALID_DATA_KEYS = %i[enabled ssl_enabled host_name].freeze
53
+
54
+ # Instance Methods
55
+ #####################################
56
+
57
+ # Enable this source for retrieving patch info
58
+ #
59
+ # @return [void]
60
+ #
61
+ def enable
62
+ return if enabled?
63
+ validate_host_port('enable a patch source')
64
+ @enabled = true
65
+ @need_to_update = true
66
+ end
67
+
68
+ # Disable this source for retrieving patch info
69
+ #
70
+ # @return [void]
71
+ #
72
+ def disable
73
+ return unless enabled?
74
+ @enabled = false
75
+ @need_to_update = true
76
+ end
77
+
78
+ # see PatchSource attr_reader :host_name
79
+ #
80
+ def host_name=(newname)
81
+ return if newname == host_name
82
+ raise JSS::InvalidDataError, 'names must be String' unless name.is_a? String
83
+ @host_name = name
84
+ @need_to_update = true
85
+ end
86
+ alias hostname= host_name=
87
+ alias host= host_name=
88
+
89
+ # see PatchSource attr_reader :port
90
+ #
91
+ def port=(new_port)
92
+ return if new_port == port
93
+ raise JSS::InvalidDataError, 'ports must be Integers' unless port.is_a? Integer
94
+ @port = new_port
95
+ @need_to_update = true
96
+ end
97
+
98
+ # Use SSL for connecting to the source host
99
+ #
100
+ # @return [void]
101
+ #
102
+ def use_ssl
103
+ return if ssl_enabled?
104
+ @ssl_enabled = true
105
+ @need_to_update = true
106
+ end
107
+ alias enable_ssl use_ssl
108
+
109
+ # Do not use SSL for connecting to the source host
110
+ #
111
+ # @return [void]
112
+ #
113
+ def no_ssl
114
+ return unless ssl_enabled?
115
+ @ssl_enabled = false
116
+ @need_to_update = true
117
+ end
118
+ alias disable_ssl no_ssl
119
+
120
+ def create
121
+ validate_host_port('create a patch source')
122
+ super
123
+ end
124
+
125
+ def update
126
+ validate_host_port('update a patch source')
127
+ super
128
+ end
129
+
130
+ private
131
+
132
+ # raise an exeption if needed when trying to do something that needs
133
+ # a host and port set
134
+ #
135
+ # @param action[String] The action that needs a host and port
136
+ #
137
+ # @return [void]
138
+ #
139
+ def validate_host_port(action)
140
+ raise JSS::UnsupportedError, "Cannot #{action} without first setting a host_name and port" if host_name.to_s.empty? || port.to_s.empty?
141
+ end
142
+
143
+ def rest_xml
144
+ doc = REXML::Document.new
145
+ src = doc.add_element self.class::RSRC_OBJECT_KEY.to_s
146
+ src.add_element('enabled').text = @enabled.to_s
147
+ src.add_element('name').text = @name
148
+ src.add_element('ssl_enabled').text = @ssl_enabled.to_s
149
+ src.add_element('host_name').text = @host_name
150
+ src.add_element('port').text = @port.to_s
151
+ doc.to_s
152
+ end
153
+
154
+ end # class PatchInternalSource
155
+
156
+ end # module JSS