ruby-jss 1.0.0b2 → 1.0.0b6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e2cda3beb0c57a922fa2bae9be0ed07ddb0ad3f4
4
- data.tar.gz: a5c707f859c044e7d7021b9e6c7d4a0c5f6900a9
2
+ SHA256:
3
+ metadata.gz: b7a56f45bbacab2bbc21539605b30f7bedbaef3672a639d2f32b80d766352090
4
+ data.tar.gz: d9ade8517e722087ae3fc6fe4ed79c01b938a01a1e5b556c3df6ca2b294282d1
5
5
  SHA512:
6
- metadata.gz: 2f655fcf07b79416bcccef6eceacec0c023e7fccb4507c4588df60c38c7a40c226951539eecdaa85dd094e3fbcd9682417e04e20b371ceeb1e2f1ac6d1422b8c
7
- data.tar.gz: 60e672418b4fff07a72df15cbe2e8aa5963566db72425c1640a11bf0355a8f79ef196783c74dbeff2f983da143a3d10971d333598ee243190279d3199c22acef
6
+ metadata.gz: 81be4beedc05189fb45f8978895458c1ee9ee70eaee7755c90bd3b7f9af01084f39593b5caaea77973ad20d41e317e40235a5307d4279ebc84dcd9e780120095
7
+ data.tar.gz: 0afc5a122964e84d1869a5c4f9914d0b790d91f641a1b76aa1673640bea0a501449e6d59c653fcec3adc0b3f14e00d6ae18350b2a988be32d4dc415b0f92fd30
data/.yardopts CHANGED
@@ -1,4 +1,4 @@
1
- --title "The JSS API Ruby Gem - access to the Casper Suite API"
1
+ --title "ruby-jss: access to the Jamf Pro 'Classic' API"
2
2
  --readme README.md
3
3
  --embed-mixins
4
4
  -
data/CHANGES.md CHANGED
@@ -1,35 +1,73 @@
1
1
  # Change History
2
2
 
3
- ## v 1.0.0b2, 2018-06-x26
3
+ ## v 1.0.0, 2018-08-10
4
+
5
+ Finally we're going to version 1.0, which we should have done when we went opensource. Future releases will try to adhere to [Semantic Versioning](https://semver.org/) as described in the [rubygems.org guidelines](https://guides.rubygems.org/patterns/#semantic-versioning)
6
+
7
+ **IMPORTANT** This version is not backward compatible with 0.x version. Please read the details below and test your code before deploying widely.
8
+
4
9
 
5
- Finally we're going to version 1.0, which we should have done when we went opensource.
6
10
 
7
11
  - requirement: Jamf Pro API version must be 10.4 or higher
8
12
 
13
+ - change: defaults to using TLSv1.2 for API connections.
14
+
15
+ As of Jamf Pro 10.5. the server requires TLSv1.2 and will not accept connections using TLSv1.
16
+
17
+ **COMPATIBILITY:**
18
+
19
+ MacOS 10.12 and lower have an old version of the openssl library which used by the built-in ruby (/usr/bin/ruby), which does not support TLSv1.2.
20
+
21
+ If you are using macOS 10.12 or lower to connect to Jamf Pro 10.4 (the lowest Jamf server supported by this version of ruby-jss), you must specify the older TLS when using APIConnection#connect, e.g.
22
+
23
+ `JSS.api.connect server: 'myjss.myschool.edu', user: 'username', pw: :prompt, ssl_version: :TLSv1`
24
+
25
+ Machines running macOS 10.12 or lower will not be able to connect to Jamf Pro > v10.4 with the built-in ruby openssl library. If you specify `ssl_version: :TLSv1` you will get an error because the server won't accept it. If you leave the default :TLSv1_2, ruby's openssl library will complain that it doesn't know about that.
26
+
27
+ If you have 10.12 or older machines that must connect to newer Jamf Pro servers with ruby-jss, there are a few options.
28
+
29
+ - upgade the machines to 10.13 or higher
30
+ - install a newer openssl, then install a your own ruby using that openssl (both can be done with homebrew)
31
+ - do the above, then extract the openssl library and modify it to work with the built-in ruby.
32
+
33
+ If you have questions about this, feel free to reach out to ruby-jss@pixar.com, or in the #ruby or #jss-api channels in MacAdmins slack space, for some advice.
34
+
9
35
  - add: JSS::PatchSource metaclass and the subclassses JSS::PatchInternalSource, and JSS::PatchExternalSource. These provide acecss to the patchavailabletitles enpoint, which is needed to acquire name_id's for creating/activating JSS::PatchTitles
10
36
 
11
- - add: JSS::PatchTitle, which also gives access to the patchreports endpoint. Also uses the JSS::PatchTitle::Version class to handle patch versions within a title, and assign packages to them.
37
+ - add: JSS::PatchTitle, which also gives access to the patchreports endpoint. Also uses the JSS::PatchTitle::Version class to handle patch versions within a title, and assign packages to them. **WARNING**: ruby-jss will now allow duplicate 'display names' (the #name of the JSS::PatchTitle instance) - but the Jamf Web UI will allow duplicates. If you have duplicates, and retrieve PatchTitles by name, which one gets returned to you is undefined.
38
+
39
+ - The 'source_id' and 'name_id' of a patch title are, when combined, a unique identifier in the JSS (i.e. name_id's are unique within sources) As such, the PatchTitle.all list provides a :source_name_id key, which is a String made by joining the two values with a '-', e.g. '1-GoogleChrome'. This value can be used as a lookup for .fetch, e.g. `JSS::PatchTitle.fetch source_name_id: '1-GoogleChrome'`. There is also a matching .all_source_name_ids method, and the .map_all_ids_to() method takes :source_name_id as a mapping parameter. You can also use .fetch and .make with both source: (id or name) and name_id: specified.
40
+
12
41
 
13
- - add: JSS::PatchPolicy. PatchPolicies are creatable when providing an active PatchTitle and an approiate PatchTitle::Version that has a package assigned to it.
42
+ - add: JSS::PatchPolicy. PatchPolicies are creatable when providing an active PatchTitle and an appropriate PatchTitle::Version that has a package assigned to it.
14
43
 
15
44
  - add: the Group metaclass now has a calculate_members option (bool) to the #create method. When true, the membership of the group will be updated in the existing ruby instance immediately after the group is created in the JSS. Doesn't do much for static groups, but is useful for smart groups. Defaults to true. If you don't care about the membership immediately, or don't want to wait for the membership to be calculated on the server, set this to false.
16
45
 
17
46
  - improvement: better handling of http error responses
18
47
 
48
+ - improvement: JSS::Policy objects now have setters for the 'Maintenence' subset, e.g. recons ('update inventory'), reset_name, fix_byhost, etc.
49
+
19
50
  - add: more generic data validation methods in JSS::Validate module, and more use of them throughout the code.
20
51
 
21
52
  - add: 'support' in SelfServable for notifications - note that there are API bugs limiting the usefulness of this.
22
53
 
23
54
  - add: regex options to JSS::Criteriable::Criterion objects
24
55
 
25
- - remove: the .new class method on APIObject subclasses no longer works. Even thought its the standard ruby way to create instances of a class, it was confusing, since it implied creating new objects in the JSS. Instead you must now use .fetch to instantiate existing objects and .make to instantiate local instances of objects to be created in the JSS.
56
+ - remove: the .new class method on APIObject subclasses no longer works. Even though it's the standard ruby way to create instances of a class, it was confusing, since it implied creating new objects in the JSS. Instead you must now use the .fetch class method to instantiate existing objects and the .make class method to instantiate local instances of objects to be created in the JSS.
57
+
58
+ **COMPATIBILITY:**
59
+
60
+ `existingcomp = JSS::Computer.new id: 1234` and `new_pol = JSS::Policy.new id: :new, name: 'mypolicy'` will now raise an error.
61
+
62
+ Instead you must use `existingcomp = JSS::Computer.fetch id: 1234` and `new_pol = JSS::Policy.make name: 'mypolicy'`
26
63
 
27
- - update: API connections now default to TLSv1.2. If you're connecting from a machine that doesn't support TLSv1.2 (like the os-supplied ruby in macOS < 10.13) then you can specify `ssl_version: 'TLSv1'` when you connect, as long as your Jamf Pro server supports it.
64
+ Note that the instance methods #create (create the current instance as a new object in the JSS) and #update (send changes in the current instance to the JSS) remain unchanged, and both continue handled by #save
28
65
 
29
- - add: there is now a, sort-of, spec/testing framework. While based on ruby's minitest specifications, its wrapped in a very custom executable with a helper module. See the README in the test directory for details. Specs will be added slowly over time.
66
+ - add: there is now a, sort-of, spec/testing framework. While based on ruby's minitest specifications, it's wrapped in a very custom executable with a helper module. See the README in the test directory for details. Specs will be added slowly over time.
30
67
 
31
- - fix: as Apple says: various bugfixes and improvements.
68
+ - add: JSS::Client now has a .management_action class method, which wraps around the 'Management Action.app' tool that comes with Jamf Pro and creates Notification Center notifications. At the moment support is minimal, and the notification type (alert vs. banner) is up to the User.
32
69
 
70
+ - misc: as Apple says: various bugfixes and improvements.
33
71
 
34
72
  ## v 0.14.0, 2018-05-30
35
73
 
data/README.md CHANGED
@@ -27,13 +27,13 @@
27
27
 
28
28
  ## DESCRIPTION
29
29
 
30
- The ruby-jss project provides a Ruby module called JSS, which is used for accessing the REST API of
30
+ ruby-jss defines a Ruby module called JSS, which is used for accessing the 'classic' REST API of
31
31
  the JAMF Software Server (JSS), the core of Jamf Pro, an enterprise-level management tool for Apple
32
32
  devices from [Jamf.com](http://www.jamf.com/). It is available as a
33
33
  [rubygem](https://rubygems.org/gems/ruby-jss), and the
34
34
  [source is on github](https://github.com/PixarAnimationStudios/ruby-jss).
35
35
 
36
- The module abstracts API resources as Ruby objects, and provides methods for interacting with those
36
+ The module abstracts many API resources as Ruby objects, and provides methods for interacting with those
37
37
  resources. It also provides some features that aren't a part of the API itself, but come with other
38
38
  Jamf-related tools, such as uploading .pkg and .dmg {JSS::Package} data to the master distribution
39
39
  point, and the installation of {JSS::Package} objects on client machines. (See [BEYOND THE API](#beyond-the-api))
@@ -47,7 +47,6 @@ Hopefully others will find it useful, and add more to it as well.
47
47
 
48
48
  [Full technical documentation can be found here.](http://www.rubydoc.info/gems/ruby-jss/)
49
49
 
50
-
51
50
  ## SYNOPSIS
52
51
 
53
52
  Here are some simple examples of using ruby-jss
@@ -157,8 +156,8 @@ Some classes can use more than just the :id and :name keys for lookups, e.g. com
157
156
 
158
157
  You can even fetch objects without specifying the kind of identifier, e.g. `JSS::Computer.fetch 3241`, but this will be slower, since ruby-jss searches by matching the given value with all available identifiers, returning the first match.
159
158
 
160
- *NOTE*: A class's '.fetch' method is now the preferred method to use for retrieving existing objects.
161
- The '.new' method still works as before, but is deprecated for object retrieval and doing so may raise errors in the future. See below for using .make to create new objects in the JSS.
159
+ *NOTE*: For APIObject subclasses, the '.fetch' class method is now the required method to use for retrieving existing objects
160
+ from the API. The '.new' method no longer works. See below for using .make to create new objects in the JSS.
162
161
 
163
162
  --------
164
163
 
@@ -185,7 +184,9 @@ Then use the #create method to create it in the JSS. The #save method is an alia
185
184
  new_pkg.create # returns 453, the id number of the object just created
186
185
  ```
187
186
 
188
- *NOTE*: A class's '.make' method is now the preferred method to use for creating new objects. The '.new id: :new' method still works as before, but is deprecated for object creation and doing so may raise errors in the future.
187
+ *NOTE*: For APIObject subclasses, the '.make' class method is now the required method to use for making ruby instances to be
188
+ created in the JSS. The '.new' method no longer works.
189
+
189
190
 
190
191
  --------
191
192
 
@@ -240,6 +241,7 @@ Here's what we've implemented so far. See each Class's [documentation(http://www
240
241
  * {JSS::ComputerExtensionAttribute}
241
242
  * {JSS::ComputerGroup}
242
243
  * {JSS::Department}
244
+ * {JSS::DistributionPoint}
243
245
  * {JSS::MobileDeviceApplication}
244
246
  * {JSS::MobileDeviceExtensionAttribute}
245
247
  * {JSS::MobileDeviceGroup}
@@ -258,6 +260,10 @@ Here's what we've implemented so far. See each Class's [documentation(http://www
258
260
  * {JSS::Computer}
259
261
  * {JSS::MobileDevice}
260
262
  * {JSS::Policy} (still not fully implemented)
263
+ * {JSS::PatchInternalSource}
264
+ * {JSS::PatchExternalSource}
265
+ * {JSS::PatchTitle}
266
+ * {JSS::PatchPolicy}
261
267
 
262
268
  **NOTE** Computer and Mobile Device data gathered by an Inventory Upate (a.k.a. 'recon') is not editable.
263
269
 
@@ -368,26 +374,6 @@ While the Jamf Pro API provides access to object data in the JSS, this gem tries
368
374
  * Extension Attributes
369
375
  * {JSS::ExtensionAttribute} work with {JSS::AdvancedSearch} subclasses to provide extra reporting about Ext. Attrib. values.
370
376
 
371
- ## REQUIREMENTS
372
-
373
- ruby-jss was written for:
374
-
375
- * Mac OS X 10.9 or higher
376
- * Ruby 2.0 or higher
377
- * Casper Suite version 9.4 or higher
378
-
379
- It also requires these gems, which will be installed automatically if you install JSS with `gem install jss`
380
-
381
- * rest-client >=1.6.7 ( >= 1.7.0 with Casper >= 9.6.1) http://rubygems.org/gems/rest-client
382
- * json or json\_pure >= 1.6.5 http://rubygems.org/gems/json or http://rubygems.org/gems/json_pure
383
- * (only in ruby 1.8.7. Ruby >= 1.9 has json in its standard library)
384
- * ruby-mysql >= 2.9.12 http://rubygems.org/gems/ruby-mysql
385
- * (only for a few things that still require direct SQL access to the JSS database)
386
- * plist =3.1.0 http://rubygems.org/gems/plist
387
- * for the {JSS::Composer} module and {JSS::Client} class
388
- * net-ldap >= 0.3.1 http://rubygems.org/gems/net-ldap
389
- * for accessing the LDAP servers defined in the JSS, to check for user and group info.
390
-
391
377
  ## INSTALL
392
378
 
393
379
  NOTE: You may need to install XCode, and it's CLI tools, in order to install the required gems.
@@ -396,10 +382,23 @@ In general, you can install ruby-jss with this command:
396
382
 
397
383
  `gem install ruby-jss`
398
384
 
385
+ ## REQUIREMENTS
386
+
387
+ ruby-jss was written for:
388
+
389
+ * Mac OS X 10.9 or higher
390
+ * Ruby 2.0 or higher
391
+ * Casper Suite version 10.4 or higher
392
+
393
+ It also requires other ruby gems, which will be installed automatically if you install with `gem install ruby-jss`
394
+ See the .gemspec file for details
395
+
399
396
 
400
397
  ## HELP
401
398
 
402
- Full documentation is available at [rubydoc.info](http://www.rubydoc.info/gems/ruby-jss/)
399
+ Full documentation is available at [rubydoc.info](http://www.rubydoc.info/gems/ruby-jss/).
400
+
401
+ There's a [wiki on the github page](https://github.com/PixarAnimationStudios/ruby-jss/wiki), feel free to contribute examples and tidbits.
403
402
 
404
403
  [Email the developers](mailto:ruby-jss@pixar.com)
405
404
 
data/bin/cgrouper CHANGED
@@ -207,9 +207,9 @@ class App
207
207
 
208
208
  # get the group from the API
209
209
  if @options.action == :create_group
210
- @group = JSS::ComputerGroup.new :id => :new, :name => @options.group, :type => :static
210
+ @group = JSS::ComputerGroup.make :name => @options.group, :type => :static
211
211
  else
212
- @group = JSS::ComputerGroup.new :name => @options.group
212
+ @group = JSS::ComputerGroup.fetch :name => @options.group
213
213
  end
214
214
 
215
215
  end # if ACTIONS_NEEDING_GROUP
@@ -40,8 +40,6 @@
40
40
  # == Author
41
41
  # Chris Lasell <chrisl@pixar.com>
42
42
  #
43
- # == Copyright
44
- # Copyright (c) 2017 Pixar Animation Studios
45
43
  ##############################
46
44
 
47
45
  require 'ruby-jss'
data/bin/netseg-update CHANGED
@@ -322,8 +322,7 @@ Notes:
322
322
  end # if noop
323
323
 
324
324
  ender = @use_cidr ? :cidr : :ending_address
325
- new_seg = JSS::NetworkSegment.new(
326
- :id => :new,
325
+ new_seg = JSS::NetworkSegment.make(
327
326
  :name => seg,
328
327
  :starting_address => seg_data[:starting],
329
328
  ender => seg_data[:ending]
@@ -339,7 +338,7 @@ Notes:
339
338
  puts "Without --no-op this would: Delete segment named '#{seg}',"
340
339
  next
341
340
  end # if noop
342
- JSS::NetworkSegment.new(name: seg).delete
341
+ JSS::NetworkSegment.fetch(name: seg).delete
343
342
  puts "Deleted Network Segment '#{seg}' from the JSS"
344
343
  end # @segments_to_delete.each do |seg|
345
344
  end # delete_segments
@@ -354,7 +353,7 @@ Notes:
354
353
  IPAddr.new(seg_data[:ending])
355
354
  end
356
355
 
357
- this_seg = JSS::NetworkSegment.new name: seg
356
+ this_seg = JSS::NetworkSegment.fetch name: seg
358
357
  data_range = data_start..data_end
359
358
  next if this_seg.range == data_range
360
359
 
@@ -57,7 +57,7 @@ api_server_port:
57
57
  ### The SSL version to use for the API connection
58
58
  ###
59
59
  ### If you leave this blank:
60
- ### Defaults to 'TLSv1'
60
+ ### Defaults to 'TLSv1_2'
61
61
  ###
62
62
  api_ssl_version:
63
63
 
@@ -75,14 +75,8 @@ api_verify_cert:
75
75
  ### - api_username
76
76
  ### The username to use when making an API connection.
77
77
  ###
78
- ### *** REQUIRED ***
79
- ###
80
78
  ### Note that the passwords are not storeable here!!
81
- ### see https://github.com/PixarAnimationStudios/ruby-jss/blob/master/README.md#passwords
82
- ###
83
- ### If you leave this blank:
84
- ### d3 may break, as well as any other code that
85
- ### expects a configured api user name
79
+ ### see https://github.com/PixarAnimationStudios/ruby-jss/blob/master/README.md#password
86
80
  ###
87
81
  api_username:
88
82
 
@@ -336,6 +336,7 @@ module JSS
336
336
 
337
337
  # @return [Boolean] are we connected right now?
338
338
  attr_reader :connected
339
+ alias connected? connected
339
340
 
340
341
  # @return [JSS::Server] the details of the JSS to which we're connected.
341
342
  attr_reader :server
@@ -509,14 +510,19 @@ module JSS
509
510
  def get_rsrc(rsrc, format = :json)
510
511
  # puts object_id
511
512
  validate_connected
513
+
512
514
  raise JSS::InvalidDataError, 'format must be :json or :xml' unless %i[json xml].include? format
513
515
 
516
+ # TODO: fix what rubocop is complaining about in the line below.
517
+ # (I doubt we want to CGI.escape the whole resource)
514
518
  rsrc = URI.encode rsrc
515
519
  begin
516
520
  @last_http_response = @cnx[rsrc].get(accept: format)
517
521
  rescue RestClient::ExceptionWithResponse => e
518
522
  handle_http_error e
519
523
  end
524
+ # TODO: make sure we're returning the String version of the
525
+ # response (i.e. its body) here and in POST, PUT, DELETE.
520
526
  format == :json ? JSON.parse(@last_http_response, symbolize_names: true) : @last_http_response
521
527
  end
522
528
 
@@ -625,9 +631,6 @@ module JSS
625
631
  srvr ||= JSS::Client.jss_server
626
632
  srvr
627
633
  end
628
-
629
- # aliases
630
- alias connected? connected
631
634
  alias host hostname
632
635
 
633
636
  #################
@@ -210,7 +210,7 @@ module JSS
210
210
  @api.timeout = 1800
211
211
  @api.open_timeout = 1800
212
212
  begin
213
- requery = self.class.new(id: @id)
213
+ requery = self.class.fetch(id: @id)
214
214
  @search_results = requery.search_results
215
215
  @result_display_keys = requery.result_display_keys
216
216
  ensure
@@ -172,7 +172,7 @@ module JSS
172
172
  def create
173
173
  new_invitation_id = super
174
174
 
175
- jss_me = ComputerInvitation.new(id: new_invitation_id, name: 'set_by_request')
175
+ jss_me = ComputerInvitation.fetch(id: new_invitation_id, name: 'set_by_request')
176
176
  @name = jss_me.name
177
177
  @invitation_type = jss_me.invitation_type
178
178
  @create_account_if_does_not_exist = jss_me.create_account_if_does_not_exist
@@ -81,7 +81,7 @@ module JSS
81
81
  ### crta = JSS::Criteriable::Criteria.new [crtn_0, crtn_1]
82
82
  ###
83
83
  ### # create a new Advanced Search
84
- ### srch = JSS::AdvancedComputerSearch.new :id => :new, :name => "my computer search"
84
+ ### srch = JSS::AdvancedComputerSearch.make, :name => "my computer search"
85
85
  ### srch.display_fields = ["Computer Name"]
86
86
  ###
87
87
  ### # add our Criteria to it
@@ -1,3 +1,28 @@
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
+
1
26
  # This is just a stub for now.
2
27
 
3
28
  #
@@ -106,7 +106,7 @@ module JSS
106
106
  # Constructor
107
107
  #####################################
108
108
 
109
- # When creating a new group in the JSS, you must call .new with a :type key
109
+ # When creating a new group in the JSS, you must call .make with a :type key
110
110
  # and a value of :smart or :static, as well as a :name and the :id => :new
111
111
  #
112
112
  # @see JSS::APIObject
@@ -424,7 +424,7 @@ module JSS
424
424
  #
425
425
  def upload_ipa(path)
426
426
  new_ipa = Pathname.new path
427
- upload(:app, new_icon)
427
+ upload(:app, new_ipa)
428
428
  refresh_ipa
429
429
  end
430
430
 
@@ -36,8 +36,20 @@ module JSS
36
36
  # attribute, a Hash of versions keyed by the version string. The values are
37
37
  # JSS::PatchTitle::Version objects.
38
38
  #
39
- # Use the patch_report method on the PatchTitle class, an instance of it, or
40
- # a PatchTitle::Version, to retrieve a report of computers with a
39
+ # When creating/activating new Patch Titles, with .make, a unique name:, a
40
+ # source: and a name_id: must be provided - the source must be the name or id
41
+ # of an existing PatchSource, and the name_id must be offered by that source.
42
+ # Once created, the source_id and name_id cannot be changed.
43
+ #
44
+ # When fetching titles, they can be fetched by id:, source_name_id:, or both
45
+ # source: and name_id:
46
+ #
47
+ # WARNING: While they can be fetched by name, beware: the JSS does not enforce
48
+ # unique names of titles even thought ruby-jss does. If there are duplicates
49
+ # of the name you fetch, which one you get is undefined.
50
+ #
51
+ # Use the patch_report class or instance method, or
52
+ # PatchTitle::Version.patch_report, to retrieve a report of computers with a
41
53
  # specific version of the title installed, or :all, :latest, or :unknown
42
54
  # versions. Reports called on the class or an instance default to :all
43
55
  # versions, and are slower to retrieve than a specific version,
@@ -152,7 +164,10 @@ module JSS
152
164
  # source_id: parameter, which limites the results to
153
165
  # patch titles with the specified source_id.
154
166
  #
155
- # ALSO, JAMF BUG: More broken json - the id is coming as a string.
167
+ # Also - since the combined source_id and name_id are unique, create an
168
+ # identifier key ':source_name_id' by joining them with '-'
169
+ #
170
+ # JAMF BUG: More broken json - the id is coming as a string.
156
171
  # so here we turn it into an integer manually :-(
157
172
  # Ditto for source_id
158
173
  #
@@ -160,6 +175,7 @@ module JSS
160
175
  data = super refresh, api: api
161
176
  data.each do |info|
162
177
  info[:id] = info[:id].to_i
178
+ info[:source_name_id] = "#{info[:source_id]}-#{info[:name_id]}"
163
179
  info[:source_id] = info[:source_id].to_i
164
180
  end
165
181
  return data unless source_id
@@ -182,12 +198,6 @@ module JSS
182
198
  all(refresh, source_id: source_id, api: api).map { |i| i[:id] }
183
199
  end
184
200
 
185
- # @return [Array<String>] all 'name_id' values for active patches
186
- #
187
- def self.all_name_ids(refresh = false, source_id: nil, api: JSS.api)
188
- all(refresh, source_id: source_id, api: api).map { |i| i[:name_id] }
189
- end
190
-
191
201
  # Returns an Array of unique source_ids used by active Patches
192
202
  #
193
203
  # e.g. if there are patches that come from one internal source
@@ -207,6 +217,12 @@ module JSS
207
217
  all(refresh, api: api).map { |i| i[:source_id] }.sort.uniq
208
218
  end
209
219
 
220
+ # @return [Array<String>] all 'source_name_id' values for active patches
221
+ #
222
+ def self.all_source_name_ids(refresh = false, api: JSS.api)
223
+ all(refresh, api: api).map { |i| i[:source_name_id] }
224
+ end
225
+
210
226
  # Get a patch report for a softwaretitle, withouth fetching an instance.
211
227
  # Defaults to reporting all versions. Specifiying a version will be faster.
212
228
  #
@@ -280,18 +296,50 @@ module JSS
280
296
  end
281
297
  private_class_method :patch_report_rsrc
282
298
 
283
- # for some reason, patch titles can't be fetched by name.
284
- # only by id. SO, look up the id if given a name.
299
+ # Patch titles only have an id-based GET resource in the API.
300
+ # so all other lookup values have to be converted to ID before
301
+ # the call to super
285
302
  #
286
- def self.fetch(id: nil, name: nil, api: JSS.api)
287
- unless id
288
- id = JSS::PatchTitle.map_all_ids_to(:name).invert[name]
289
- raise NoSuchItemError, "No matching #{self::RSRC_OBJECT_KEY} found" unless id
290
- end
303
+ def self.fetch(identifier = nil, **params)
304
+ # default api
305
+ api = params[:api] ? params[:api] : JSS.api
306
+
307
+ # source: and source_id: are considered the same, source_id: wins
308
+ params[:source_id] ||= params[:source]
309
+
310
+ # if given a source name, this converts it to an id
311
+ params[:source_id] = JSS::PatchSource.valid_id params[:source_id]
312
+
313
+ # build a possible source_name_id
314
+ params[:source_name_id] ||= "#{params[:source_id]}-#{params[:name_id]}"
315
+
316
+ id =
317
+ if identifier
318
+ valid_id identifier
319
+ elsif params[:id]
320
+ all_ids.include?(params[:id]) ? params[:id] : nil
321
+ elsif params[:source_name_id]
322
+ map_all_ids_to(:source_name_id).invert[params[:source_name_id]]
323
+ elsif params[:name]
324
+ map_all_ids_to(:name).invert[params[:name]]
325
+ end
326
+
327
+ raise JSS::NoSuchItemError, "No matching #{name} found" unless id
291
328
 
292
329
  super id: id, api: api
293
330
  end
294
331
 
332
+ # Override the {APIObject.valid_id}, since patch sources are so non-standard
333
+ # Accept id, source_name_id, or name.
334
+ # Note name may not be unique, and if not, ymmv
335
+ #
336
+ def self.valid_id(ident, refresh = false, api: JSS.api)
337
+ id = all_ids(refresh, api: api).include?(ident) ? ident : nil
338
+ id ||= map_all_ids_to(:source_name_id).invert[ident]
339
+ id ||= map_all_ids_to(:name).invert[ident]
340
+ id
341
+ end
342
+
295
343
  # Attributes
296
344
  #####################################
297
345
 
@@ -303,6 +351,9 @@ module JSS
303
351
  # for this title
304
352
  attr_reader :source_id
305
353
 
354
+ # @return [String] the source_id and name_id joined by '-', a unique identifier
355
+ attr_reader :source_name_id
356
+
306
357
  # @return [Boolean] Are new patches announced in the JSS web ui?
307
358
  attr_reader :web_notification
308
359
  alias web_notification? web_notification
@@ -311,17 +362,31 @@ module JSS
311
362
  attr_reader :email_notification
312
363
  alias email_notification? email_notification
313
364
 
314
- # @return [Hash{String => JSS::PatchTitle::Version}] The JSS::PatchVersions fetched for
315
- # this title, keyed by version string
316
- attr_reader :versions
317
-
318
- # PatchTitles may be fetched by name: or id:
319
365
  #
320
366
  def initialize(**args)
321
367
  super
322
368
 
323
- @name_id = @init_data[:name_id]
324
- @source_id = @init_data[:source_id]
369
+ if in_jss
370
+ @name_id = @init_data[:name_id]
371
+ @source_id = @init_data[:source_id]
372
+ else
373
+ # source: and source_id: are considered the same, source_id: wins
374
+ @init_data[:source_id] ||= @init_data[:source]
375
+
376
+ raise JSS::MissingDataError, 'source: and name_id: must be provided' unless @init_data[:name_id] && @init_data[:source_id]
377
+
378
+ @source_id = JSS::PatchSource.valid_id(@init_data[:source_id])
379
+
380
+ raise JSS::NoSuchItemError, "No Patch Sources match '#{@init_data[:source]}'" unless source_id
381
+
382
+ @name_id = @init_data[:name_id]
383
+
384
+ valid_name_id = JSS::PatchSource.available_name_ids(@source_id).include? @name_id
385
+
386
+ raise JSS::NoSuchItemError, "source #{@init_data[:source]} doesn't offer name_id '#{@init_data[:name_id]}'" unless valid_name_id
387
+ end
388
+
389
+ @source_name_id = "#{@source_id}-#{@name_id}"
325
390
 
326
391
  @init_data[:notifications] ||= {}
327
392
  notifs = @init_data[:notifications]
@@ -337,6 +402,16 @@ module JSS
337
402
  @changed_pkgs = []
338
403
  end
339
404
 
405
+ # @return [Hash{String => JSS::PatchTitle::Version}] The JSS::PatchVersions fetched for
406
+ # this title, keyed by version string
407
+ def versions
408
+ return @versions unless in_jss
409
+ return @versions unless @versions.empty?
410
+ # if we are in jss, and versions is empty, re-fetch them
411
+ @versions = self.class.fetch(id: id).versions
412
+ end
413
+
414
+
340
415
  # @return [Hash] Subset of @versions, containing those which have packages
341
416
  # assigned
342
417
  #
@@ -376,33 +451,14 @@ module JSS
376
451
  @need_to_update = true
377
452
  end
378
453
 
379
- def source_id=(new_id)
380
- sid = JSS::PatchSource.valid_patch_source_id new_id
381
- raise JSS::NoSuchItemError, "No active Patch Sources matche '#{new_id}'" unless sid
382
- return if sid == source_id
383
- @source_id = sid
384
- @need_to_update = true
385
- end
386
-
387
- def name_id=(new_id)
388
- return if new_id == name_id
389
- raise JSS::MissingDataError, 'source_id must be set before setting name_id' if source_id.to_s.empty?
390
- raise JSS::NoSuchItemError, "source_id #{source_id} doesn't offer name_id '#{new_id}'" unless JSS::PatchSource.available_name_ids(source_id).include? new_id
391
- @name_id = new_id
392
- @need_to_update = true
393
- end
394
-
395
454
  # wrapper to fetch versions after creating
396
455
  def create
397
- validate_for_saving
398
456
  response = super
399
- @versions = self.class.fetch(id: id).versions
400
457
  response
401
458
  end
402
459
 
403
460
  # wrapper to clear @changed_pkgs after updating
404
461
  def update
405
- validate_for_saving
406
462
  response = super
407
463
  @changed_pkgs.clear
408
464
  response
@@ -433,10 +489,6 @@ module JSS
433
489
  #################################
434
490
  private
435
491
 
436
- def validate_for_saving
437
- raise JSS::MissingDataError, 'PatchTitles must have valid source_id and name_id' if source_id.to_s.empty? || name_id.to_s.empty?
438
- end
439
-
440
492
  # Return the REST XML for this title, with the current values,
441
493
  # for saving or updating.
442
494
  #