ruby-jss 1.1.3 → 1.2.0b1

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
2
  SHA256:
3
- metadata.gz: 90a0bdaae94b5d0d1cb5ccad691ecf57daa75e205ceda668813655484ee232c8
4
- data.tar.gz: 6c8e0211b2397df59d576b8fec2901f22003dd2d562c7a755209ebd0026493ca
3
+ metadata.gz: 2aa99b8b7dcfc161d2251f1af4b6efa5d302a575bcadc5feb843536252acf98b
4
+ data.tar.gz: cb691744d29c3d9ff178cd56d33dcab35f7a5a00ddcdc831b6ade3f33209496e
5
5
  SHA512:
6
- metadata.gz: de40656a561b6624eaa28b4c851314de7bb4a84e151f656d1c021a784255c5c95ce76b2e7013553cc02f3094707934481e05059bf931b3fd9c659eedb8e05415
7
- data.tar.gz: 8fdef7af00dc52b1ce253288415eade80d8d3812ed5470ad84f0e3c9292c95e32b1080b7c56e7457ce21e927bb0782e63124df1bb5efa2423e95694c620a148e
6
+ metadata.gz: 3fc08c5a37bd84d8077b15827357918e2d1b901b85f5341099540941cff8001ffcbeb0242a2e133e1909efd0fc14b6910e92b8f5d03660344129dd1404485f24
7
+ data.tar.gz: 6548b0c75545dda10280d8200cb2496799dcf015304c2e5d32da3db34591f2ee02e53c7e6bb15f6138628744ee16cb87760f8215ced4f19a7325a07683dfe505
data/CHANGES.md CHANGED
@@ -4,43 +4,94 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## \[1.2.0] - Unreleased
8
+ ### Added
9
+ - APIConnection#flushcache can be used to flush all cached data, or just for specific APIObject lists or ExtensionAttribute definitions. This is now used more often throughout ruby-jss.
10
+
11
+ ### Fixed
12
+ - Group.all_static was returning all_smart, now actually returns all_static
13
+
14
+ - Fix in error message raised in Group.change_membership
15
+
16
+ - APIConnection#connect now flushes all cached data, so if you use it to change which server is being used by an existing connection, you don't keep the cached data from the old server.
17
+
18
+ - PatchPolicy now overrides APIObject.fetch to still allow for fetching by name, even there's no patchpolicies/name/... endpoint
19
+
20
+ ### Changed
21
+ - Group#create with `calculate_members: true`, will sometimes try to re-read the new group before the JSS knows it exists, causing a `404 Not Found` error. This is especially common when using a clustered environment behind a load-balanced hostname (like _something.jamfcloud.com_).
22
+ Now, if a 404 happens when trying to refresh the membership using calculate_members, ruby-jss will retry every second for up to 10 seconds.
23
+ To change the number of retries, provide an integer with the `retries:` parameter. If you don't need to know the group members after creation, pass `calculate_members: false` when calling Group#create or Group#save
24
+
25
+ - Creating, Updating, or Deleting objects of the APIObject subclasses now flushes the cached `all` lists for that class, so subseqent uses of the `all` lists will refresh the data from the API. This means that `APIObject.valid_id` will work immediately upon object creation. NOTE: However, when using a clustered environment behind a load-balanced hostname (like jamfcloud.com), it may take some time for the `all` list to update on all nodes of the cluster, so you might still need to pause up to the number of seconds defined of the cluster's sync interval to ensure valid lists.
26
+
27
+ - Case-insentive lookup & validation methods in APIObject now use `String#casecmp?` for much simpler code
28
+
29
+ - Creatable#create no longer takes an `api:` parameter (it never should have) The API connection given in #make is always used for creating the object in the API.
30
+
31
+ - Added class JSS::IBeacon, implementing the .../ibeacons/... endpoints
32
+
7
33
  ## \[1.1.3] - 2019-09-23
8
34
  ### Added
9
35
  - MobileDeviceExtensionAttribute now has a `.history` class method matching that of ComputerExtensionAttribute. Requires direct MySQL database access. Thanks @aurica!
36
+
10
37
  - JSS::AmbiguousError exception class
38
+
11
39
  - More caching of API data to improve general speed
12
40
  - The hashes created by `APIObject.map_all_ids_to(blah)`
13
41
  - ExtensionAttribute definitions when used by extendable classes
42
+
14
43
  - APIObject.fetch can take the search term `:random` and you'll get a randomly selected object. Example: `a_random_computer = JSS::Computer.fetch :random`
44
+
15
45
  - Keys of the hash returned by `Computer#hardware` are now available as instance methods on Computer objects. So as well as `a_computer.hardware[:total_ram]` you can also do `a_computer.total_ram`
46
+
16
47
  - Policy now recognizes the frequency Symbol `:once_per_user_per_computer`
48
+
17
49
  - Attribute reader :management_status added to Computer class
50
+
18
51
  - Implemented some useful String methods from newer versions of Ruby into older Rubies: `casecmp?`, `delete_prefix`, & `delete_suffix`
52
+
19
53
  - master_distribution_point class method in APIConnection & DistribtutionPoint now raise an error when no dist. point is 'master'
20
54
  - The error states that the cloud dist. point may be the master, and there's no classic API access to it.
21
55
 
22
56
 
23
57
  ### Fixed
24
58
  - Can't Modify Frozen Hash error when instantiating JSS::Scopbable::Scope. Thanks to @shahn for reporting this one.
59
+
25
60
  - MobileDeviceExtensionAttribute now handles empty `as_of` timestamp. Thanks @aurica!
61
+
26
62
  - A few typos. Thanks to @cybertunnel for finding some.
63
+
27
64
  - A bug when parsing the `server_path` parameter to `API::Connection.new`
65
+
28
66
  - Bugs in handling blank values in Policy#search_by_path and Policy#printer_ids. Thanks @cybertunnel
67
+
29
68
  - Computer.management_data with a specified subset returned one level too high in the data structure
69
+
30
70
  - NetworkSegment.my_network_segment: error in number of params passed to other methods
71
+
31
72
  - Script#name= now works again, no longer uses a constant from an ancient version. Thanks @shahn
73
+
32
74
  - Computer#asset_tag= now accepts nil to erase the value
75
+
33
76
  - APIConnection.my_distribution_point & DistributionPoint.my_distribution_point now return the master_distribution_point object if there isn't one assigned to the current network segment.
77
+
34
78
  - RestClient no longer warns about calling 'to_i' on Responses when calling APIConnection#put_rsrc & #post_rsrc
35
79
 
36
80
  ### Changed
37
81
  - Monkey Patches are being moved to a better, more traceable technique, see https://www.justinweiss.com/articles/3-ways-to-monkey-patch-without-making-a-mess/
82
+
38
83
  - MobileDevices and Computers now raise JSS::AmbiguousError when being fetched by explicitly by name, e.g. `JSS::Computer.fetch name: 'foo'` and that name is not unique in the JSS. Previously, you'd get an object back, but no guarantee as to which one it was. You'll still get an undefined object if you use a bare searchterm, e.g. `JSS::Computer.fetch 'foo'`
84
+
39
85
  - Documentation for subclassing APIObject is updated & expanded. See the comments above the class definition in api_object.rb
86
+
40
87
  - `APIObject.valid_id` is now case-insensitive
88
+
41
89
  - Removed deprecated VALID_DATA_KEYS constants from APIObject subclasses
90
+
42
91
  - Various changes in APIObject and its subclasses to try making `.fetch` and other lookup-methods faster.
92
+
43
93
  - All of the NetworkSegment-related methods in APIConnection have been moved back to NetworkSegment. The methods in APIConnection still work, but are marked deprecated and will go away eventually.
94
+
44
95
  - Removed last call to deprecated `URI.encode`, replaced with `CGI.escape`
45
96
 
46
97
 
@@ -324,6 +324,9 @@ module JSS
324
324
 
325
325
  RSRC_NOT_FOUND_MSG = 'The requested resource was not found'.freeze
326
326
 
327
+ # These classes are extendable, and may need cache flushing for EA definitions
328
+ EXTENDABLE_CLASSES = [JSS::Computer, JSS::MobileDevice, JSS::User].freeze
329
+
327
330
  # Attributes
328
331
  #####################################
329
332
 
@@ -420,10 +423,8 @@ module JSS
420
423
  #
421
424
  def initialize(args = {})
422
425
  @name = args.delete :name
423
- @name ||= :disconnected
426
+ @name ||= :unknown
424
427
  @connected = false
425
- @object_list_cache = {}
426
- @ext_attr_definition_cache = {}
427
428
  connect args unless args.empty?
428
429
  end # init
429
430
 
@@ -467,6 +468,9 @@ module JSS
467
468
  # @return [true]
468
469
  #
469
470
  def connect(args = {})
471
+ # new connections always get new caches
472
+ flushcache
473
+
470
474
  args[:no_port_specified] = args[:port].to_s.empty?
471
475
  args = apply_connection_defaults args
472
476
 
@@ -966,15 +970,33 @@ module JSS
966
970
  # Empty all cached lists from this connection
967
971
  # then run garbage collection to clear any available memory
968
972
  #
973
+ # If an APIObject Subclass's RSRC_LIST_KEY is specified, only the caches
974
+ # for that class are flushed (e.g. :computers, :comptuer_groups)
975
+ #
969
976
  # NOTE if you've referenced objects in these caches, those objects
970
977
  # won't be removed from memory, but all cached data will be recached
971
978
  # as needed.
972
979
  #
980
+ # @param key[Symbol, Class] Flush only the caches for the given RSRC_LIST_KEY. or
981
+ # the EAdef cache for the given extendable class. If nil (the default)
982
+ # flushes all caches
983
+ #
973
984
  # @return [void]
974
985
  #
975
- def flushcache
976
- @object_list_cache = {}
977
- @ext_attr_definition_cache = {}
986
+ def flushcache(key = nil)
987
+ if EXTENDABLE_CLASSES.include? key
988
+ @ext_attr_definition_cache[key] = {}
989
+ elsif key
990
+ map_key_pfx = "#{key}_map_"
991
+ @object_list_cache.delete_if do |cache_key, _cache|
992
+ cache_key == key || cache_key.to_s.start_with?(map_key_pfx)
993
+ end
994
+ @ext_attr_definition_cache
995
+ else
996
+ @object_list_cache = {}
997
+ @ext_attr_definition_cache = {}
998
+ end
999
+
978
1000
  GC.start
979
1001
  end
980
1002
 
@@ -1001,7 +1023,7 @@ module JSS
1001
1023
 
1002
1024
  # raise exception if not connected
1003
1025
  def validate_connected
1004
- raise JSS::InvalidConnectionError, 'Not Connected. Use .connect first.' unless connected?
1026
+ raise JSS::InvalidConnectionError, "Connection '#{@name}' Not Connected. Use .connect first." unless connected?
1005
1027
  end
1006
1028
 
1007
1029
  # Apply defaults from the JSS::CONFIG,
@@ -1258,8 +1280,8 @@ module JSS
1258
1280
  # @return [APIConnection] the new, active connection
1259
1281
  #
1260
1282
  def self.new_api_connection(args = {})
1283
+ args[:name] ||= :default
1261
1284
  @api = APIConnection.new args
1262
- @api
1263
1285
  end
1264
1286
 
1265
1287
  # Switch the connection used for all API interactions to the
@@ -1282,7 +1304,7 @@ module JSS
1282
1304
  # @return [void]
1283
1305
  #
1284
1306
  def self.use_default_connection
1285
- use_api_connection API
1307
+ use_api_connection @api
1286
1308
  end
1287
1309
 
1288
1310
  # The currently active JSS::APIConnection instance.
@@ -1290,7 +1312,7 @@ module JSS
1290
1312
  # @return [JSS::APIConnection]
1291
1313
  #
1292
1314
  def self.api
1293
- @api
1315
+ @api ||= APIConnection.new name: :default
1294
1316
  end
1295
1317
 
1296
1318
  # aliases of module methods
@@ -1310,7 +1332,7 @@ module JSS
1310
1332
  end
1311
1333
 
1312
1334
  # create the default connection
1313
- new_api_connection(name: :default) unless @api
1335
+ new_api_connection unless @api
1314
1336
 
1315
1337
  # Save the default connection in the API constant,
1316
1338
  # mostly for backward compatibility.
@@ -345,7 +345,7 @@ module JSS
345
345
  no_aliases ? @lookup_keys.values.uniq : @lookup_keys
346
346
  end
347
347
 
348
- # Given a lookup or, or an alias of one, return the matching fetch_rsrc_key
348
+ # Given a lookup key, or an alias of one, return the matching fetch_rsrc_key
349
349
  # for building a fetch/GET resource URL, or nil if no fetch_rsrc_key is defined.
350
350
  #
351
351
  # See {OTHER_LOOKUP_KEYS} in the APIObject class comments/docs above for details.
@@ -452,7 +452,7 @@ module JSS
452
452
 
453
453
  cache = api.object_list_cache
454
454
  cache_key = self::RSRC_LIST_KEY
455
- cache[cache_key] = nil if refresh
455
+ api.flushcache(cache_key) if refresh
456
456
  return cache[cache_key] if cache[cache_key]
457
457
 
458
458
  cache[cache_key] = api.get_rsrc(self::RSRC_BASE)[cache_key]
@@ -582,6 +582,7 @@ module JSS
582
582
  # @return [Integer, nil] the id of the matching object, or nil if it doesn't exist
583
583
  #
584
584
  def self.valid_id(identifier, refresh = false, api: JSS.api)
585
+
585
586
  # refresh if needed
586
587
  all(refresh, api: api) if refresh
587
588
 
@@ -591,26 +592,11 @@ module JSS
591
592
  keys_to_check = lookup_keys(no_aliases: true)
592
593
  keys_to_check.delete :id # we've already checked :id
593
594
 
594
- # downcase for speedy case-insensitivity -
595
- # include?, and I assume value?, is faster with downcasing. See
596
- # https://stackoverflow.com/questions/9333952/case-insensitive-arrayinclude/9334066#9334066
597
- identifier.downcase! if identifier.is_a? String
598
-
599
595
  keys_to_check.each do |key|
600
596
  mapped_ids = map_all_ids_to key, api: api
601
- # downcase - see comment above
602
- mapped_ids.each { |_k, v| v.downcase! if v.is_a? String }
603
-
604
- # if name is not unique, skip to the next key, there is no
605
- # valid id for a non-unique name
606
- if key == :name
607
- num_name_matches = mapped_ids.values.select { |n| n == identifier }.size
608
- next unless num_name_matches == 1
609
- else
610
- next unless mapped_ids.value? identifier
611
- end
612
-
613
- return mapped_ids.invert[identifier]
597
+ matches = mapped_ids.select { |_id, ident| ident.casecmp? identifier }
598
+ # If exactly one match, return the id
599
+ return matches.keys.first if matches.size == 1
614
600
  end
615
601
 
616
602
  nil
@@ -627,8 +613,10 @@ module JSS
627
613
  #
628
614
  # # => the Integer id, or nil if no such serial number
629
615
  #
630
- # Raises a JSS::Ambiguous error if NON_UNIQUE_NAMES is set and
631
- # a :name isn't unique
616
+ # Raises a JSS::Ambiguous error if there's more than one matching value
617
+ # for any key, which might be true of names for Computers and Devices
618
+ #
619
+ # This is similar to .valid_id, except only one key is searched
632
620
  #
633
621
  # @param key [Symbol] they key in which to look for the identifier. Must be
634
622
  # a valid lookup key for this subclass.
@@ -645,24 +633,23 @@ module JSS
645
633
  # @return [Integer, nil] the id of the matching object, or nil if it
646
634
  # doesn't exist
647
635
  #
648
- def self.id_for_identifier(key, ident, refresh = false, api: JSS.api)
636
+ def self.id_for_identifier(key, val, refresh = false, api: JSS.api)
649
637
  # refresh if needed
650
638
  all(refresh, api: api) if refresh
651
639
 
652
640
  # get the real key if an alias was used
653
641
  key = real_lookup_key key
654
642
 
655
- return all_ids.include?(ident) ? ident : nil if key == :id
656
-
657
- validate_unique_name(ident) if key == :name
643
+ # do id's expicitly, they are integers
644
+ return all_ids.include?(val) ? val : nil if key == :id
658
645
 
659
- # downcase for speed
660
- ident.downcase! if ident.is_a? String
661
646
  mapped_ids = map_all_ids_to key, api: api
662
- mapped_ids.each { |_k, v| v.downcase! if v.is_a? String }
663
- return nil unless mapped_ids.value? ident
647
+ matches = mapped_ids.select { |_id, map_val| val.casecmp? map_val }
648
+ raise JSS::AmbiguousError, "Key #{key}: value '#{val}' is not unique for #{self}" if matches.size > 1
664
649
 
665
- mapped_ids.invert[ident]
650
+ return nil if matches.size.zero?
651
+
652
+ matches.keys.first
666
653
  end
667
654
 
668
655
  # Return true or false if an object of this subclass
@@ -814,52 +801,70 @@ module JSS
814
801
  api ||= JSS.api
815
802
 
816
803
  # refresh the .all list if needed
817
- all(:refresh, api: api) if args.delete :refresh
804
+ if args.delete(:refresh) || searchterm == :random
805
+ all(:refresh, api: api)
806
+ just_refreshed = true
807
+ else
808
+ just_refreshed = false
809
+ end
818
810
 
819
811
  # a random object?
820
- if searchterm == :random
821
- return new id: all_ids.sample, api: api
822
- end
812
+ return new id: all.sample[:id], api: api if searchterm == :random
823
813
 
824
814
  # get the lookup key and value, if given
825
815
  fetch_key, fetch_val = args.to_a.first
816
+ fetch_rsrc_key = fetch_rsrc_key(fetch_key)
826
817
 
827
- if fetch_key
828
- validate_unique_name(fetch_val) if fetch_key == :name
818
+ err_detail = "where #{fetch_key} = #{fetch_val}"
829
819
 
830
- # does this lookup key have a fetch_rsrc_key?
831
- fetch_rsrc_key = fetch_rsrc_key(fetch_key)
832
- return new fetch_rsrc: "#{self::RSRC_BASE}/#{fetch_rsrc_key}/#{CGI.escape fetch_val.to_s}", api: api if fetch_rsrc_key
833
- end
820
+ # names should raise an error if more than one exists,
821
+ # so we always have to do id_for_identifier, which will do so.
822
+ if fetch_rsrc_key == :name
823
+ id = id_for_identifier fetch_key, fetch_val, !just_refreshed, api: api
824
+ fetch_rsrc = id ? "#{self::RSRC_BASE}/name/#{CGI.escape fetch_val.to_s}" : nil
825
+
826
+ # if the fetch rsrc key exists, it can be used directly in an endpoint path
827
+ # so, use it directly, rather than looking up the id first.
828
+ elsif fetch_rsrc_key
829
+ fetch_rsrc = "#{self::RSRC_BASE}/#{fetch_rsrc_key}/#{CGI.escape fetch_val.to_s}"
834
830
 
835
- # if we'ere here, we need to get the id from either the lookup key/val or
836
- # the searchterm
837
- if fetch_key
838
- # it has an OTHER_LOOKUP_KEY but that key doesn't have a fetch_rsrc
839
- # so we look in the .map_all_ids_to_* hash for it.
840
- id = id_for_identifier fetch_key, fetch_val, api: api
841
- err_detail = "where #{fetch_key} = #{fetch_val}"
831
+ # it has an OTHER_LOOKUP_KEY but that key doesn't have a fetch_rsrc
832
+ # so we look in the .map_all_ids_to_* hash for it.
833
+ elsif fetch_key
834
+ id = id_for_identifier fetch_key, fetch_val, !just_refreshed, api: api
835
+ fetch_rsrc = id ? "#{self::RSRC_BASE}/id/#{id}" : nil
836
+
837
+ # no fetch key was given in the args, so try a search term
842
838
  elsif searchterm
843
839
  id = valid_id searchterm, api: api
840
+ fetch_rsrc = id ? "#{self::RSRC_BASE}/id/#{id}" : nil
844
841
  err_detail = "matching #{searchterm}"
842
+
845
843
  else
846
844
  raise ArgumentError, 'Missing searchterm or fetch key'
847
845
  end
848
- raise JSS::NoSuchItemError, "No #{self::RSRC_OBJECT_KEY} found #{err_detail}" unless id
849
846
 
850
- new id: id, api: api
847
+ begin
848
+ return new fetch_rsrc: fetch_rsrc, api: api
849
+ rescue RestClient::NotFound
850
+ raise JSS::NoSuchItemError, "No #{self::RSRC_OBJECT_KEY} found #{err_detail}" unless fetch_rsrc
851
+ end
851
852
  end # fetch
852
853
 
853
854
  # Make a ruby instance of a not-yet-existing APIObject.
854
855
  #
855
- # This is the preferred way to create new objects in the JSS.
856
- # It's a wrapper for using APIObject.new with the 'id: :new' parameter.
857
- # and helps avoid the confusion of using ruby's .new class method for making
858
- # ruby instances.
856
+ # This is how to create new objects in the JSS. A name: must be provided,
857
+ # and different subclasses can take other named parameters.
859
858
  #
860
859
  # For retrieving existing objects in the JSS, use {APIObject.fetch}
861
860
  #
862
- # For actually creating the object in the JSS, see {APIObject#create}
861
+ # After calling this you'll have a local instance, which will be created
862
+ # in the JSS when you call #create on it. see {APIObject#create}
863
+ #
864
+ # @param name[String] The name of this object, generally must be uniqie
865
+ #
866
+ # @param api[JSS::APIConnection] the connection thru which to make this
867
+ # object. Defaults to the deault API connection in JSS.api
863
868
  #
864
869
  # @param args[Hash] The data for creating an object, such as name:
865
870
  # See {APIObject#initialize}
@@ -868,6 +873,9 @@ module JSS
868
873
  #
869
874
  def self.make(**args)
870
875
  validate_not_metaclass(self)
876
+ unless constants.include?(:CREATABLE)
877
+ raise JSS::UnsupportedError, "Creating #{self.class::RSRC_LIST_KEY} isn't yet supported. Please use other Casper workflows."
878
+ end
871
879
  raise ArgumentError, "Use '#{self.class}.fetch id: xx' to retrieve existing JSS objects" if args[:id]
872
880
 
873
881
  args[:api] ||= JSS.api
@@ -921,9 +929,14 @@ module JSS
921
929
  api.delete_rsrc "#{self::RSRC_BASE}/id/#{vid}"
922
930
  else
923
931
  skipped << vid
924
- end # if current_ids include v
932
+ end # if current_ids include vid
925
933
  end # each victim
926
934
 
935
+ # clear any cached all-lists or id-maps for this class
936
+ # so they'll re-cache as needed
937
+ api.flushcache self::RSRC_LIST_KEY
938
+ # all :refresh, api: api
939
+
927
940
  skipped
928
941
  end # self.delete
929
942
 
@@ -932,17 +945,6 @@ module JSS
932
945
  raise JSS::UnsupportedError, 'JSS::APIObject is a metaclass. Do not use it directly' if klass == JSS::APIObject
933
946
  end
934
947
 
935
- # Raise an exception if a name is being used for fetching and it isn't
936
- # unique. Case Insensitive
937
- def self.validate_unique_name(name, refresh = false)
938
- return unless defined? self::NON_UNIQUE_NAMES
939
-
940
- name.downcase!
941
- matches = all_names(refresh).map(&:downcase).select { |n| n == name }
942
- raise JSS::AmbiguousError, "Name '#{name}' is not unique for #{self}" if matches.size > 1
943
- end
944
-
945
-
946
948
  # Attributes
947
949
  #####################################
948
950
 
@@ -1024,9 +1026,11 @@ module JSS
1024
1026
  def save
1025
1027
  if @in_jss
1026
1028
  raise JSS::UnsupportedError, 'Updating this object in the JSS is currently not supported by ruby-jss' unless updatable?
1029
+
1027
1030
  update
1028
1031
  else
1029
1032
  raise JSS::UnsupportedError, 'Creating this object in the JSS is currently not supported by ruby-jss' unless creatable?
1033
+
1030
1034
  create
1031
1035
  end
1032
1036
  end
@@ -1120,6 +1124,12 @@ module JSS
1120
1124
  @id = nil
1121
1125
  @in_jss = false
1122
1126
  @need_to_update = false
1127
+
1128
+ # clear any cached all-lists or id-maps for this class
1129
+ # so they'll re-cache as needed
1130
+ @api.flushcache self.class::RSRC_LIST_KEY
1131
+ # self.class.all :refresh, api: @api
1132
+
1123
1133
  :deleted
1124
1134
  end # delete
1125
1135
 
@@ -1275,7 +1285,12 @@ module JSS
1275
1285
 
1276
1286
  raise JSS::MissingDataError, "You must provide a :name to create a #{self.class::RSRC_OBJECT_KEY}." unless args[:name]
1277
1287
 
1278
- raise JSS::AlreadyExistsError, "A #{self.class::RSRC_OBJECT_KEY} already exists with the name '#{args[:name]}'" if self.class.all_names(api: @api).include? args[:name]
1288
+ return if defined? self.class::NON_UNIQUE_NAMES
1289
+
1290
+ matches = self.class.all_names(:refresh, api: @api).select { |n| n.casecmp? args[:name] }
1291
+
1292
+ raise JSS::AlreadyExistsError, "A #{self.class::RSRC_OBJECT_KEY} already exists with the name '#{args[:name]}'" unless matches.empty?
1293
+
1279
1294
  end
1280
1295
 
1281
1296
  # Given initialization args, perform an API lookup for an object.
@@ -1462,6 +1477,7 @@ module JSS
1462
1477
  end
1463
1478
 
1464
1479
  # Meta Programming
1480
+ ####################################################
1465
1481
 
1466
1482
  # Loop through the defined lookup keys and make
1467
1483
  # .all_<key>s methods for each one, with
@@ -1537,6 +1553,7 @@ require 'jss/api_object/computer_invitation'
1537
1553
  require 'jss/api_object/department'
1538
1554
  require 'jss/api_object/distribution_point'
1539
1555
  require 'jss/api_object/ebook'
1556
+ require 'jss/api_object/ibeacon'
1540
1557
  require 'jss/api_object/ldap_server'
1541
1558
  require 'jss/api_object/mac_application'
1542
1559
  require 'jss/api_object/mobile_device'
@@ -198,6 +198,18 @@ module JSS
198
198
  @id # remember to return the id
199
199
  end
200
200
 
201
+ # Wrapper/alias for both create and update
202
+ def save(get_results = false)
203
+ if @in_jss
204
+ raise JSS::UnsupportedError, 'Updating this object in the JSS is currently not supported by ruby-jss' unless updatable?
205
+ update get_results
206
+ else
207
+ raise JSS::UnsupportedError, 'Creating this object in the JSS is currently not supported by ruby-jss' unless creatable?
208
+ create get_results
209
+ end
210
+ end
211
+
212
+
201
213
  # Requery the API for the search results.
202
214
  #
203
215
  # This can be very slow, so temporarily reset the API timeout to 30 minutes
@@ -1076,13 +1076,13 @@ module JSS
1076
1076
 
1077
1077
  def serial_number=(new_val)
1078
1078
  return nil if new_val == @serial_number
1079
- @serial_number = new_val.empty? ? new_val : JSS::Validate.unique_identifier(JSS::Computer, :serial_number, new_val, api: api)
1079
+ @serial_number = new_val.empty? ? new_val : JSS::Validate.doesnt_already_exist(JSS::Computer, :serial_number, new_val, api: api)
1080
1080
  @need_to_update = true
1081
1081
  end
1082
1082
 
1083
1083
  def udid=(new_val)
1084
1084
  return nil if new_val == @udid
1085
- @udid = new_val.empty? ? new_val : JSS::Validate.unique_identifier(JSS::Computer, :udid, new_val, api: api)
1085
+ @udid = new_val.empty? ? new_val : JSS::Validate.doesnt_already_exist(JSS::Computer, :udid, new_val, api: api)
1086
1086
  @need_to_update = true
1087
1087
  end
1088
1088
 
@@ -71,15 +71,21 @@ module JSS
71
71
  #
72
72
  # @return [Integer] the jss ID of the newly created object
73
73
  #
74
- def create(api: nil)
75
- api ||= @api
74
+ def create
76
75
  raise JSS::UnsupportedError, "Creating or editing #{self.class::RSRC_LIST_KEY} isn't yet supported. Please use other Casper workflows." unless creatable?
76
+
77
77
  raise AlreadyExistsError, "This #{self.class::RSRC_OBJECT_KEY} already exists. Use #update to make changes." if @in_jss
78
- api.post_rsrc(rest_rsrc, rest_xml) =~ %r{><id>(\d+)</id><}
78
+
79
+ @api.post_rsrc(rest_rsrc, rest_xml) =~ %r{><id>(\d+)</id><}
79
80
  @id = Regexp.last_match(1).to_i
80
81
  @in_jss = true
81
82
  @need_to_update = false
82
83
  @rest_rsrc = "#{self.class::RSRC_BASE}/id/#{@id}"
84
+
85
+ # clear any caches for this class
86
+ # so they'll re-cache as needed
87
+ @api.flushcache self.class::RSRC_LIST_KEY
88
+
83
89
  @id
84
90
  end
85
91
 
@@ -180,6 +180,7 @@ module JSS
180
180
  raise MissingDataError, 'No popup_choices set for Pop-up Menu input_type.' unless @popup_choices.is_a?(Array) && !@popup_choices.empty?
181
181
  end
182
182
  super
183
+ @api.flushcache self.class
183
184
  end
184
185
 
185
186
  # @see JSS::APIObject#delete
@@ -191,6 +192,7 @@ module JSS
191
192
  @api.open_timeout = orig_open_timeout + 1800
192
193
  begin
193
194
  super
195
+ @api.flushcache self.class
194
196
  ensure
195
197
  @api.timeout = orig_timeout
196
198
  @api.open_timeout = orig_open_timeout
@@ -233,7 +235,7 @@ module JSS
233
235
  #
234
236
  def data_type=(new_val)
235
237
  return nil if @data_type == new_val
236
- raise JSS::InvalidDataError, "data_type must be a string, one of: #{DATA_TYPES.join(', ')}" unless DATA_TYPES.include? new_val
238
+ raise JSS::InvalidDataError, "data_type must be a string, one of: '#{DATA_TYPES.join("', '")}'" unless DATA_TYPES.include? new_val
237
239
  @data_type = new_val
238
240
  @need_to_update = true
239
241
  end #
@@ -84,7 +84,7 @@ module JSS
84
84
  # groups.
85
85
  #
86
86
  def self.all_static(refresh = false, api: JSS.api)
87
- all(refresh, api: api).select { |g| (g[:is_smart]) }
87
+ all(refresh, api: api).reject { |g| g[:is_smart] }
88
88
  end
89
89
 
90
90
  # Immediatly add and/or remove members in a static group without
@@ -105,7 +105,7 @@ module JSS
105
105
  # @return [void]
106
106
  #
107
107
  def self.change_membership(group, add_members: [], remove_members: [], api: JSS.api)
108
- raise JSS::NoSuchItemError, "No #{self} matching '#{ident}'" unless (group_id = valid_id group, api: api)
108
+ raise JSS::NoSuchItemError, "No #{self} matching '#{group}'" unless (group_id = valid_id group, api: api)
109
109
  raise JSS::UnsupportedError, "Not a static group, can't change membership directly" if map_all_ids_to(:is_smart, api: api)[group_id]
110
110
 
111
111
  add_members = [add_members].flatten
@@ -210,7 +210,7 @@ module JSS
210
210
  #####################################
211
211
 
212
212
  # When creating a new group in the JSS, you must call .make with a :type key
213
- # and a value of :smart or :static, as well as a :name and the :id => :new
213
+ # and a value of :smart or :static, as well as a :name
214
214
  #
215
215
  # @see JSS::APIObject
216
216
  #
@@ -236,23 +236,61 @@ module JSS
236
236
 
237
237
  # @see Creatable#create
238
238
  #
239
- def create(calculate_members: true)
239
+ # @param calculate_members [Boolan] should the local membership list be
240
+ # re-read from the API after the group is created?
241
+ #
242
+ # @param retries [Integer] If calculate_members is true, refetching the
243
+ # group to re-read the membership can happen too fast, the JSS won't know
244
+ # it exists yet and will throw a RestClient::NotFound error. If that
245
+ # happens, try again this many times with a 1 second pause between attempts.
246
+ #
247
+ def create(calculate_members: true, retries: 10)
240
248
  if @is_smart
241
249
  raise JSS::MissingDataError, 'No criteria specified for smart group' unless @criteria
242
250
  end
243
251
  super()
244
- refresh_members if calculate_members
252
+
253
+ if calculate_members
254
+ tries = 0
255
+ while tries < retries
256
+ begin
257
+ refresh_members
258
+ break
259
+ rescue RestClient::NotFound
260
+ sleep 1
261
+ tries += 1
262
+ end # begin
263
+ end # while
264
+ end # if calc members
265
+
245
266
  @id
246
267
  end
247
268
 
248
269
  # @see Updatable#update
249
270
  #
250
- def update
251
- super
252
- refresh_members
271
+ def update(refresh: true)
272
+ super()
273
+ refresh_members if refresh
253
274
  @id
254
275
  end
255
276
 
277
+ # Wrapper/alias for both create and update
278
+ def save(**params)
279
+ params[:calculate_members] = true if params[:calculate_members].nil?
280
+ params[:retries] = 10 if params[:retries].nil?
281
+ params[:refresh] = true if params[:refresh].nil?
282
+
283
+ if @in_jss
284
+ raise JSS::UnsupportedError, 'Updating this object in the JSS is currently not supported by ruby-jss' unless updatable?
285
+
286
+
287
+ update refresh: params[:refresh]
288
+ else
289
+ raise JSS::UnsupportedError, 'Creating this object in the JSS is currently not supported by ruby-jss' unless creatable?
290
+ create calculate_members: params[:calculate_members], retries: params[:retries]
291
+ end
292
+ end
293
+
256
294
  # @see APIObject#delete
257
295
  #
258
296
  def delete
@@ -269,6 +307,7 @@ module JSS
269
307
  #
270
308
  def criteria=(new_criteria)
271
309
  raise InvalidDataError, 'Only smart groups have criteria.' unless @is_smart
310
+
272
311
  super
273
312
  end
274
313
 
@@ -0,0 +1,198 @@
1
+ ### Copyright 2019 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
+ # Module Variables
30
+ #####################################
31
+
32
+ # Module Methods
33
+ #####################################
34
+
35
+ # Classes
36
+ #####################################
37
+
38
+ # An iBeacon in the JSS.
39
+ # @see JSS::APIObject
40
+ #
41
+ class IBeacon < JSS::APIObject
42
+
43
+ # Mix-Ins
44
+ #####################################
45
+ include JSS::Creatable
46
+ include JSS::Updatable
47
+
48
+ # Class Methods
49
+ #####################################
50
+
51
+ # Class Constants
52
+ #####################################
53
+
54
+ # The base for REST resources of this class
55
+ RSRC_BASE = 'ibeacons'.freeze
56
+
57
+ # the hash key used for the JSON list output of all objects in the JSS
58
+ RSRC_LIST_KEY = :ibeacons
59
+
60
+ # The hash key used for the JSON object output.
61
+ # It's also used in various error messages
62
+ RSRC_OBJECT_KEY = :ibeacon
63
+
64
+ # the object type for this object in
65
+ # the object history table.
66
+ # See {APIObject#add_object_history_entry}
67
+ OBJECT_HISTORY_OBJECT_TYPE = 360
68
+
69
+ # The major & minor values, if used, must be in this range
70
+ MAJOR_MINOR_RANGE = 0..65_535.freeze
71
+
72
+ # If not used, this is the value for the major and minor
73
+ MAJOR_MINOR_UNUSED = -1
74
+
75
+ # Attributes
76
+ #####################################
77
+
78
+ ### @return [String] the top-level region identifier, a valid UUID
79
+ attr_reader :uuid
80
+
81
+ ### @return [IPAddr] the mid-level region identifier,
82
+ # an integer within MAJOR_MINOR_RANGE or MAJOR_MINOR_UNUSED
83
+ attr_reader :major
84
+
85
+ ### @return [IPAddr] the low-level region identifier,
86
+ # an integer within MAJOR_MINOR_RANGE or MAJOR_MINOR_UNUSED
87
+ attr_reader :minor
88
+
89
+ # Constructor
90
+ # @see JSS::APIObject.initialize
91
+ #####################################
92
+ def initialize(args = {})
93
+ super args
94
+
95
+ @uuid = @init_data[:uuid]
96
+ @major = @init_data[:major]
97
+ @minor = @init_data[:minor]
98
+
99
+ # defaults
100
+ @major ||= MAJOR_MINOR_UNUSED
101
+ @minor ||= MAJOR_MINOR_UNUSED
102
+ end # init
103
+
104
+
105
+ # Public Instance Methods
106
+ #####################################
107
+
108
+ # set the uuid
109
+ #
110
+ # @param newval[String] the new uuid
111
+ #
112
+ # @return [void]
113
+ #
114
+ def uuid=(newval)
115
+ JSS::Validate.uuid newval
116
+ @uuid = newval
117
+ @need_to_update = true
118
+ end
119
+
120
+ # Set the major value to for this iBeacon region.
121
+ # Use nil or -1 to not use the major value
122
+ #
123
+ # @param newval[String] the new value
124
+ #
125
+ # @return [void]
126
+ #
127
+ def major=(newval)
128
+ if newval.nil? || newval == MAJOR_MINOR_UNUSED
129
+ newval ||= MAJOR_MINOR_UNUSED
130
+ else
131
+ JSS::Validate.ibeacon_major_minor newval
132
+ end
133
+ @major = newval
134
+ @need_to_update = true
135
+ end
136
+
137
+ # Set the minoe value to for this iBeacon region.
138
+ # Use nil or -1 to not use the major value
139
+ #
140
+ # @param newval[String] the new value
141
+ #
142
+ # @return [void]
143
+ #
144
+ def minor=(newval)
145
+ if newval.nil? || newval == MAJOR_MINOR_UNUSED
146
+ newval ||= MAJOR_MINOR_UNUSED
147
+ else
148
+ JSS::Validate.ibeacon_major_minor newval
149
+ end
150
+
151
+ @minor = newval
152
+ @need_to_update = true
153
+ end
154
+
155
+ # @return is the major identifier being used in this region?
156
+ def using_major?
157
+ @major == MAJOR_MINOR_UNUSED
158
+ end
159
+
160
+ # @return is the minor identifier being used in this region?
161
+ def using_minor?
162
+ @minor == MAJOR_MINOR_UNUSED
163
+ end
164
+
165
+ # @see Creatable.create
166
+ def create
167
+ raise JSS::MissingDataError, 'uuid may not be empty' if @uuid.to_s.empty?
168
+
169
+ super
170
+ end
171
+
172
+ # @see Updatable.update
173
+ def update
174
+ raise JSS::MissingDataError, 'uuid may not be empty' if @uuid.to_s.empty?
175
+
176
+ super
177
+ end
178
+
179
+ # private instance methods
180
+ ######################
181
+ private
182
+
183
+ # the xml formated data for adding or updating this in the JSS
184
+ #
185
+ def rest_xml
186
+ doc = REXML::Document.new APIConnection::XML_HEADER
187
+ ns = doc.add_element RSRC_OBJECT_KEY.to_s
188
+ ns.add_element('name').text = @name
189
+ ns.add_element('uuid').text = @uuid
190
+ ns.add_element('major').text = @major.to_s
191
+ ns.add_element('minor').text = @minor.to_s
192
+ doc.to_s
193
+ end # rest_xml
194
+
195
+
196
+ end # class ibeacon
197
+
198
+ end # module
@@ -437,6 +437,9 @@ module JSS
437
437
  def raw_targets_to_ids(targets, api: JSS.api, expand_groups: true)
438
438
  targets = targets.is_a?(Array) ? targets : [targets]
439
439
 
440
+ # flush caches before checking ids and managment
441
+ api.flushcache self::RSRC_LIST_KEY
442
+
440
443
  # make sure its an array of ids
441
444
  targets.map! do |md|
442
445
  id = valid_id md, api: api
@@ -453,7 +456,7 @@ module JSS
453
456
 
454
457
  # make sure all of them are managed, or else the API will raise a 400
455
458
  # 'Bad Request' when sending the command to an unmanaged target
456
- all_mgd = self.map_all_ids_to(:managed, api: api).select { |_id, mgd| mgd }.keys
459
+ all_mgd = map_all_ids_to(:managed, api: api).select { |_id, mgd| mgd }.keys
457
460
 
458
461
  targets.each do |target_id|
459
462
  raise JSS::UnmanagedError, "#{self} with id #{target_id} is not managed. Cannot send command." unless all_mgd.include? target_id
@@ -497,14 +497,14 @@ module JSS
497
497
  #
498
498
  def serial_number=(new_val)
499
499
  return nil if new_val == @serial_number
500
- @serial_number = new_val.empty? ? new_val : JSS::Validate.unique_identifier(self.class, :serial_number, new_val, api: api)
500
+ @serial_number = new_val.empty? ? new_val : JSS::Validate.doesnt_already_exist(self.class, :serial_number, new_val, api: api)
501
501
  @need_to_update = true
502
502
  end
503
503
 
504
504
  #
505
505
  def udid=(new_val)
506
506
  return nil if new_val == @udid
507
- @udid = new_val.empty? ? new_val : JSS::Validate.unique_identifier(self.class, :udid, new_val, api: api)
507
+ @udid = new_val.empty? ? new_val : JSS::Validate.doesnt_already_exist(self.class, :udid, new_val, api: api)
508
508
  @need_to_update = true
509
509
  end
510
510
 
@@ -252,9 +252,24 @@ module JSS
252
252
  def self.all_for_title(title, api: JSS.api)
253
253
  title_id = JSS::PatchTitle.valid_id title
254
254
  raise JSS::NoSuchItemError, "No PatchTitle matching '#{title}'" unless title_id
255
+
255
256
  api.get_rsrc("#{RSRC_BY_PATCH_TITLE}#{title_id}")[RSRC_BY_PATCH_TITLE_LIST_KEY]
256
257
  end
257
258
 
259
+ # Override APIObject.fetch, since there's no .../patchpolicies/name/... endpoint
260
+ # @see APIObject#fetch
261
+ #
262
+ def self.fetch(searchterm = nil, **args)
263
+ name_search = args.delete :name
264
+ if name_search
265
+ id = valid_id name_search
266
+ raise JSS::NoSuchItemError, "No #{self::RSRC_OBJECT_KEY} found #{err_detail}" unless id
267
+
268
+ args[:id] = id
269
+ end
270
+ super
271
+ end # fetch
272
+
258
273
  # Attributes
259
274
  ################################
260
275
 
@@ -93,9 +93,15 @@ module JSS
93
93
  return nil unless @need_to_update
94
94
  raise JSS::UnsupportedError, "Editing #{self.class::RSRC_LIST_KEY} isn't yet supported. Please use other Casper workflows." unless updatable?
95
95
  raise JSS::NoSuchItemError, "Not In JSS! Use #create to create this #{self.class::RSRC_OBJECT_KEY} in the JSS before updating it." unless @in_jss
96
+
96
97
  @api.put_rsrc @rest_rsrc, rest_xml
97
98
  @need_to_update = false
98
99
  refresh_icon if self_servable?
100
+
101
+ # clear any cached all-lists or id-maps for this class
102
+ # so they'll re-cache as needed
103
+ @api.flushcache self.class::RSRC_LIST_KEY
104
+
99
105
  @id
100
106
  end # update
101
107
 
@@ -49,9 +49,13 @@ module JSS
49
49
  def self.mac_address(val, msg = nil)
50
50
  msg ||= "Not a valid MAC address: '#{val}'"
51
51
  raise JSS::InvalidDataError, msg unless val =~ MAC_ADDR_RE
52
+
52
53
  val
53
54
  end
54
55
 
56
+ # Segments of a valid IPv4 address are integers in this range.
57
+ IP_SEGMENT_RANGE = 0..255
58
+
55
59
  # Validate the format and content of an IPv4 address
56
60
  #
57
61
  # @param val[String] The value to validate
@@ -65,12 +69,14 @@ module JSS
65
69
  ok = true
66
70
  parts = val.strip.split '.'
67
71
  ok = false unless parts.size == 4
68
- parts.each { |p| ok = false unless p.jss_integer? && p.to_i < 256 && p.to_i >= 0 }
72
+ parts.each { |p| ok = false unless p.jss_integer? && IP_SEGMENT_RANGE.include?(p.to_i) }
69
73
  raise JSS::InvalidDataError, msg unless ok
74
+
70
75
  val
71
76
  end
72
77
 
73
- # Validate that a value doesn't already exist for a given identifier of a given class
78
+ # Validate that a value doesn't already exist for a given identifier of a
79
+ # given class
74
80
  #
75
81
  # e.g. when klass = JSS::Computer, identifier = :name, and val = 'foo'
76
82
  # will raise an error when a computer named 'foo' exists
@@ -89,9 +95,17 @@ module JSS
89
95
  #
90
96
  # @return [Object] the validated unique value
91
97
  #
92
- def self.unique_identifier(klass, identifier, val, msg = nil, api: JSS.api)
98
+ def self.doesnt_already_exist(klass, identifier, val, msg = nil, api: JSS.api)
93
99
  msg ||= "A #{klass} already exists with #{identifier} '#{val}'"
94
100
  return val unless klass.all(:refresh, api: api).map { |i| i[identifier] }.include? val
101
+
102
+ key = klass.real_lookup_key identifier
103
+
104
+ # use map_all_ids_to cuz it works with any identifer, even non-existing
105
+ existing_values = klass.map_all_ids_to( key, api: api).values
106
+ matches = existing_values.select { |existing_val| existing_val.casecmp? val }
107
+ return val if matches.empty?
108
+
95
109
  raise JSS::AlreadyExistsError, msg
96
110
  end
97
111
 
@@ -113,6 +127,7 @@ module JSS
113
127
  return bool if JSS::TRUE_FALSE.include? bool
114
128
  return true if bool.to_s =~ /^(true|yes)$/i
115
129
  return false if bool.to_s =~ /^(false|no)$/i
130
+
116
131
  raise JSS::InvalidDataError, msg
117
132
  end
118
133
 
@@ -131,6 +146,7 @@ module JSS
131
146
  msg ||= 'Value must be an integer'
132
147
  val = val.to_i if val.is_a?(String) && val.jss_integer?
133
148
  raise JSS::InvalidDataError, msg unless val.is_a? Integer
149
+
134
150
  val
135
151
  end
136
152
 
@@ -145,6 +161,42 @@ module JSS
145
161
  def self.non_empty_string(val, msg = nil)
146
162
  msg ||= 'value must be a non-empty String'
147
163
  raise JSS::InvalidDataError, msg unless val.is_a?(String) && !val.empty?
164
+
165
+ val
166
+ end
167
+
168
+ UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.freeze
169
+
170
+ # validate that the given value is a valid uuid string
171
+ #
172
+ # @param val [Object] the thing to validate
173
+ #
174
+ # @param msg[String] A custom error message when the value is invalid
175
+ #
176
+ # @return [String] the valid uuid string
177
+ #
178
+ def self.uuid(val, msg = nil)
179
+ msg ||= 'value must be valid uuid'
180
+ raise JSS::InvalidDataError, msg unless val.is_a?(String) && val =~ UUID_RE
181
+
182
+ val
183
+ end
184
+
185
+ # validate that the given value is an integer in the JSS::IBeacon::MAJOR_MINOR_RANGE
186
+ #
187
+ # @param val [Object] the thing to validate
188
+ #
189
+ # @param msg[String] A custom error message when the value is invalid
190
+ #
191
+ # @return [String] the valid integer
192
+ #
193
+ def self.ibeacon_major_minor(val, msg = nil)
194
+ msg ||= "value must be an integer in the range #{JSS::IBeacon::MAJOR_MINOR_RANGE}"
195
+ val = val.to_i if val.is_a?(String) && val.jss_integer?
196
+ ok = val.is_a? Integer
197
+ ok = JSS::IBeacon::MAJOR_MINOR_RANGE.include? val if ok
198
+ raise JSS::InvalidDataError, msg unless ok
199
+
148
200
  val
149
201
  end
150
202
 
@@ -27,6 +27,6 @@
27
27
  module JSS
28
28
 
29
29
  ### The version of ruby-jss
30
- VERSION = '1.1.3'.freeze
30
+ VERSION = '1.2.0b1'.freeze
31
31
 
32
32
  end # module
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-jss
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.2.0b1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Lasell
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-09-24 00:00:00.000000000 Z
12
+ date: 2019-10-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: plist
@@ -180,6 +180,7 @@ files:
180
180
  - lib/jss/api_object/group/computer_group.rb
181
181
  - lib/jss/api_object/group/mobile_device_group.rb
182
182
  - lib/jss/api_object/group/user_group.rb
183
+ - lib/jss/api_object/ibeacon.rb
183
184
  - lib/jss/api_object/ldap_server.rb
184
185
  - lib/jss/api_object/locatable.rb
185
186
  - lib/jss/api_object/mac_application.rb
@@ -288,12 +289,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
288
289
  version: 2.0.0
289
290
  required_rubygems_version: !ruby/object:Gem::Requirement
290
291
  requirements:
291
- - - ">="
292
+ - - ">"
292
293
  - !ruby/object:Gem::Version
293
- version: '0'
294
+ version: 1.3.1
294
295
  requirements: []
295
- rubyforge_project:
296
- rubygems_version: 2.7.8
296
+ rubygems_version: 3.0.3
297
297
  signing_key:
298
298
  specification_version: 4
299
299
  summary: A Ruby interface to the Jamf Pro REST API