ruby-jss 0.14.0 → 1.0.0b2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ruby-jss might be problematic. Click here for more details.

@@ -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