ruby-jss 2.0.0b3 → 2.0.0b5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +1 -0
- data/README-2.0.0.md +68 -57
- data/README.md +227 -172
- data/lib/jamf/api/classic/api_objects/distribution_point.rb +12 -50
- data/lib/jamf/api/classic/api_objects/patch_title.rb +14 -9
- data/lib/jamf/api/classic/base_classes/api_object.rb +68 -9
- data/lib/jamf/api/classic/base_classes/patch_source.rb +10 -5
- data/lib/jamf/api/connection.rb +1 -1
- data/lib/jamf/api/jamf_pro/mixins/collection_resource.rb +8 -12
- data/lib/jamf/version.rb +1 -1
- data/lib/jamf/zeitwerk_config.rb +217 -0
- data/lib/jamf.rb +7 -39
- metadata +4 -4
- data/lib/zeitwerk_config.rb +0 -168
@@ -107,27 +107,18 @@ module Jamf
|
|
107
107
|
def self.master_distribution_point(refresh = false, default: nil, api: nil, cnx: Jamf.cnx)
|
108
108
|
cnx = api if api
|
109
109
|
|
110
|
-
@master_distribution_point = nil if refresh
|
111
|
-
return @master_distribution_point if @master_distribution_point
|
112
|
-
|
113
110
|
all_ids(refresh, cnx: cnx).each do |dp_id|
|
114
111
|
dp = fetch id: dp_id, cnx: cnx
|
115
|
-
if dp.master?
|
116
|
-
@master_distribution_point = dp
|
117
|
-
break
|
118
|
-
end
|
112
|
+
return dp if dp.master?
|
119
113
|
end
|
120
114
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
@master_distribution_point
|
127
|
-
elsif default == :random
|
128
|
-
@master_distribution_point = fetch(id: all_ids.sample, cnx: cnx)
|
115
|
+
case default
|
116
|
+
when :random
|
117
|
+
fetch id: all_ids.sample, cnx: cnx
|
118
|
+
when nil
|
119
|
+
raise Jamf::NoSuchItemError, 'No Master FileShare Distribtion Point. Use the default: parameter if needed.'
|
129
120
|
else
|
130
|
-
|
121
|
+
fetch default, cnx: cnx
|
131
122
|
end
|
132
123
|
end
|
133
124
|
|
@@ -267,38 +258,9 @@ module Jamf
|
|
267
258
|
# @return [String] the ssh password as a SHA256 digest
|
268
259
|
attr_reader :ssh_password_sha256
|
269
260
|
|
270
|
-
# As well as the standard :id, :name, and :data, you can
|
271
|
-
# instantiate this class with :id => :master, in which case you'll
|
272
|
-
# get the Master Distribution Point as defined in the JSS.
|
273
|
-
# An error will be raised if one hasn't been defined.
|
274
|
-
#
|
275
|
-
# You can also do this more easily by calling JSS.master_distribution_point
|
276
|
-
#
|
277
261
|
def initialize(**args)
|
278
|
-
|
279
|
-
|
280
|
-
args[:cnx] ||= Jamf.cnx
|
281
|
-
|
282
|
-
@cnx = args[:cnx]
|
283
|
-
|
284
|
-
@init_data = nil
|
285
|
-
|
286
|
-
# looking for master?
|
287
|
-
if args[:id] == :master
|
288
|
-
|
289
|
-
self.class.all_ids(cnx: @cnx).each do |id|
|
290
|
-
@init_data = @cnx.c_get("#{RSRC_BASE}/id/#{id}")[RSRC_OBJECT_KEY]
|
291
|
-
if @init_data[:is_master]
|
292
|
-
@id = @init_data[:id]
|
293
|
-
@name = @init_data[:name]
|
294
|
-
break
|
295
|
-
end # if data is master
|
296
|
-
@init_data = nil
|
297
|
-
end # each id
|
298
|
-
end # if args is master
|
299
|
-
|
300
|
-
super(args) if @init_data.nil?
|
301
|
-
|
262
|
+
super
|
263
|
+
|
302
264
|
@ip_address = @init_data[:ip_address]
|
303
265
|
@local_path = @init_data[:local_path]
|
304
266
|
@enable_load_balancing = @init_data[:enable_load_balancing]
|
@@ -365,7 +327,7 @@ module Jamf
|
|
365
327
|
when :ro then @read_only_password_sha256
|
366
328
|
when :http then @http_password_sha256
|
367
329
|
when :ssh then @ssh_password_sha256
|
368
|
-
|
330
|
+
end # case
|
369
331
|
|
370
332
|
return nil if sha256 == EMPTY_PW_256
|
371
333
|
|
@@ -449,7 +411,7 @@ module Jamf
|
|
449
411
|
JSS.stdin line
|
450
412
|
else
|
451
413
|
pw
|
452
|
-
|
414
|
+
end
|
453
415
|
|
454
416
|
pwok = check_pw(access, password)
|
455
417
|
unless pwok
|
@@ -466,7 +428,7 @@ module Jamf
|
|
466
428
|
when 'smb' then '/sbin/mount_smbfs'
|
467
429
|
when 'afp' then '/sbin/mount_afp'
|
468
430
|
else raise "Can't mount distribution point #{@name}: no known connection type."
|
469
|
-
|
431
|
+
end
|
470
432
|
|
471
433
|
@mountpoint.mkpath
|
472
434
|
|
@@ -180,6 +180,7 @@ module Jamf
|
|
180
180
|
info[:source_id] = info[:source_id].to_i
|
181
181
|
end
|
182
182
|
return data unless source_id
|
183
|
+
|
183
184
|
data.select { |p| p[:source_id] == source_id }
|
184
185
|
end
|
185
186
|
|
@@ -311,9 +312,11 @@ module Jamf
|
|
311
312
|
# so all other lookup values have to be converted to ID before
|
312
313
|
# the call to super
|
313
314
|
#
|
314
|
-
def self.fetch(identifier = nil, **params)
|
315
|
-
# default
|
316
|
-
|
315
|
+
def self.fetch(identifier = nil, **params)
|
316
|
+
# default connection if unspecified
|
317
|
+
cnx = params.delete :cnx
|
318
|
+
cnx ||= params.delete :api # backward compatibility, deprecated
|
319
|
+
cnx ||= Jamf.cnx
|
317
320
|
|
318
321
|
# source: and source_id: are considered the same, source_id: wins
|
319
322
|
params[:source_id] ||= params[:source]
|
@@ -349,8 +352,8 @@ module Jamf
|
|
349
352
|
cnx = api if api
|
350
353
|
|
351
354
|
id = all_ids(refresh, cnx: cnx).include?(ident) ? ident : nil
|
352
|
-
id ||=
|
353
|
-
id ||=
|
355
|
+
id ||= map_all(:id, to: :source_name_id).invert[ident]
|
356
|
+
id ||= map_all(:id, to: :name).invert[ident]
|
354
357
|
id
|
355
358
|
end
|
356
359
|
|
@@ -376,7 +379,6 @@ module Jamf
|
|
376
379
|
attr_reader :email_notification
|
377
380
|
alias email_notification? email_notification
|
378
381
|
|
379
|
-
#
|
380
382
|
def initialize(**args)
|
381
383
|
super
|
382
384
|
|
@@ -421,11 +423,11 @@ module Jamf
|
|
421
423
|
def versions
|
422
424
|
return @versions unless in_jss
|
423
425
|
return @versions unless @versions.empty?
|
426
|
+
|
424
427
|
# if we are in jss, and versions is empty, re-fetch them
|
425
428
|
@versions = self.class.fetch(id: id).versions
|
426
429
|
end
|
427
430
|
|
428
|
-
|
429
431
|
# @return [Hash] Subset of @versions, containing those which have packages
|
430
432
|
# assigned
|
431
433
|
#
|
@@ -442,6 +444,7 @@ module Jamf
|
|
442
444
|
def email_notification=(new_setting)
|
443
445
|
return if email_notification == new_setting
|
444
446
|
raise Jamf::InvalidDataError, 'New Setting must be boolean true or false' unless Jamf::TRUE_FALSE.include? @email_notification = new_setting
|
447
|
+
|
445
448
|
@need_to_update = true
|
446
449
|
end
|
447
450
|
|
@@ -454,6 +457,7 @@ module Jamf
|
|
454
457
|
def web_notification=(new_setting)
|
455
458
|
return if web_notification == new_setting
|
456
459
|
raise Jamf::InvalidDataError, 'New Setting must be boolean true or false' unless Jamf::TRUE_FALSE.include? @web_notification = new_setting
|
460
|
+
|
457
461
|
@need_to_update = true
|
458
462
|
end
|
459
463
|
|
@@ -467,8 +471,8 @@ module Jamf
|
|
467
471
|
|
468
472
|
# wrapper to fetch versions after creating
|
469
473
|
def create
|
470
|
-
|
471
|
-
|
474
|
+
super
|
475
|
+
|
472
476
|
end
|
473
477
|
|
474
478
|
# wrapper to clear @changed_pkgs after updating
|
@@ -535,6 +539,7 @@ module Jamf
|
|
535
539
|
pkg = velem.add_element 'package'
|
536
540
|
# leave am empty package element to remove the pkg assignement
|
537
541
|
next if versions[vers].package_id == :none
|
542
|
+
|
538
543
|
pkg.add_element('id').text = versions[vers].package_id.to_s
|
539
544
|
end # do vers
|
540
545
|
end
|
@@ -557,8 +557,9 @@ module Jamf
|
|
557
557
|
#
|
558
558
|
######################################
|
559
559
|
def self.map_all(ident, to:, cnx: Jamf.cnx, refresh: false, cached_list: nil)
|
560
|
+
orig_ident = ident
|
560
561
|
ident = lookup_keys[ident]
|
561
|
-
raise Jamf::InvalidDataError, "No identifier :#{
|
562
|
+
raise Jamf::InvalidDataError, "No identifier :#{orig_ident} for class #{self}" unless ident
|
562
563
|
|
563
564
|
list = cached_list || all(refresh, cnx: cnx)
|
564
565
|
mapped = list.map do |i|
|
@@ -673,28 +674,85 @@ module Jamf
|
|
673
674
|
# is undefined. In short - dont' use names here unless you know they are
|
674
675
|
# unique.
|
675
676
|
#
|
677
|
+
# NOTE: Integers passed in as strings, e.g. '12345' will be converted to
|
678
|
+
# integers and return the matching integer id if it exists.
|
679
|
+
#
|
680
|
+
# This means that if you have names that might match '12345' and you use
|
681
|
+
# valid_id '12345'
|
682
|
+
# you will get back the id 12345, if such an id exists, even if it is not
|
683
|
+
# the object with the name '12345'
|
684
|
+
#
|
685
|
+
# To explicitly look for '12345' as a name, use:
|
686
|
+
# valid_id name: '12345'
|
687
|
+
# See the ident_and_val param below.
|
688
|
+
#
|
676
689
|
# @param identfier [String,Integer] An identifier for an object, a value for
|
677
|
-
#
|
690
|
+
# one of the available lookup_keys. Omit this and use 'identifier: value'
|
691
|
+
# if you want to limit the search to a specific indentifier key, e.g.
|
692
|
+
# name: 'somename'
|
693
|
+
# or
|
694
|
+
# id: 76538
|
678
695
|
#
|
679
696
|
# @param refresh [Boolean] Should the data be re-read from the server
|
680
697
|
#
|
698
|
+
# @param ident_and_val [Hash] Do not pass in Hash.
|
699
|
+
# This Hash internally holds the arbitrary identifier key
|
700
|
+
# and desired value when you call ".valid_id ident: 'value'", e.g.
|
701
|
+
# ".valid_id name: 'somename'" or ".valid_id udid: some_udid"
|
702
|
+
# Using explicit identifier keys like this will speed things up, since
|
703
|
+
# the method doesn't have to search through all available identifiers
|
704
|
+
# for the desired value.
|
705
|
+
#
|
681
706
|
# @param cnx [Jamf::Connection] an API connection to use for the query.
|
682
707
|
# Defaults to the corrently active API. See {Jamf::Connection}
|
683
708
|
#
|
684
709
|
# @return [Integer, nil] the id of the matching object, or nil if it doesn't exist
|
685
710
|
#
|
686
|
-
def self.valid_id(identifier, refresh = false, api: nil, cnx: Jamf.cnx)
|
711
|
+
def self.valid_id(identifier = nil, refresh = false, api: nil, cnx: Jamf.cnx, **ident_and_val)
|
687
712
|
cnx = api if api
|
688
713
|
|
689
|
-
# refresh if needed
|
714
|
+
# refresh the cache if needed
|
690
715
|
all(refresh, cnx: cnx) if refresh
|
691
716
|
|
692
|
-
#
|
717
|
+
# Were we given an explict identifier key, like name: or id:?
|
718
|
+
# If so, just look for that.
|
719
|
+
unless ident_and_val.empty?
|
720
|
+
# only the first k/v pair of the ident_and_val hash is used
|
721
|
+
key = ident_and_val.keys.first
|
722
|
+
val = ident_and_val[key]
|
723
|
+
|
724
|
+
# if we are explicitly looking for an id, ensure we use an integer
|
725
|
+
# even if we were given an integer in a string.
|
726
|
+
if key == :id
|
727
|
+
val = val.to_i if val.is_a?(String) && val.j_integer?
|
728
|
+
return all_ids(cnx: cnx).include?(val) ? val : nil
|
729
|
+
end
|
730
|
+
|
731
|
+
# map the identifiers to ids, and return the id if there's
|
732
|
+
# a case-insensitive matching identifire
|
733
|
+
map_all(key, to: :id).each do |ident_val, id|
|
734
|
+
return id if ident_val.to_s.casecmp? val.to_s
|
735
|
+
end
|
736
|
+
nil
|
737
|
+
end
|
738
|
+
|
739
|
+
# If we are here, we need to seach all available identifier keys
|
740
|
+
# Start by looking for it as an id.
|
741
|
+
|
742
|
+
# it its a valid integer id, return it
|
693
743
|
return identifier if all_ids(cnx: cnx).include? identifier
|
694
744
|
|
745
|
+
# if its a valid integer-in-a-string id, return it
|
746
|
+
if identifier.is_a?(String) && identifier.j_integer?
|
747
|
+
int_id = identifier.to_i
|
748
|
+
return int_id if all_ids(cnx: cnx).include? int_id
|
749
|
+
end
|
750
|
+
|
751
|
+
# Now go through all the other identifier keys
|
695
752
|
keys_to_check = lookup_keys(no_aliases: true)
|
696
753
|
keys_to_check.delete :id # we've already checked :id
|
697
754
|
|
755
|
+
# loop thru looking for a match
|
698
756
|
keys_to_check.each do |key|
|
699
757
|
mapped_ids = map_all_ids_to key, cnx: cnx
|
700
758
|
matches = mapped_ids.select { |_id, ident| ident.casecmp? identifier }
|
@@ -905,7 +963,7 @@ module Jamf
|
|
905
963
|
|
906
964
|
# which connection?
|
907
965
|
cnx = args.delete :cnx
|
908
|
-
cnx ||= args.delete :api
|
966
|
+
cnx ||= args.delete :api # backward compatibility, deprecated
|
909
967
|
cnx ||= Jamf.cnx
|
910
968
|
|
911
969
|
# refresh the .all list if needed
|
@@ -1073,7 +1131,7 @@ module Jamf
|
|
1073
1131
|
# @param cnx [Jamf::Connection] the connection thru which to make this
|
1074
1132
|
# object. Defaults to the deault API connection in Jamf.cnx
|
1075
1133
|
#
|
1076
|
-
# @param args[Hash] The data for creating an object, such as name:
|
1134
|
+
# @param args [Hash] The data for creating an object, such as name:
|
1077
1135
|
# See {APIObject#initialize}
|
1078
1136
|
#
|
1079
1137
|
# @return [APIObject] The un-created ruby-instance of a JSS object
|
@@ -1084,8 +1142,9 @@ module Jamf
|
|
1084
1142
|
raise Jamf::UnsupportedError, "Creating #{self.class::RSRC_LIST_KEY} isn't yet supported. Please use other Casper workflows."
|
1085
1143
|
end
|
1086
1144
|
raise ArgumentError, "Use '#{self.class}.fetch id: xx' to retrieve existing JSS objects" if args[:id]
|
1087
|
-
|
1088
|
-
args[:
|
1145
|
+
|
1146
|
+
args[:cnx] ||= args[:api] # deprecated
|
1147
|
+
args[:cnx] ||= Jamf.cnx
|
1089
1148
|
args[:id] = :new
|
1090
1149
|
new(**args)
|
1091
1150
|
end
|
@@ -128,8 +128,9 @@ module Jamf
|
|
128
128
|
|
129
129
|
# Fetch either an internal or external patch source
|
130
130
|
#
|
131
|
-
# BUG: there's an API bug
|
132
|
-
# which is why we rescue internal server errors
|
131
|
+
# BUG: there's an API bug when fetching a non-existent patch source
|
132
|
+
# which is why we rescue 500 internal server errors and report them
|
133
|
+
# as 'no matching patch source'
|
133
134
|
#
|
134
135
|
# @see APIObject.fetch
|
135
136
|
#
|
@@ -162,7 +163,7 @@ module Jamf
|
|
162
163
|
# @see APIObject.make
|
163
164
|
#
|
164
165
|
def self.create(**args)
|
165
|
-
case
|
166
|
+
case name
|
166
167
|
when 'Jamf::PatchSource'
|
167
168
|
Jamf::PatchExternalSource.make args
|
168
169
|
when 'Jamf::PatchExternalSource'
|
@@ -184,7 +185,7 @@ module Jamf
|
|
184
185
|
def self.delete(victims, api: nil, cnx: Jamf.cnx)
|
185
186
|
cnx = api if api
|
186
187
|
|
187
|
-
case
|
188
|
+
case name
|
188
189
|
when 'Jamf::PatchSource'
|
189
190
|
Jamf::PatchExternalSource victims, cnx: cnx
|
190
191
|
when 'Jamf::PatchExternalSource'
|
@@ -292,6 +293,7 @@ module Jamf
|
|
292
293
|
|
293
294
|
return :internel if Jamf::PatchInternalSource.valid_id ident, refresh, cnx: cnx
|
294
295
|
return :external if Jamf::PatchExternalSource.valid_id ident, refresh, cnx: cnx
|
296
|
+
|
295
297
|
nil
|
296
298
|
end
|
297
299
|
|
@@ -324,7 +326,10 @@ module Jamf
|
|
324
326
|
|
325
327
|
# Init
|
326
328
|
def initialize(**args)
|
327
|
-
|
329
|
+
if instance_of?(Jamf::PatchSource)
|
330
|
+
raise Jamf::UnsupportedError,
|
331
|
+
'PatchSource is an abstract metaclass. Please use PatchInternalSource or PatchExternalSource'
|
332
|
+
end
|
328
333
|
|
329
334
|
super
|
330
335
|
|
data/lib/jamf/api/connection.rb
CHANGED
@@ -106,7 +106,7 @@ module Jamf
|
|
106
106
|
# If you provide connection details when calling 'new', they will be passed
|
107
107
|
# to the {#connect} method immediately. Otherwise you can call {#connect} later.
|
108
108
|
#
|
109
|
-
#
|
109
|
+
# production_server = Jamf::Connection.new(
|
110
110
|
# 'https://produser@prodserver.address.org:8443/'
|
111
111
|
# name: 'prod',
|
112
112
|
# pw: :prompt
|
@@ -24,6 +24,7 @@
|
|
24
24
|
#
|
25
25
|
|
26
26
|
module Jamf
|
27
|
+
|
27
28
|
# A Collection Resource in Jamf Pro
|
28
29
|
#
|
29
30
|
# See {Jamf::Resource} for general info about API resources.
|
@@ -60,6 +61,7 @@ module Jamf
|
|
60
61
|
# @abstract
|
61
62
|
######################################
|
62
63
|
module CollectionResource
|
64
|
+
|
63
65
|
include Jamf::JPAPIResource
|
64
66
|
|
65
67
|
# when this module is included, also extend our Class Methods
|
@@ -71,6 +73,7 @@ module Jamf
|
|
71
73
|
# Class Methods
|
72
74
|
#####################################
|
73
75
|
module ClassMethods
|
76
|
+
|
74
77
|
# 'include' all of these, so their methods become defined in this
|
75
78
|
# module, and will become Class Methods when this module
|
76
79
|
# is extended.
|
@@ -128,7 +131,6 @@ module Jamf
|
|
128
131
|
@patch_path ||= defined?(self::PATCH_PATH) ? self::PATCH_PATH : self::LIST_PATH
|
129
132
|
end
|
130
133
|
|
131
|
-
|
132
134
|
# The path for POSTing to create a single object in the collection.
|
133
135
|
#
|
134
136
|
# Classes including CollectionResource really need to define POST_PATH if it
|
@@ -256,7 +258,6 @@ module Jamf
|
|
256
258
|
#
|
257
259
|
######################################
|
258
260
|
def all(sort: nil, filter: nil, instantiate: false, cnx: Jamf.cnx, refresh: nil)
|
259
|
-
|
260
261
|
# if we are here, we need to query for all items, possibly filtered and
|
261
262
|
# sorted
|
262
263
|
sort = Jamf::Sortable.parse_url_sort_param(sort)
|
@@ -287,7 +288,6 @@ module Jamf
|
|
287
288
|
# arbitrary pages from the collection.
|
288
289
|
#
|
289
290
|
def pager(page_size: Jamf::Pager::DEFAULT_PAGE_SIZE, sort: nil, filter: nil, instantiate: false, cnx: Jamf.cnx)
|
290
|
-
|
291
291
|
sort = Jamf::Sortable.parse_url_sort_param(sort)
|
292
292
|
filter = filterable? ? Jamf::Filterable.parse_url_filter_param(filter) : nil
|
293
293
|
|
@@ -325,7 +325,6 @@ module Jamf
|
|
325
325
|
|
326
326
|
raise Jamf::NoSuchItemError, "No attribute :#{to} for class #{self}" unless self::OAPI_PROPERTIES.key? to
|
327
327
|
|
328
|
-
|
329
328
|
list = cached_list || all(cnx: cnx)
|
330
329
|
to_class = self::OAPI_PROPERTIES[to][:class]
|
331
330
|
to_multi = self::OAPI_PROPERTIES[to][:multi]
|
@@ -362,7 +361,7 @@ module Jamf
|
|
362
361
|
# raw_data some_value
|
363
362
|
#
|
364
363
|
# If some_value is an integer or a string containing an integer, it
|
365
|
-
# is assumed to be an :id otherwise all the available identifers
|
364
|
+
# is assumed to be an :id, otherwise all the available identifers
|
366
365
|
# are searched, in the order you see them when you call <class>.identifiers
|
367
366
|
#
|
368
367
|
# If no matching object is found, nil is returned.
|
@@ -382,8 +381,6 @@ module Jamf
|
|
382
381
|
#
|
383
382
|
######################################
|
384
383
|
def raw_data(searchterm = nil, ident: nil, value: nil, cnx: Jamf.cnx)
|
385
|
-
|
386
|
-
|
387
384
|
# given a value with no ident key
|
388
385
|
return raw_data_by_searchterm_only(searchterm, cnx: cnx) if searchterm
|
389
386
|
|
@@ -491,15 +488,13 @@ module Jamf
|
|
491
488
|
#
|
492
489
|
# @return [Boolean]
|
493
490
|
######################################
|
494
|
-
|
491
|
+
def creatable?
|
495
492
|
true
|
496
493
|
end
|
497
494
|
|
498
495
|
# Make a new thing to be added to the API
|
499
496
|
######################################
|
500
497
|
def create(**params)
|
501
|
-
|
502
|
-
|
503
498
|
# no such animal when .creating
|
504
499
|
params.delete :id
|
505
500
|
|
@@ -582,7 +577,7 @@ module Jamf
|
|
582
577
|
def delete(*ids, cnx: Jamf.cnx)
|
583
578
|
raise Jamf::UnsupportedError, "Deleting #{self} objects is not currently supported" unless deletable?
|
584
579
|
|
585
|
-
return bulk_delete(ids, cnx: Jamf.cnx) if
|
580
|
+
return bulk_delete(ids, cnx: Jamf.cnx) if bulk_deletable?
|
586
581
|
|
587
582
|
errs = []
|
588
583
|
ids.each do |id_to_delete|
|
@@ -650,7 +645,6 @@ module Jamf
|
|
650
645
|
raise NoMethodError, "no method '#{list_method_name}': '#{attr_name}' is not an indentifier for #{self}"
|
651
646
|
end
|
652
647
|
end
|
653
|
-
|
654
648
|
end # create_identifier_list_method
|
655
649
|
private :create_identifier_list_method
|
656
650
|
|
@@ -758,5 +752,7 @@ module Jamf
|
|
758
752
|
raise Jamf::MissingDataError, "Attribute '#{attr_name}' cannot be nil, must be a #{attr_def[:class]}"
|
759
753
|
end
|
760
754
|
end
|
755
|
+
|
761
756
|
end # class CollectionResource
|
757
|
+
|
762
758
|
end # module JAMF
|