ruby-jss 1.2.3 → 1.2.4a1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/jamf.rb +169 -0
- data/lib/jamf/api/abstract_classes/collection_resource.rb +422 -0
- data/lib/jamf/api/abstract_classes/generic_reference.rb +145 -0
- data/lib/jamf/api/abstract_classes/json_object.rb +1074 -0
- data/lib/jamf/api/abstract_classes/prestage.rb +219 -0
- data/lib/jamf/api/abstract_classes/prestage_skip_setup_items.rb +126 -0
- data/lib/jamf/api/abstract_classes/resource.rb +250 -0
- data/lib/jamf/api/abstract_classes/singleton_resource.rb +87 -0
- data/lib/jamf/api/attribute_classes/ip_address.rb +66 -0
- data/lib/jamf/api/attribute_classes/timestamp.rb +144 -0
- data/lib/jamf/api/connection.rb +734 -0
- data/lib/jamf/api/connection/api_error.rb +111 -0
- data/lib/jamf/api/connection/api_error_styleguide.rb +96 -0
- data/lib/jamf/api/connection/token.rb +220 -0
- data/lib/jamf/api/json_objects/account_prefs.rb +79 -0
- data/lib/jamf/api/json_objects/android_details.rb +139 -0
- data/lib/jamf/api/json_objects/appletv_details.rb +110 -0
- data/lib/jamf/api/json_objects/attachment.rb +68 -0
- data/lib/jamf/api/json_objects/cellular_network.rb +151 -0
- data/lib/jamf/api/json_objects/change_log_entry.rb +77 -0
- data/lib/jamf/api/json_objects/computer_prestage_skip_setup_items.rb +67 -0
- data/lib/jamf/api/json_objects/country.rb +51 -0
- data/lib/jamf/api/json_objects/extension_attribute_value.rb +128 -0
- data/lib/jamf/api/json_objects/installed_application.rb +59 -0
- data/lib/jamf/api/json_objects/installed_certificate.rb +53 -0
- data/lib/jamf/api/json_objects/installed_configuration_profile.rb +67 -0
- data/lib/jamf/api/json_objects/installed_ebook.rb +58 -0
- data/lib/jamf/api/json_objects/installed_provisioning_profile.rb +59 -0
- data/lib/jamf/api/json_objects/inventory_preload_extension_attribute.rb +52 -0
- data/lib/jamf/api/json_objects/ios_details.rb +244 -0
- data/lib/jamf/api/json_objects/location.rb +95 -0
- data/lib/jamf/api/json_objects/md_prestage_name.rb +57 -0
- data/lib/jamf/api/json_objects/md_prestage_names.rb +82 -0
- data/lib/jamf/api/json_objects/md_prestage_skip_setup_items.rb +165 -0
- data/lib/jamf/api/json_objects/mobile_device_details.rb +219 -0
- data/lib/jamf/api/json_objects/mobile_device_security.rb +101 -0
- data/lib/jamf/api/json_objects/prestage_assignment.rb +61 -0
- data/lib/jamf/api/json_objects/prestage_location.rb +104 -0
- data/lib/jamf/api/json_objects/prestage_purchasing_data.rb +132 -0
- data/lib/jamf/api/json_objects/prestage_scope.rb +54 -0
- data/lib/jamf/api/json_objects/prestage_sync_status.rb +63 -0
- data/lib/jamf/api/json_objects/purchasing_data.rb +125 -0
- data/lib/jamf/api/mixins/abstract.rb +58 -0
- data/lib/jamf/api/mixins/bulk_deletable.rb +39 -0
- data/lib/jamf/api/mixins/change_log.rb +136 -0
- data/lib/jamf/api/mixins/extendable.rb +75 -0
- data/lib/jamf/api/mixins/immutable.rb +39 -0
- data/lib/jamf/api/mixins/locatable.rb +124 -0
- data/lib/jamf/api/mixins/lockable.rb +48 -0
- data/lib/jamf/api/mixins/referable.rb +92 -0
- data/lib/jamf/api/mixins/searchable.rb +202 -0
- data/lib/jamf/api/mixins/uncreatable.rb +40 -0
- data/lib/jamf/api/mixins/undeletable.rb +40 -0
- data/lib/jamf/api/resources/collection_resources/account.rb +163 -0
- data/lib/jamf/api/resources/collection_resources/building.rb +114 -0
- data/lib/jamf/api/resources/collection_resources/category.rb +82 -0
- data/lib/jamf/api/resources/collection_resources/computer.rb +49 -0
- data/lib/jamf/api/resources/collection_resources/computer_prestage.rb +80 -0
- data/lib/jamf/api/resources/collection_resources/department.rb +79 -0
- data/lib/jamf/api/resources/collection_resources/extension_attribute.rb +45 -0
- data/lib/jamf/api/resources/collection_resources/inventory_preload_record.rb +274 -0
- data/lib/jamf/api/resources/collection_resources/md_prestage.rb +139 -0
- data/lib/jamf/api/resources/collection_resources/mobile_device.rb +315 -0
- data/lib/jamf/api/resources/collection_resources/script.rb +190 -0
- data/lib/jamf/api/resources/collection_resources/site.rb +77 -0
- data/lib/jamf/api/resources/singleton_resources/app_store_country_codes.rb +131 -0
- data/lib/jamf/api/resources/singleton_resources/authorization.rb +88 -0
- data/lib/jamf/api/resources/singleton_resources/client_checkin_settings.rb +139 -0
- data/lib/jamf/api/resources/singleton_resources/reenrollment_settings.rb +95 -0
- data/lib/jamf/client.rb +301 -0
- data/lib/jamf/client/jamf_binary.rb +132 -0
- data/lib/jamf/client/jamf_helper.rb +298 -0
- data/lib/jamf/client/management_action.rb +114 -0
- data/lib/jamf/compatibility.rb +88 -0
- data/lib/jamf/composer.rb +190 -0
- data/lib/jamf/configuration.rb +281 -0
- data/lib/jamf/exceptions.rb +107 -0
- data/lib/jamf/ruby_extensions.rb +36 -0
- data/lib/jamf/ruby_extensions/array.rb +35 -0
- data/lib/jamf/ruby_extensions/array/predicates.rb +46 -0
- data/lib/jamf/ruby_extensions/array/utils.rb +47 -0
- data/lib/jamf/ruby_extensions/filetest.rb +32 -0
- data/lib/jamf/ruby_extensions/filetest/predicates.rb +46 -0
- data/lib/jamf/ruby_extensions/hash.rb +33 -0
- data/lib/jamf/ruby_extensions/hash/backports.rb +92 -0
- data/lib/jamf/ruby_extensions/ipaddr.rb +37 -0
- data/lib/jamf/ruby_extensions/ipaddr/utils.rb +95 -0
- data/lib/jamf/ruby_extensions/object.rb +30 -0
- data/lib/jamf/ruby_extensions/object/predicates.rb +51 -0
- data/lib/jamf/ruby_extensions/pathname.rb +39 -0
- data/lib/jamf/ruby_extensions/pathname/predicates.rb +50 -0
- data/lib/jamf/ruby_extensions/pathname/utils.rb +75 -0
- data/lib/jamf/ruby_extensions/string.rb +35 -0
- data/lib/jamf/ruby_extensions/string/backports.rb +66 -0
- data/lib/jamf/ruby_extensions/string/conversions.rb +65 -0
- data/lib/jamf/ruby_extensions/string/predicates.rb +47 -0
- data/lib/jamf/utility.rb +423 -0
- data/lib/jamf/validate.rb +224 -0
- data/lib/jamf/version.rb +32 -0
- data/lib/jpapi.rb +26 -0
- data/lib/jss/version.rb +1 -1
- metadata +104 -4
|
@@ -0,0 +1,145 @@
|
|
|
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
|
+
module Jamf
|
|
27
|
+
|
|
28
|
+
# This class is a reference to an individual API object from some other
|
|
29
|
+
# API object.
|
|
30
|
+
#
|
|
31
|
+
# This class is subclassed automatically when the {Jamf::Referable} module
|
|
32
|
+
# is included into a class.
|
|
33
|
+
#
|
|
34
|
+
# See {Jamf::Referable} for how to use the subclasses of GenericReference.
|
|
35
|
+
#
|
|
36
|
+
# Subclasses must define:
|
|
37
|
+
#
|
|
38
|
+
# REFERENT_CLASS - the full class to which this is a reference
|
|
39
|
+
# e.g. for BuildingReference it would be Jamf::Building
|
|
40
|
+
#
|
|
41
|
+
# Defining REFERENT_CLASS is handled automatically by including the
|
|
42
|
+
# Referable module
|
|
43
|
+
#
|
|
44
|
+
# @abstract
|
|
45
|
+
#
|
|
46
|
+
class GenericReference < Jamf::JSONObject
|
|
47
|
+
|
|
48
|
+
extend Jamf::Abstract
|
|
49
|
+
|
|
50
|
+
# Constants
|
|
51
|
+
#####################################
|
|
52
|
+
|
|
53
|
+
OBJECT_MODEL = {
|
|
54
|
+
|
|
55
|
+
id: {
|
|
56
|
+
class: :integer,
|
|
57
|
+
identifier: :primary,
|
|
58
|
+
readonly: true
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
name: {
|
|
62
|
+
class: :string,
|
|
63
|
+
readonly: true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}.freeze
|
|
67
|
+
parse_object_model
|
|
68
|
+
|
|
69
|
+
# Constructor
|
|
70
|
+
#####################################
|
|
71
|
+
|
|
72
|
+
# Make a new reference to an API CollectionResource Object
|
|
73
|
+
#
|
|
74
|
+
# The data parameter can be one of:
|
|
75
|
+
#
|
|
76
|
+
# 1) A Hash with an :id and :name
|
|
77
|
+
# This is mostly used automatically when parsing fetched API data.
|
|
78
|
+
# When some attribute of an OBJECT_MODEL has `class: Someclass::Reference`
|
|
79
|
+
# the JSON hash from the API will be passed as the data param.
|
|
80
|
+
#
|
|
81
|
+
# e.g.
|
|
82
|
+
# - Policy::OBJECT_MODEL[:category][:class] is Jamf::Category::Reference
|
|
83
|
+
# - the policy JSON from the api might contain `category: { id: 234, name: 'foobar' }`
|
|
84
|
+
# - that hash will be passed into Jamf::Category::Reference.new, and the
|
|
85
|
+
# resulting instance used as the value of the policy's :category attribute.
|
|
86
|
+
#
|
|
87
|
+
# 2) An instance of the REFERENT_CLASS.
|
|
88
|
+
# This can be used to make a reference to some specific instance of
|
|
89
|
+
# the referent class.
|
|
90
|
+
#
|
|
91
|
+
# e.g. if you have an instance of Category in the variable `my_cat`
|
|
92
|
+
# then `ref_to_my_cat = Category::Reference.new my_cat` will work as
|
|
93
|
+
# expected.
|
|
94
|
+
#
|
|
95
|
+
# 3) A valid identifier for an existing REFERENT_CLASS in the JSS.
|
|
96
|
+
# The given value will be used with the REFERENT_CLASS's .valid_id method
|
|
97
|
+
# to see if there's a matching instance, which the reference refers to.
|
|
98
|
+
#
|
|
99
|
+
# e.g. `ref_to_my_cat = Category::Reference.new 12` creates a reference
|
|
100
|
+
# to Category id 12, and `ref_to_my_cat = Category::Reference.new 'foo'`
|
|
101
|
+
# creates a reference to the category named 'foo' - assuming they exist.
|
|
102
|
+
#
|
|
103
|
+
# The last two of these are commonly used with setters for attributes that
|
|
104
|
+
# have class: <some reference class>
|
|
105
|
+
#
|
|
106
|
+
# e.g. setting the category of a policy when
|
|
107
|
+
# Policy::OBJECT_MODEL[:category] is Category::Reference
|
|
108
|
+
#
|
|
109
|
+
# `mypolicy.category = a_cat` # a_cat is a Category instance
|
|
110
|
+
# `mypolicy.category = 12` # use categoty id 12
|
|
111
|
+
# `mypolicy.category = 'foo'` # use categoty named 'foo'
|
|
112
|
+
#
|
|
113
|
+
#
|
|
114
|
+
#
|
|
115
|
+
# @param data[Hash,CollectionResource,String,Integer]
|
|
116
|
+
#
|
|
117
|
+
def initialize(data, cnx: Jamf.cnx)
|
|
118
|
+
ref_class = self.class::REFERENT_CLASS
|
|
119
|
+
case data
|
|
120
|
+
when Hash
|
|
121
|
+
super
|
|
122
|
+
when ref_class
|
|
123
|
+
raise Jamf::InvalidDataError, "Provided #{ref_class} hasn't been created" unless data.exist?
|
|
124
|
+
@id = data.id
|
|
125
|
+
@name = data.name if data.respond_to? :name
|
|
126
|
+
@cnx = data.cnx
|
|
127
|
+
when nil
|
|
128
|
+
@id = nil
|
|
129
|
+
@name = nil
|
|
130
|
+
@cnx = cnx
|
|
131
|
+
else
|
|
132
|
+
@id = ref_class.valid_id data, cnx: cnx
|
|
133
|
+
raise "No matching #{ref_class}" unless @id
|
|
134
|
+
@name = ref_class.map_all(:id, to: :name, cnx: cnx)[@id]
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def to_jamf
|
|
139
|
+
return nil if @id.nil?
|
|
140
|
+
{ id: @id }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
end # class
|
|
144
|
+
|
|
145
|
+
end # module
|
|
@@ -0,0 +1,1074 @@
|
|
|
1
|
+
# Copyright 2019 Pixar
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "Apache License")
|
|
4
|
+
# with the following modification; you may not use this file except in
|
|
5
|
+
# compliance with the Apache License and the following modification to it:
|
|
6
|
+
# Section 6. Trademarks. is deleted and replaced with:
|
|
7
|
+
#
|
|
8
|
+
# 6. Trademarks. This License does not grant permission to use the trade
|
|
9
|
+
# names, trademarks, service marks, or product names of the Licensor
|
|
10
|
+
# and its affiliates, except as required to comply with Section 4(c) of
|
|
11
|
+
# the License and to reproduce the content of the NOTICE file.
|
|
12
|
+
#
|
|
13
|
+
# You may obtain a copy of the Apache License at
|
|
14
|
+
#
|
|
15
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
16
|
+
#
|
|
17
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
18
|
+
# distributed under the Apache License with the above modification is
|
|
19
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
20
|
+
# KIND, either express or implied. See the Apache License for the specific
|
|
21
|
+
# language governing permissions and limitations under the Apache License.
|
|
22
|
+
#
|
|
23
|
+
#
|
|
24
|
+
|
|
25
|
+
# The module
|
|
26
|
+
module Jamf
|
|
27
|
+
|
|
28
|
+
# Classes
|
|
29
|
+
#####################################
|
|
30
|
+
|
|
31
|
+
# # Jamf::JSONObject
|
|
32
|
+
#
|
|
33
|
+
# In JSON & Javascript, an 'object' is a data structure equivalent to a
|
|
34
|
+
# Hash in Ruby. Much of the JSON data exchaged with the API is formatted as
|
|
35
|
+
# these JSON objects.
|
|
36
|
+
#
|
|
37
|
+
# Jamf::JSONObject is a meta class that provides a way to convert those JSON
|
|
38
|
+
# 'objects' into not just Hashes (that's done by the Jamf::Connection) but
|
|
39
|
+
# into full-fledged ruby Classes. Once implemented in ruby-jss, all JSON
|
|
40
|
+
# objects (Hashes) used anywhere in the Jamf Pro API have a matching Class in
|
|
41
|
+
# ruby-jss which is a subclass of Jamf::JSONObject
|
|
42
|
+
#
|
|
43
|
+
# The Jamf::JSONObject class is abstract, and cannot be instantiated or used
|
|
44
|
+
# directly. It merely provides the common functionality needed for dealing
|
|
45
|
+
# with all JSON objects in the API.
|
|
46
|
+
#
|
|
47
|
+
#
|
|
48
|
+
# ## Subclassing
|
|
49
|
+
#
|
|
50
|
+
# When implementing a JSON object in the API as a class in ruby-jss,
|
|
51
|
+
# you will make a subclass of either Jamf::JSONObject, Jamf::SingletonResource
|
|
52
|
+
# or Jamf::CollectionResource.
|
|
53
|
+
#
|
|
54
|
+
# Here's the relationship between these MetaClasses:
|
|
55
|
+
#
|
|
56
|
+
# Jamf::JSONObject
|
|
57
|
+
# (abstract)
|
|
58
|
+
# |
|
|
59
|
+
# |
|
|
60
|
+
# -----------------------
|
|
61
|
+
# | |
|
|
62
|
+
# Jamf::Resource |
|
|
63
|
+
# (abstract) |
|
|
64
|
+
# | |
|
|
65
|
+
# | |
|
|
66
|
+
# | Jamf::Computer::Reference
|
|
67
|
+
# | Jamf::Location
|
|
68
|
+
# | Jamf::ChangeLog::Entry
|
|
69
|
+
# | (more non-resource JSON object classes)
|
|
70
|
+
# |
|
|
71
|
+
# |----------------------------------------
|
|
72
|
+
# | |
|
|
73
|
+
# | |
|
|
74
|
+
# Jamf::SingletonResource Jamf::CollectionResource
|
|
75
|
+
# (abstract) (abstract)
|
|
76
|
+
# | |
|
|
77
|
+
# | |
|
|
78
|
+
# Jamf::Settings::ReEnrollment Jamf::Computer
|
|
79
|
+
# Jamf::Settings::SelfService Jamf::Building
|
|
80
|
+
# Jamf::SystemInfo Jamf::PatchPolicy
|
|
81
|
+
# (more singleton resource classes) (more collection resource classes)
|
|
82
|
+
#
|
|
83
|
+
#
|
|
84
|
+
# Direct descendents of Jamf::JSONObject are arbitrary JSON objects that
|
|
85
|
+
# appear inside other objects, e.g. the Location data for a computer,
|
|
86
|
+
# or a reference to a building.
|
|
87
|
+
#
|
|
88
|
+
# {Jamf::Resource} classes represent direct resources of the API, i.e. items
|
|
89
|
+
# accessible with a URL. The ability to interact with those URLs is defined in
|
|
90
|
+
# the metaclass Jamf::Resource, and all resources must define a RSRC_VERSION
|
|
91
|
+
# and a RSRC_PATH. See {Jamf::Resource} for more info.
|
|
92
|
+
#
|
|
93
|
+
# There are two kinds of resources in the API:
|
|
94
|
+
#
|
|
95
|
+
# {Jamf::SingletonResource} classes represent objects in the API that have
|
|
96
|
+
# only one instance, such as various settings, or server-wide state. These
|
|
97
|
+
# objects cannot be created or deleted, only fetched and updated.
|
|
98
|
+
#
|
|
99
|
+
# {Jamf::CollectionResource} classes represent collections of objects in the
|
|
100
|
+
# API. These resources can list all of their members, and individual members
|
|
101
|
+
# can be retrieved, updated, created and deleted.
|
|
102
|
+
#
|
|
103
|
+
# Subclasses need to meet the requirements for all of their ancestors,
|
|
104
|
+
# so once you decide which one you're subclassing, be sure to read the docs
|
|
105
|
+
# for each one. E.g. to implement Jamf::Package, it will be a
|
|
106
|
+
# {Jamf::CollectionResource}, which is a {Jamf::Resource}, which is a
|
|
107
|
+
# {Jamf::JSONObject}, and the requirements for all must be met.
|
|
108
|
+
#
|
|
109
|
+
# The remainder of this page documents the requirements and details of
|
|
110
|
+
# Jamf::JSONObject.
|
|
111
|
+
#
|
|
112
|
+
#
|
|
113
|
+
# NOTES:
|
|
114
|
+
#
|
|
115
|
+
# - subclasses may define more methods, include mix-ins, and if
|
|
116
|
+
# needed can override methods defined in metaclasses. Please read the
|
|
117
|
+
# docs before overriding.
|
|
118
|
+
#
|
|
119
|
+
# - Throughout the documentation 'parsed JSON object' means the result of running
|
|
120
|
+
# a raw JSON string thru `JSON.parse raw_json, symbolize_names: true`. This
|
|
121
|
+
# is performed in the {Jamf::Connection} methods which interact with the API:
|
|
122
|
+
# {Jamf::Connection#get}, {Jamf::Connection#post}, {Jamf::Connection#put}
|
|
123
|
+
# {Jamf::Connection#patch} and {Jamf::Connection#delete}.
|
|
124
|
+
#
|
|
125
|
+
# - Related to the above, the {Jamf::Connection} methods
|
|
126
|
+
# {Jamf::Connection#post} and {Jamf::Connection#put} call `#to_json` on the
|
|
127
|
+
# data passed to them, before sending it to the API. Subclasses and
|
|
128
|
+
# application code should never call #to_json anywhere. The data passed
|
|
129
|
+
# to put and post should be the output of `#to_jamf` on a Jamf::JSONObject,
|
|
130
|
+
# which is handled by the the #update and #create methods as needed.
|
|
131
|
+
#
|
|
132
|
+
#
|
|
133
|
+
# ###
|
|
134
|
+
#
|
|
135
|
+
# ### Required Constant: OBJECT_MODEL & call to parse_object_model
|
|
136
|
+
#
|
|
137
|
+
# Each descendent of JSONObject must define the constant OBJECT_MODEL, which
|
|
138
|
+
# is a Hash of Hashes that collectively define the top-level keys of the JSON
|
|
139
|
+
# object as attributes of the matching ruby class.
|
|
140
|
+
#
|
|
141
|
+
# Immediately after the definition of OBJECT_MODEL, the subclass *MUST* call
|
|
142
|
+
# `self.parse_object_model` to convert the model into actual ruby attributes
|
|
143
|
+
# with getters and setters.
|
|
144
|
+
#
|
|
145
|
+
# The OBJECT_MODEL Hash directly implements the matching JSON object model
|
|
146
|
+
# defined at https://developer.jamf.com/apis/jamf-pro-api/index and is used
|
|
147
|
+
# to automatically create attributes & accessor methods mirroring those
|
|
148
|
+
# in the API.
|
|
149
|
+
#
|
|
150
|
+
# The keys of the main hash are the symbolized names of the attributes as they
|
|
151
|
+
# come from the JSON fetched from the API.
|
|
152
|
+
#
|
|
153
|
+
# _ATTRIBUTE NAMES:_
|
|
154
|
+
#
|
|
155
|
+
# The attribute names in the Jamf Pro API JSON data are in 'lowerCamelCase'
|
|
156
|
+
# (https://en.wikipedia.org/wiki/Camel_case), and are used that way
|
|
157
|
+
# throughout the Jamf module in order to maintain consistency with the API
|
|
158
|
+
# itself. This differs from the ruby standard of using 'snake_case'
|
|
159
|
+
# (https://en.wikipedia.org/wiki/Snake_case) for attributes,
|
|
160
|
+
# methods, & local variables. I believe that maintaining consistency with the
|
|
161
|
+
# API we are mirroring is more important (and simpler) than conforming with
|
|
162
|
+
# ruby's community standards. I also believe that doing so is in-line with the
|
|
163
|
+
# ruby community's larger philosophy.
|
|
164
|
+
#
|
|
165
|
+
# "There's more than one way to do it" - because context matters.
|
|
166
|
+
# If that weren't true, I'd be writing Python.
|
|
167
|
+
#
|
|
168
|
+
# Each attribute key has a Hash of details defining how the attribute is
|
|
169
|
+
# used in the class. Getters and setters are created from these details, and
|
|
170
|
+
# they are used to parse incoming, and generate outgoing JSON data
|
|
171
|
+
#
|
|
172
|
+
# The possible keys of the details Hash for each attribute are:
|
|
173
|
+
#
|
|
174
|
+
# - class:
|
|
175
|
+
# - identfier:
|
|
176
|
+
# - required:
|
|
177
|
+
# - readonly:
|
|
178
|
+
# - multi:
|
|
179
|
+
# - enum:
|
|
180
|
+
# - validator:
|
|
181
|
+
# - aliases:
|
|
182
|
+
#
|
|
183
|
+
# For an example of an OBJECT_MODEL hash, see {Jamf::MobileDeviceDetails::OBJECT_MODEL}
|
|
184
|
+
#
|
|
185
|
+
# The details for each key's value are as follows. Note that omitting a
|
|
186
|
+
# boolean key is the same as setting it to false.
|
|
187
|
+
#
|
|
188
|
+
# class: \[Symbol or Class]
|
|
189
|
+
# -----------------
|
|
190
|
+
# This is the only required key for all attributes.
|
|
191
|
+
#
|
|
192
|
+
# Symbol is one of :string, :integer, :float, or :boolean
|
|
193
|
+
# These are the JSON data types that don't need parsing into ruby
|
|
194
|
+
# beyond that done by `JSON.parse`. When processing an attribute with one of
|
|
195
|
+
# these symbols as the `class:`, the JSON value is used as-is.
|
|
196
|
+
#
|
|
197
|
+
# When this is not a Symbol, it must be an actual class, such as
|
|
198
|
+
# Jamf::Timestamp or Jamf::PurchasingData.
|
|
199
|
+
#
|
|
200
|
+
# Classes used this way _must_:
|
|
201
|
+
#
|
|
202
|
+
# - Have an #initialize method that takes two parameters and performs
|
|
203
|
+
# validation on them:
|
|
204
|
+
#
|
|
205
|
+
# A first positional parameter, the value used to create the instance,
|
|
206
|
+
# which accepts, at the very least, the Parsed JSON data for the attribute.
|
|
207
|
+
# This can be a single value (e.g. a string for Jamf::Timestamp), or a Hash
|
|
208
|
+
# (e.g. for Jamf::Location), or whatever. Other values are
|
|
209
|
+
# allowed if your initialize method handles them properly.
|
|
210
|
+
#
|
|
211
|
+
# A keyword parameter `cnx:`. This can be ignored if not needed, but
|
|
212
|
+
# #initialize must accept it. If used, it will contain a Jamf::Connection
|
|
213
|
+
# object, either the one from which the first param came, or the one
|
|
214
|
+
# to which we'll be validating or creating a new object
|
|
215
|
+
#
|
|
216
|
+
# - Define a #to_jamf method that returns a value that can be used
|
|
217
|
+
# in the data sent back to the API. Subclasses of JSONObject already
|
|
218
|
+
# have this requirement, and the value is a Hash.
|
|
219
|
+
#
|
|
220
|
+
#
|
|
221
|
+
# Classes used in the class: value of an attribute definition are often
|
|
222
|
+
# also subclasses of JSONObject (e.g. Jamf::Location) but do not have to be
|
|
223
|
+
# as long as they conform to the standards above, e.g. Jamf::Timestamp.
|
|
224
|
+
#
|
|
225
|
+
# See also: [Data Validation](#data_validation) below.
|
|
226
|
+
#
|
|
227
|
+
#
|
|
228
|
+
# identifier: \[Boolean or Symbol :primary]
|
|
229
|
+
# -----------------
|
|
230
|
+
# Only applicable to descendents of Jamf::CollectionResource
|
|
231
|
+
#
|
|
232
|
+
# If true, this value must be unique among all members of the class in
|
|
233
|
+
# the JAMF, and can be used to look up objects.
|
|
234
|
+
#
|
|
235
|
+
# If the symbol :primary, this is the primary identifier, used in API
|
|
236
|
+
# resource paths for this particular object. Usually its the :id attribute,
|
|
237
|
+
# but for some objects may be some other attribute, e.g. for config-
|
|
238
|
+
# profiles, it would be a uuid.
|
|
239
|
+
#
|
|
240
|
+
#
|
|
241
|
+
# required: \[Boolean]
|
|
242
|
+
# -----------------
|
|
243
|
+
# If true, this attribute must be provided when creating a new local instance
|
|
244
|
+
# with .make, and cannot be nil or empty when sending a new instance to the
|
|
245
|
+
# API with \#create
|
|
246
|
+
#
|
|
247
|
+
#
|
|
248
|
+
# readonly: \[Boolean]
|
|
249
|
+
# -----------------
|
|
250
|
+
# If true, no setter method(s) will be created, and the value is not
|
|
251
|
+
# sent to the API with #create or #update
|
|
252
|
+
#
|
|
253
|
+
#
|
|
254
|
+
# multi: \[Boolean]
|
|
255
|
+
# -----------------
|
|
256
|
+
# When true, this value comes as a JSON array and its items are defined by
|
|
257
|
+
# the 'class:' setting described above. The JSON array is used
|
|
258
|
+
# to contstruct an attribute array of the correct kind of item.
|
|
259
|
+
#
|
|
260
|
+
# Example:
|
|
261
|
+
# > When `class:` is Jamf::Computer::Reference the incoming JSON array
|
|
262
|
+
# > of Hashes (computer references) will become an array of
|
|
263
|
+
# > Jamf::Computer::Reference instances.
|
|
264
|
+
#
|
|
265
|
+
# The stored array is not directly accessible, the getter will return a
|
|
266
|
+
# frozen duplicate of it.
|
|
267
|
+
#
|
|
268
|
+
# If not readonly, several setters are created:
|
|
269
|
+
#
|
|
270
|
+
# - a direct setter which takes an Array of 'class:', replacing the original
|
|
271
|
+
# - a <attrname>\_append method, appends a new value to the array,
|
|
272
|
+
# aliased as `<<`
|
|
273
|
+
# - a <attrname>\_prepend method, prepends a new value to the array
|
|
274
|
+
# - a <attrname>\_insert method, inserts a new value to the array
|
|
275
|
+
# at the given index
|
|
276
|
+
# - a <attrname>\_delete\_at method, deletes a value at the given index
|
|
277
|
+
#
|
|
278
|
+
# This protection of the underlying array is needed for two reasons:
|
|
279
|
+
#
|
|
280
|
+
# 1. so ruby-jss knows when changes are made and need to be saved
|
|
281
|
+
# 2. so that validation can be performed on values added to the array.
|
|
282
|
+
#
|
|
283
|
+
#
|
|
284
|
+
# enum: \[Constant -> Array<Constants> ]
|
|
285
|
+
# -----------------
|
|
286
|
+
# This is a constant defined somewhere in the Jamf module. The constant
|
|
287
|
+
# must contain an Array of other Constant values, usually Strings.
|
|
288
|
+
#
|
|
289
|
+
# Example:
|
|
290
|
+
# > Attribute `:type` has enum: Jamf::ExtentionAttribute::DATA_TYPES
|
|
291
|
+
# >
|
|
292
|
+
# > The constant Jamf::ExtentionAttribute::DATA_TYPES is defined thus:
|
|
293
|
+
# >
|
|
294
|
+
# > DATA_TYPE_STRING = 'STRING'.freeze
|
|
295
|
+
# > DATA_TYPE_INTEGER = 'INTEGER'.freeze
|
|
296
|
+
# > DATA_TYPE_DATE = 'DATE'.freeze
|
|
297
|
+
# >
|
|
298
|
+
# > DATA_TYPES = [
|
|
299
|
+
# > DATA_TYPE_STRING,
|
|
300
|
+
# > DATA_TYPE_INTEGER,
|
|
301
|
+
# > DATA_TYPE_DATE,
|
|
302
|
+
# > ]
|
|
303
|
+
# >
|
|
304
|
+
# > When setting the type attribute via `#type = newval`,
|
|
305
|
+
# > `Jamf::ExtentionAttribute::DATA_TYPES.include? newval` must be true
|
|
306
|
+
# >
|
|
307
|
+
#
|
|
308
|
+
# Setters for attributes with an enum require that the new value is
|
|
309
|
+
# a member of the array as seen above. When using such setters, its wise to
|
|
310
|
+
# use the array members themselves rather than a different but identical string,
|
|
311
|
+
# however either will work. In other words, this:
|
|
312
|
+
#
|
|
313
|
+
# my_ea.dataType = Jamf::ExtentionAttribute::DATA_TYPE_INTEGER
|
|
314
|
+
#
|
|
315
|
+
# is preferred over:
|
|
316
|
+
#
|
|
317
|
+
# my_ea.dataType = 'INTEGER'
|
|
318
|
+
#
|
|
319
|
+
# since the second version creates a new string in memory, but the first uses
|
|
320
|
+
# the one already stored in a constant.
|
|
321
|
+
#
|
|
322
|
+
# See also: [Data Validation](#data_validation) below.
|
|
323
|
+
#
|
|
324
|
+
# validator: \[Symbol]
|
|
325
|
+
# -----------------
|
|
326
|
+
# (ignored if readonly: is true, or if enum: is set)
|
|
327
|
+
#
|
|
328
|
+
# The symbol is the name of a Jamf::Validators class method used in the
|
|
329
|
+
# setter to validate new values for this attribute. It only is used when
|
|
330
|
+
# class: is :string, :integer, :boolean, and :float
|
|
331
|
+
#
|
|
332
|
+
# If omitted, the setter will take any value passed to it, which is
|
|
333
|
+
# generally unwise.
|
|
334
|
+
#
|
|
335
|
+
# When the class: is an actual class, the setter will instantiate a new one
|
|
336
|
+
# with the value to be set, and validation is handled by the class itself.
|
|
337
|
+
#
|
|
338
|
+
# Example:
|
|
339
|
+
# > If the `class:` for an attrib named ':releaseDate' is class: Jamf::Timestamp
|
|
340
|
+
# > then the setter method will look like this:
|
|
341
|
+
# >
|
|
342
|
+
# > def releaseDate=(newval)
|
|
343
|
+
# > newval = Jamf::Timestamp.new newval unless newval.is_a? Jamf::Timestamp
|
|
344
|
+
# > # ^^^ This will validate newval
|
|
345
|
+
# > return if newval == @releaseDate
|
|
346
|
+
# > @releaseDate = newval
|
|
347
|
+
# > @need_to_update = true
|
|
348
|
+
# > end
|
|
349
|
+
#
|
|
350
|
+
# see also: [Data Validation](#data_validation) below.
|
|
351
|
+
#
|
|
352
|
+
#
|
|
353
|
+
# aliases: \[Array of Symbols]
|
|
354
|
+
# -----------------
|
|
355
|
+
# Other names for this attribute. If provided, getters, and setters will
|
|
356
|
+
# be made for all aliases. Should be used very sparingly.
|
|
357
|
+
#
|
|
358
|
+
# Attributes of class :boolean automatically have a getter alias ending with a '?'.
|
|
359
|
+
#
|
|
360
|
+
# Documenting your code
|
|
361
|
+
# ---------------------
|
|
362
|
+
# For documenting attributes with YARD, put this above each
|
|
363
|
+
# attribute name key:
|
|
364
|
+
#
|
|
365
|
+
# ```
|
|
366
|
+
# # @!attribute <attrname>
|
|
367
|
+
# # @param [Class] <Describe setter value if needed>
|
|
368
|
+
# # @return [Class] <Describe value if needed>
|
|
369
|
+
# ```
|
|
370
|
+
#
|
|
371
|
+
# If the value is readonly, remove the @param line, and add \[r], like this:
|
|
372
|
+
#
|
|
373
|
+
# ```
|
|
374
|
+
# # @!attribute [r] <attrname
|
|
375
|
+
# ```
|
|
376
|
+
#
|
|
377
|
+
# for more info see https://www.rubydoc.info/gems/yard/file/docs/Tags.md#attribute
|
|
378
|
+
#
|
|
379
|
+
#
|
|
380
|
+
# #### Sub-subclassing
|
|
381
|
+
#
|
|
382
|
+
# If you need to subclass a subclass of JSONObject, and the new subclass needs
|
|
383
|
+
# to expand on the OBJECT_MODEL in its parent, then you must use Hash#merge
|
|
384
|
+
# to combine them in the subclass. Here's an example of ComputerPrestage
|
|
385
|
+
# which inherits from Prestage:
|
|
386
|
+
#
|
|
387
|
+
# class ComputerPrestage < Jamf::Prestage
|
|
388
|
+
#
|
|
389
|
+
# OBJECT_MODEL = superclass::OBJECT_MODEL.merge(
|
|
390
|
+
#
|
|
391
|
+
# newAttr: {
|
|
392
|
+
# [attr details]
|
|
393
|
+
# }
|
|
394
|
+
#
|
|
395
|
+
# ).freeze
|
|
396
|
+
#
|
|
397
|
+
#
|
|
398
|
+
# #### Data Validation \{#data_validation}
|
|
399
|
+
#
|
|
400
|
+
# Attributes that are not readonly are subject to data validation when values are
|
|
401
|
+
# assigned. How that validation happens depends on the definition of the
|
|
402
|
+
# attribute as described above. Validation failure will raise an exception,
|
|
403
|
+
# usually Jamf::InvalidDataError.
|
|
404
|
+
#
|
|
405
|
+
# If the attribute is defined with an enum, the value must be
|
|
406
|
+
# a key or value of the enum.
|
|
407
|
+
#
|
|
408
|
+
# If the attribute's class: is defined as a Class, (e.g. Jamf::Timestamp)
|
|
409
|
+
# its .new method is called with the value and the current API connection.
|
|
410
|
+
# The class itself performs valuation when the value is used to
|
|
411
|
+
# instantiate it.
|
|
412
|
+
#
|
|
413
|
+
# If the attribute is defined with a validator, the value is passed
|
|
414
|
+
# to that validator.
|
|
415
|
+
#
|
|
416
|
+
# If the attribute is defined as a :string, :integer, :float or :bool
|
|
417
|
+
# without an enum or validator, it is checked to be the correct type
|
|
418
|
+
#
|
|
419
|
+
# If an attribute is an identifier, it must be unique in its class and
|
|
420
|
+
# API connection.
|
|
421
|
+
#
|
|
422
|
+
# ### Constructor / Instantiation {#constructor}
|
|
423
|
+
#
|
|
424
|
+
# The .new method should rarely (never?) be called directly for any JSONObject
|
|
425
|
+
# class.
|
|
426
|
+
#
|
|
427
|
+
# The Resource classes are instantiated via the .fetch and .create methods.
|
|
428
|
+
#
|
|
429
|
+
# Other JSONObject classes are embedded inside the Resource classes
|
|
430
|
+
# and are instantiated while parsing data from the API or by the setters for
|
|
431
|
+
# the attributes holding them.
|
|
432
|
+
#
|
|
433
|
+
# When subclassing JSONObject, you can often just use the #initialize defined
|
|
434
|
+
# here. You may want to override #initialize to accept different kinds of data
|
|
435
|
+
# and if you do, you _must_:
|
|
436
|
+
#
|
|
437
|
+
# - Have an #initialize method that takes two parameters and performs
|
|
438
|
+
# validation using them:
|
|
439
|
+
#
|
|
440
|
+
# 1. A positional first parameter: the value used to create the instance
|
|
441
|
+
# Your method may accept any kind of value, as long as it can use it
|
|
442
|
+
# to create a valid object. At the very least it _must_ accept a Hash
|
|
443
|
+
# that comes from the API for this object. If you call `super` then
|
|
444
|
+
# that Hash must be passed.
|
|
445
|
+
#
|
|
446
|
+
# For example, Jamf::GenericReference, which defines references to
|
|
447
|
+
# other resources, such as Buildings, can take a Hash containing the
|
|
448
|
+
# name: and id: of the building (as provided by the API), or can take
|
|
449
|
+
# just a name or id, or can take a Jamf::Building object.
|
|
450
|
+
#
|
|
451
|
+
# The initialize method must perform validation as necessary and raise
|
|
452
|
+
# an exception if the data provided is not acceptable.
|
|
453
|
+
#
|
|
454
|
+
# 2. A keyword parameter `cnx:` containing a Jamf::Connection instance.
|
|
455
|
+
# This is the API connection through which this JSON object interacts
|
|
456
|
+
# with the appropriate Jamf Pro server. Usually this is used to validate
|
|
457
|
+
# the data recieved in the first positional parameter.
|
|
458
|
+
#
|
|
459
|
+
# ### Required Instance Methods
|
|
460
|
+
#
|
|
461
|
+
# Subclasses of JSONObject must have a #to_jamf method.
|
|
462
|
+
# For most simple objects, the one defined in JSONObject will work as is.
|
|
463
|
+
#
|
|
464
|
+
# If you need to override it, it _must_
|
|
465
|
+
#
|
|
466
|
+
# - Return a Hash that can be used in the data sent back to the API.
|
|
467
|
+
# - Not call #.to_json. All conversion to and from JSON happens in the
|
|
468
|
+
# Jamf::Connection class.
|
|
469
|
+
#
|
|
470
|
+
# @abstract
|
|
471
|
+
#
|
|
472
|
+
class JSONObject
|
|
473
|
+
|
|
474
|
+
extend Jamf::Abstract
|
|
475
|
+
|
|
476
|
+
# Constants
|
|
477
|
+
#####################################
|
|
478
|
+
|
|
479
|
+
# These classes are used from JSON in the raw
|
|
480
|
+
JSON_TYPE_CLASSES = %i[string integer float boolean].freeze
|
|
481
|
+
|
|
482
|
+
# Predicate (boolean) attibutes that start with this RE will
|
|
483
|
+
# have an alias without the 'is' so :isManaged will have
|
|
484
|
+
# getters isManaged? and managed?
|
|
485
|
+
#
|
|
486
|
+
PREDICATE_RE = /^is([A-Z]\w*)$/.freeze
|
|
487
|
+
|
|
488
|
+
# Public Class Methods
|
|
489
|
+
#####################################
|
|
490
|
+
|
|
491
|
+
# By default, JSONObjects (as a whole) are mutable,
|
|
492
|
+
# although some attributes may not be (see OBJECT_MODEL in the JSONObject
|
|
493
|
+
# docs)
|
|
494
|
+
#
|
|
495
|
+
# When an entire sublcass of JSONObject is read-only/immutable,
|
|
496
|
+
# `extend Jamf::Immutable`, which will override this to return false.
|
|
497
|
+
# Doing so will prevent any setters from being created for the subclass
|
|
498
|
+
# and will cause Jamf::Resource.save to raise an error
|
|
499
|
+
#
|
|
500
|
+
def self.mutable?
|
|
501
|
+
true
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Given a Symbol that might be an alias of a key fron OBJECT_MODEL
|
|
505
|
+
# return the real key
|
|
506
|
+
#
|
|
507
|
+
# e.g. if OBJECT_MODEL has an entry like this:
|
|
508
|
+
# displayName: { aliases: [:name, :display_name] }
|
|
509
|
+
# Then
|
|
510
|
+
# attr_key_for_alias(:name) and attr_key_for_alias(:display_name)
|
|
511
|
+
# will return :displayName
|
|
512
|
+
#
|
|
513
|
+
# Returns nil if no such alias exists.
|
|
514
|
+
#
|
|
515
|
+
# @param als [Symbol] the alias to look up
|
|
516
|
+
#
|
|
517
|
+
# @return [Symbol, nil] The real object model key for the alias
|
|
518
|
+
#
|
|
519
|
+
def self.attr_key_for_alias(als)
|
|
520
|
+
validate_not_abstract
|
|
521
|
+
self::OBJECT_MODEL.each { |k, deets| return k if k == als || deets[:aliases]&.include?(als) }
|
|
522
|
+
nil
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Private Class Methods
|
|
526
|
+
#####################################
|
|
527
|
+
|
|
528
|
+
# create getters and setters for subclasses of APIObject
|
|
529
|
+
# based on their OBJECT_MODEL Hash.
|
|
530
|
+
#
|
|
531
|
+
##############################
|
|
532
|
+
def self.parse_object_model
|
|
533
|
+
return if @object_model_parsed
|
|
534
|
+
|
|
535
|
+
got_primary = false
|
|
536
|
+
need_list_methods = ancestors.include?(Jamf::CollectionResource)
|
|
537
|
+
|
|
538
|
+
self::OBJECT_MODEL.each do |attr_name, attr_def|
|
|
539
|
+
create_list_methods(attr_name, attr_def) if need_list_methods && attr_def[:identifier]
|
|
540
|
+
|
|
541
|
+
# there can be only one (primary ident)
|
|
542
|
+
if attr_def[:identifier] == :primary
|
|
543
|
+
raise Jamf::UnsupportedError, 'Two identifiers marked as :primary' if got_primary
|
|
544
|
+
|
|
545
|
+
got_primary = true
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
create_getters attr_name, attr_def
|
|
549
|
+
next if attr_def[:readonly]
|
|
550
|
+
|
|
551
|
+
create_setters attr_name, attr_def if mutable?
|
|
552
|
+
end # do |attr_name, attr_def|
|
|
553
|
+
|
|
554
|
+
@object_model_parsed = true
|
|
555
|
+
end # parse_object_model
|
|
556
|
+
private_class_method :parse_object_model
|
|
557
|
+
|
|
558
|
+
# create a getter for an attribute, and any aliases needed
|
|
559
|
+
##############################
|
|
560
|
+
def self.create_getters(attr_name, attr_def)
|
|
561
|
+
# multi_value - only return a frozen dup, no direct editing of Array
|
|
562
|
+
if attr_def[:multi]
|
|
563
|
+
define_method(attr_name) do
|
|
564
|
+
instance_variable_set("@#{attr_name}", []) unless instance_variable_get("@#{attr_name}").is_a?(Array)
|
|
565
|
+
instance_variable_get("@#{attr_name}").dup.freeze
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# single value
|
|
569
|
+
else
|
|
570
|
+
define_method(attr_name) { instance_variable_get("@#{attr_name}") }
|
|
571
|
+
|
|
572
|
+
# all booleans get a predicate alias
|
|
573
|
+
alias_method("#{attr_name}?", attr_name) if attr_def[:class] == :boolean
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
return unless attr_def[:aliases]
|
|
577
|
+
|
|
578
|
+
# aliases
|
|
579
|
+
attr_def[:aliases].each { |a| alias_method a, attr_name }
|
|
580
|
+
end # create getters
|
|
581
|
+
private_class_method :create_getters
|
|
582
|
+
|
|
583
|
+
# create setter(s) for an attribute, and any aliases needed
|
|
584
|
+
##############################
|
|
585
|
+
def self.create_setters(attr_name, attr_def)
|
|
586
|
+
# multi_value
|
|
587
|
+
if attr_def[:multi]
|
|
588
|
+
create_array_setters(attr_name, attr_def)
|
|
589
|
+
return
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
# single value
|
|
593
|
+
define_method("#{attr_name}=") do |new_value|
|
|
594
|
+
new_value = validate_attr attr_name, new_value
|
|
595
|
+
old_value = instance_variable_get("@#{attr_name}")
|
|
596
|
+
return if new_value == old_value
|
|
597
|
+
instance_variable_set("@#{attr_name}", new_value)
|
|
598
|
+
note_unsaved_change attr_name, old_value
|
|
599
|
+
end # define method
|
|
600
|
+
|
|
601
|
+
return unless attr_def[:aliases]
|
|
602
|
+
|
|
603
|
+
# setter aliases
|
|
604
|
+
attr_def[:aliases].each { |a| alias_method "#{a}=", "#{attr_name}=" }
|
|
605
|
+
end # create_setters
|
|
606
|
+
private_class_method :create_setters
|
|
607
|
+
|
|
608
|
+
##############################
|
|
609
|
+
def self.create_array_setters(attr_name, attr_def)
|
|
610
|
+
create_full_array_setters(attr_name, attr_def)
|
|
611
|
+
create_append_setters(attr_name, attr_def)
|
|
612
|
+
create_prepend_setters(attr_name, attr_def)
|
|
613
|
+
create_insert_setters(attr_name, attr_def)
|
|
614
|
+
create_delete_at_setters(attr_name, attr_def)
|
|
615
|
+
create_delete_if_setters(attr_name, attr_def)
|
|
616
|
+
end # def create_multi_setters
|
|
617
|
+
private_class_method :create_array_setters
|
|
618
|
+
|
|
619
|
+
# The attr=(newval) setter method for array values
|
|
620
|
+
##############################
|
|
621
|
+
def self.create_full_array_setters(attr_name, attr_def)
|
|
622
|
+
define_method("#{attr_name}=") do |new_value|
|
|
623
|
+
instance_variable_set("@#{attr_name}", []) unless instance_variable_get("@#{attr_name}").is_a?(Array)
|
|
624
|
+
raise Jamf::InvalidDataError, 'Value must be an Array' unless new_value.is_a? Array
|
|
625
|
+
new_value.map! { |item| validate_attr attr_name, item }
|
|
626
|
+
old_value = instance_variable_get("@#{attr_name}")
|
|
627
|
+
return if new_value == old_value
|
|
628
|
+
instance_variable_set("@#{attr_name}", new_value)
|
|
629
|
+
note_unsaved_change attr_name, old_value
|
|
630
|
+
end # define method
|
|
631
|
+
|
|
632
|
+
return unless attr_def[:aliases]
|
|
633
|
+
|
|
634
|
+
attr_def[:aliases].each { |al| alias_method "#{al}=", "#{attr_name}=" }
|
|
635
|
+
end # create_full_array_setter
|
|
636
|
+
private_class_method :create_full_array_setters
|
|
637
|
+
|
|
638
|
+
# The attr_append(newval) setter method for array values
|
|
639
|
+
##############################
|
|
640
|
+
def self.create_append_setters(attr_name, attr_def)
|
|
641
|
+
define_method("#{attr_name}_append") do |new_value|
|
|
642
|
+
instance_variable_set("@#{attr_name}", []) unless instance_variable_get("@#{attr_name}").is_a?(Array)
|
|
643
|
+
new_value = validate_attr attr_name, new_value
|
|
644
|
+
old_array = instance_variable_get("@#{attr_name}").dup
|
|
645
|
+
instance_variable_get("@#{attr_name}") << new_value
|
|
646
|
+
note_unsaved_change attr_name, old_array
|
|
647
|
+
end # define method
|
|
648
|
+
|
|
649
|
+
# always have a << alias
|
|
650
|
+
alias_method "#{attr_name}<<", "#{attr_name}_append"
|
|
651
|
+
|
|
652
|
+
return unless attr_def[:aliases]
|
|
653
|
+
|
|
654
|
+
attr_def[:aliases].each do |al|
|
|
655
|
+
alias_method "#{al}_append", "#{attr_name}_append"
|
|
656
|
+
alias_method "#{al}<<", "#{attr_name}_append"
|
|
657
|
+
end
|
|
658
|
+
end # create_append_setters
|
|
659
|
+
private_class_method :create_append_setters
|
|
660
|
+
|
|
661
|
+
# The attr_prepend(newval) setter method for array values
|
|
662
|
+
##############################
|
|
663
|
+
def self.create_prepend_setters(attr_name, attr_def)
|
|
664
|
+
define_method("#{attr_name}_prepend") do |new_value|
|
|
665
|
+
instance_variable_set("@#{attr_name}", []) unless instance_variable_get("@#{attr_name}").is_a?(Array)
|
|
666
|
+
new_value = validate_attr attr_name, new_value
|
|
667
|
+
old_array = instance_variable_get("@#{attr_name}").dup
|
|
668
|
+
instance_variable_get("@#{attr_name}").unshift new_value
|
|
669
|
+
note_unsaved_change attr_name, old_array
|
|
670
|
+
end # define method
|
|
671
|
+
|
|
672
|
+
return unless attr_def[:aliases]
|
|
673
|
+
|
|
674
|
+
attr_def[:aliases].each { |al| alias_method "#{al}_prepend", "#{attr_name}_prepend" }
|
|
675
|
+
end # create_prepend_setters
|
|
676
|
+
private_class_method :create_prepend_setters
|
|
677
|
+
|
|
678
|
+
# The attr_insert(index, newval) setter method for array values
|
|
679
|
+
def self.create_insert_setters(attr_name, attr_def)
|
|
680
|
+
define_method("#{attr_name}_insert") do |index, new_value|
|
|
681
|
+
instance_variable_set("@#{attr_name}", []) unless instance_variable_get("@#{attr_name}").is_a?(Array)
|
|
682
|
+
new_value = validate_attr attr_name, new_value
|
|
683
|
+
old_array = instance_variable_get("@#{attr_name}").dup
|
|
684
|
+
instance_variable_get("@#{attr_name}").insert index, new_value
|
|
685
|
+
note_unsaved_change attr_name, old_array
|
|
686
|
+
end # define method
|
|
687
|
+
|
|
688
|
+
return unless attr_def[:aliases]
|
|
689
|
+
|
|
690
|
+
attr_def[:aliases].each { |al| alias_method "#{al}_insert", "#{attr_name}_insert" }
|
|
691
|
+
end # create_insert_setters
|
|
692
|
+
private_class_method :create_insert_setters
|
|
693
|
+
|
|
694
|
+
# The attr_delete_at(index) setter method for array values
|
|
695
|
+
##############################
|
|
696
|
+
def self.create_delete_at_setters(attr_name, attr_def)
|
|
697
|
+
define_method("#{attr_name}_delete_at") do |index|
|
|
698
|
+
instance_variable_set("@#{attr_name}", []) unless instance_variable_get("@#{attr_name}").is_a?(Array)
|
|
699
|
+
old_array = instance_variable_get("@#{attr_name}").dup
|
|
700
|
+
deleted = instance_variable_get("@#{attr_name}").delete_at index
|
|
701
|
+
note_unsaved_change attr_name, old_array if deleted
|
|
702
|
+
end # define method
|
|
703
|
+
|
|
704
|
+
return unless attr_def[:aliases]
|
|
705
|
+
|
|
706
|
+
attr_def[:aliases].each { |al| alias_method "#{al}_delete_at", "#{attr_name}_delete_at" }
|
|
707
|
+
end # create_insert_setters
|
|
708
|
+
private_class_method :create_delete_at_setters
|
|
709
|
+
|
|
710
|
+
# The attr_delete_at(index) setter method for array values
|
|
711
|
+
##############################
|
|
712
|
+
def self.create_delete_if_setters(attr_name, attr_def)
|
|
713
|
+
define_method("#{attr_name}_delete_if") do |index, &block|
|
|
714
|
+
instance_variable_set("@#{attr_name}", []) unless instance_variable_get("@#{attr_name}").is_a?(Array)
|
|
715
|
+
old_array = instance_variable_get("@#{attr_name}").dup
|
|
716
|
+
instance_variable_get("@#{attr_name}").delete_if &block
|
|
717
|
+
note_unsaved_change attr_name, old_array if old_array != instance_variable_get("@#{attr_name}")
|
|
718
|
+
end # define method
|
|
719
|
+
|
|
720
|
+
return unless attr_def[:aliases]
|
|
721
|
+
|
|
722
|
+
attr_def[:aliases].each { |al| alias_method "#{al}_delete_if", "#{attr_name}_delete_if" }
|
|
723
|
+
end # create_insert_setters
|
|
724
|
+
private_class_method :create_delete_at_setters
|
|
725
|
+
|
|
726
|
+
# Raise an exception if this is an abstract class
|
|
727
|
+
# Used in class methods that are defined in abstract classes.
|
|
728
|
+
# Instantiation is already prevented by the Abstract mixin
|
|
729
|
+
##############################
|
|
730
|
+
def self.validate_not_abstract
|
|
731
|
+
raise Jamf::UnsupportedError, "Unsupported: #{self} is an abstract class, ." if Jamf::Abstract.abstract_classes.include? self
|
|
732
|
+
end
|
|
733
|
+
private_class_method :validate_not_abstract
|
|
734
|
+
|
|
735
|
+
# Used by auto-generated setters and .create to validate new values.
|
|
736
|
+
#
|
|
737
|
+
# returns a valid value or raises an exception
|
|
738
|
+
#
|
|
739
|
+
# If the attribute is defined to hold a JAMF class, (e.g. Jamf::Timestamp)
|
|
740
|
+
# the class itself performs validation on the value when instantiated
|
|
741
|
+
# with the value.
|
|
742
|
+
#
|
|
743
|
+
# If the attribute is defined with an enum, the value must be
|
|
744
|
+
# a key of the enum.
|
|
745
|
+
#
|
|
746
|
+
# If the attribute is defined with a validator, the value is passed
|
|
747
|
+
# to that validator.
|
|
748
|
+
#
|
|
749
|
+
# If the attribute is defined as a :string, :integer, :float or :bool
|
|
750
|
+
# without an enum or validator, it is confirmed to be the correct type
|
|
751
|
+
#
|
|
752
|
+
# Otherwise, the value is returned unchanged.
|
|
753
|
+
#
|
|
754
|
+
# If the attribute is defined as an identifier, it must be unique among
|
|
755
|
+
# the other objects of this subclass in the JSS.
|
|
756
|
+
#
|
|
757
|
+
# This method only validates single values. When called from multi-value
|
|
758
|
+
# setters, it is used for each value individually.
|
|
759
|
+
#
|
|
760
|
+
#
|
|
761
|
+
# @param attr_name[Symbol], a top-level key from OBJECT_MODEL for this class
|
|
762
|
+
#
|
|
763
|
+
# @param value [Object] the value to validate for that attribute.
|
|
764
|
+
#
|
|
765
|
+
# @return [Object] The validated, possibly converted, value.
|
|
766
|
+
#
|
|
767
|
+
def self.validate_attr(attr_name, value, cnx: Jamf.cnx)
|
|
768
|
+
attr_def = self::OBJECT_MODEL[attr_name]
|
|
769
|
+
|
|
770
|
+
# validate our value, which will raise an error or
|
|
771
|
+
# convert the value to the required type.
|
|
772
|
+
good_value =
|
|
773
|
+
|
|
774
|
+
# by enum, must be a value of the enum
|
|
775
|
+
if attr_def[:enum]
|
|
776
|
+
return value if attr_def[:enum].include? value
|
|
777
|
+
raise Jamf::InvalidDataError, "Value must be one of: :#{attr_def[:enum].join ', :'}"
|
|
778
|
+
|
|
779
|
+
# by class, the class validates the value passed with .new
|
|
780
|
+
elsif attr_def[:class].is_a? Class
|
|
781
|
+
|
|
782
|
+
klass = attr_def[:class]
|
|
783
|
+
# validation happens in klass.new
|
|
784
|
+
value.is_a?(klass) ? value : klass.new(value, cnx: cnx)
|
|
785
|
+
|
|
786
|
+
# by Validate method - pass to the method
|
|
787
|
+
elsif attr_def[:validator]
|
|
788
|
+
Jamf::Validate.send(attr_def[:validator], value)
|
|
789
|
+
|
|
790
|
+
# By json type - pass to the matching validate method
|
|
791
|
+
elsif JSON_TYPE_CLASSES.include? attr_def[:class]
|
|
792
|
+
Jamf::Validate.send(attr_def[:class], value)
|
|
793
|
+
|
|
794
|
+
# raw, no validation, should be rare
|
|
795
|
+
else
|
|
796
|
+
value
|
|
797
|
+
end # if
|
|
798
|
+
|
|
799
|
+
# if this is an identifier, it must be unique
|
|
800
|
+
Jamf::Validate.send(:unique_identifier, good_value, self.class, attr_name, cnx: @cnx) if attr_def[:identfier]
|
|
801
|
+
|
|
802
|
+
good_value
|
|
803
|
+
end # validate_attr(attr_name, value)
|
|
804
|
+
private_class_method :validate_attr
|
|
805
|
+
|
|
806
|
+
# Attributes
|
|
807
|
+
#####################################
|
|
808
|
+
|
|
809
|
+
# @return [Jamf::Connection] the API connection thru which we deal with
|
|
810
|
+
# this objcet.
|
|
811
|
+
attr_reader :cnx
|
|
812
|
+
|
|
813
|
+
# Constructor
|
|
814
|
+
#####################################
|
|
815
|
+
|
|
816
|
+
# Make an instance. Data comes from the API
|
|
817
|
+
#
|
|
818
|
+
# @param data[Hash] the data for constructing a new object.
|
|
819
|
+
# @param cnx[Jamf::Connection] the API connection for the object
|
|
820
|
+
#
|
|
821
|
+
def initialize(data, cnx: Jamf.cnx)
|
|
822
|
+
|
|
823
|
+
raise Jamf::InvalidDataError, 'Invalid JSONObject data - must be a Hash' unless data.is_a? Hash
|
|
824
|
+
|
|
825
|
+
@cnx = cnx
|
|
826
|
+
@unsaved_changes = {} if self.class.mutable?
|
|
827
|
+
|
|
828
|
+
creating = data.delete :creating_from_create
|
|
829
|
+
|
|
830
|
+
if creating
|
|
831
|
+
self.class::OBJECT_MODEL.keys.each do |attr_name|
|
|
832
|
+
next unless data.key? attr_name
|
|
833
|
+
# use our setters for each value so that they are in the unsaved changes
|
|
834
|
+
send "#{attr_name}=", data[attr_name]
|
|
835
|
+
end
|
|
836
|
+
return
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
parse_init_data data
|
|
840
|
+
end # init
|
|
841
|
+
|
|
842
|
+
# Instance Methods
|
|
843
|
+
#####################################
|
|
844
|
+
|
|
845
|
+
# a hash of all unsaved changes, including embedded JSONObjects
|
|
846
|
+
#
|
|
847
|
+
def unsaved_changes
|
|
848
|
+
return {} unless self.class.mutable?
|
|
849
|
+
|
|
850
|
+
changes = @unsaved_changes.dup
|
|
851
|
+
|
|
852
|
+
self.class::OBJECT_MODEL.each do |attr_name, attr_def|
|
|
853
|
+
# skip non-Class attrs
|
|
854
|
+
next unless attr_def[:class].is_a? Class
|
|
855
|
+
|
|
856
|
+
# the current value of the thing, e.g. a Location
|
|
857
|
+
# which may have unsaved changes
|
|
858
|
+
value = instance_variable_get "@#{attr_name}"
|
|
859
|
+
|
|
860
|
+
# skip those that don't have any changes
|
|
861
|
+
next unless value.respond_to? :unsaved_changes?
|
|
862
|
+
attr_changes = value.unsaved_changes
|
|
863
|
+
next if attr_changes.empty?
|
|
864
|
+
|
|
865
|
+
# add the sub-changes to ours
|
|
866
|
+
changes[attr_name] = attr_changes
|
|
867
|
+
end
|
|
868
|
+
changes[:ext_attrs] = ext_attrs_unsaved_changes if self.class.include? Jamf::Extendable
|
|
869
|
+
changes
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
# return true if we or any of our attributes have unsaved changes
|
|
873
|
+
#
|
|
874
|
+
def unsaved_changes?
|
|
875
|
+
return false unless self.class.mutable?
|
|
876
|
+
|
|
877
|
+
!unsaved_changes.empty?
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
def clear_unsaved_changes
|
|
881
|
+
return unless self.class.mutable?
|
|
882
|
+
|
|
883
|
+
unsaved_changes.keys.each do |attr_name|
|
|
884
|
+
attrib_val = instance_variable_get "@#{attr_name}"
|
|
885
|
+
if self.class::OBJECT_MODEL[attr_name][:multi]
|
|
886
|
+
attrib_val.each { |item| item.send :clear_unsaved_changes if item.respond_to? :clear_unsaved_changes }
|
|
887
|
+
elsif attrib_val.respond_to? :clear_unsaved_changes
|
|
888
|
+
attrib_val.send :clear_unsaved_changes
|
|
889
|
+
end
|
|
890
|
+
end
|
|
891
|
+
ext_attrs_clear_unsaved_changes if self.class.include? Jamf::Extendable
|
|
892
|
+
@unsaved_changes = {}
|
|
893
|
+
end
|
|
894
|
+
|
|
895
|
+
# @return [Hash] The data to be sent to the API, as a Hash
|
|
896
|
+
# to be converted to JSON by the Jamf::Connection
|
|
897
|
+
#
|
|
898
|
+
def to_jamf
|
|
899
|
+
data = {}
|
|
900
|
+
self.class::OBJECT_MODEL.each do |attr_name, attr_def|
|
|
901
|
+
|
|
902
|
+
raw_value = instance_variable_get "@#{attr_name}"
|
|
903
|
+
|
|
904
|
+
# If its a multi-value attribute, process it and go on
|
|
905
|
+
if attr_def[:multi]
|
|
906
|
+
data[attr_name] = multi_to_jamf(raw_value, attr_def)
|
|
907
|
+
next
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
# if its a single-value object, process it and go on.
|
|
911
|
+
cooked_value = single_to_jamf(raw_value, attr_def)
|
|
912
|
+
# next if cooked_value.nil? # ignore nil
|
|
913
|
+
data[attr_name] = cooked_value
|
|
914
|
+
end # unsaved_changes.each
|
|
915
|
+
data
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
# DOESN"T SEEM TO WORK FOR BUILDINGS - need the whole JSON object
|
|
919
|
+
# not just the changes.
|
|
920
|
+
# @return [Hash] The changes that need to be sent to the API, as a Hash
|
|
921
|
+
# to be converted to JSON by the Jamf::Connection
|
|
922
|
+
#
|
|
923
|
+
def to_jamf_changes_only
|
|
924
|
+
return unless self.class.mutable?
|
|
925
|
+
|
|
926
|
+
data = {}
|
|
927
|
+
unsaved_changes.each do |attr_name, changes|
|
|
928
|
+
attr_def = self.class::OBJECT_MODEL[attr_name]
|
|
929
|
+
|
|
930
|
+
# readonly attributes can't be changed
|
|
931
|
+
next if attr_def[:readonly]
|
|
932
|
+
|
|
933
|
+
# here's the new value for this attribute
|
|
934
|
+
raw_value = changes[:new]
|
|
935
|
+
|
|
936
|
+
# If its a multi-value attribute, process it and go on
|
|
937
|
+
if attr_def[:multi]
|
|
938
|
+
data[attr_name] = multi_to_jamf(raw_value, attr_def)
|
|
939
|
+
next
|
|
940
|
+
end
|
|
941
|
+
|
|
942
|
+
# if its a single-value object, process it and go on.
|
|
943
|
+
cooked_value = single_to_jamf(raw_value, attr_def)
|
|
944
|
+
next if cooked_value.nil? # ignore nil
|
|
945
|
+
|
|
946
|
+
data[attr_name] = cooked_value
|
|
947
|
+
end # unsaved_changes.each
|
|
948
|
+
data
|
|
949
|
+
end
|
|
950
|
+
|
|
951
|
+
# Print the JSON version of the to_jamf outout
|
|
952
|
+
# mostly for debugging/troubleshooting
|
|
953
|
+
def pretty_jamf_json
|
|
954
|
+
puts JSON.pretty_generate(to_jamf)
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
# Remove large cached items from
|
|
958
|
+
# the instance_variables used to create
|
|
959
|
+
# pretty-print (pp) output.
|
|
960
|
+
#
|
|
961
|
+
# @return [Array] the desired instance_variables
|
|
962
|
+
#
|
|
963
|
+
def pretty_print_instance_variables
|
|
964
|
+
vars = super.sort
|
|
965
|
+
vars.delete :@cnx
|
|
966
|
+
vars
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
# Private Instance Methods
|
|
970
|
+
#####################################
|
|
971
|
+
private
|
|
972
|
+
|
|
973
|
+
def note_unsaved_change(attr_name, old_value)
|
|
974
|
+
return unless self.class.mutable?
|
|
975
|
+
|
|
976
|
+
new_val = instance_variable_get "@#{attr_name}"
|
|
977
|
+
if @unsaved_changes[attr_name]
|
|
978
|
+
@unsaved_changes[attr_name][:new] = new_val
|
|
979
|
+
else
|
|
980
|
+
@unsaved_changes[attr_name] = { old: old_value, new: new_val }
|
|
981
|
+
end
|
|
982
|
+
end
|
|
983
|
+
|
|
984
|
+
# take data from the API and populate an our instance attributes
|
|
985
|
+
#
|
|
986
|
+
# @param data[Hash] The parsed API JSON data for this instance
|
|
987
|
+
#
|
|
988
|
+
# @return [void]
|
|
989
|
+
#
|
|
990
|
+
def parse_init_data(data)
|
|
991
|
+
self.class::OBJECT_MODEL.each do |attr_name, attr_def|
|
|
992
|
+
value =
|
|
993
|
+
if attr_def[:multi]
|
|
994
|
+
raw_array = data[attr_name] || []
|
|
995
|
+
|
|
996
|
+
raw_array.map! { |v| parse_single_init_value v, attr_name, attr_def }
|
|
997
|
+
else
|
|
998
|
+
parse_single_init_value data[attr_name], attr_name, attr_def
|
|
999
|
+
end
|
|
1000
|
+
instance_variable_set "@#{attr_name}", value
|
|
1001
|
+
end # OBJECT_MODEL.each
|
|
1002
|
+
end # parse_init_data(data)
|
|
1003
|
+
|
|
1004
|
+
# Parse an individual value from the API into an
|
|
1005
|
+
# attribute or a member of a multi attribute
|
|
1006
|
+
# Description of #parse_single_init_value
|
|
1007
|
+
#
|
|
1008
|
+
# @param api_value [Object] The parsed JSON value from the API
|
|
1009
|
+
# @param attr_name [Symbol] The attribute we're processing
|
|
1010
|
+
# @param attr_def [Hash] The attribute definition
|
|
1011
|
+
#
|
|
1012
|
+
# @return [Object] The storable value.
|
|
1013
|
+
#
|
|
1014
|
+
def parse_single_init_value(api_value, attr_name, attr_def)
|
|
1015
|
+
# we do get nils from the API, and they should stay nil
|
|
1016
|
+
return nil if api_value.nil?
|
|
1017
|
+
|
|
1018
|
+
# an enum value
|
|
1019
|
+
if attr_def[:enum]
|
|
1020
|
+
parse_enum_value(api_value, attr_name, attr_def)
|
|
1021
|
+
|
|
1022
|
+
# a Class value
|
|
1023
|
+
elsif attr_def[:class].class == Class
|
|
1024
|
+
attr_def[:class].new api_value, cnx: @cnx
|
|
1025
|
+
|
|
1026
|
+
# a JSON value
|
|
1027
|
+
else
|
|
1028
|
+
api_value
|
|
1029
|
+
end # if attr_def[:class].class
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
# Parse an api value into an attribute with an enum
|
|
1033
|
+
#
|
|
1034
|
+
# @param (see parse_single_init_value)
|
|
1035
|
+
# @return (see parse_single_init_value)
|
|
1036
|
+
#
|
|
1037
|
+
def parse_enum_value(api_value, attr_name, attr_def)
|
|
1038
|
+
if attr_def[:enum].include? api_value
|
|
1039
|
+
api_value
|
|
1040
|
+
else
|
|
1041
|
+
raise Jamf::InvalidDataError, "#{api_value} is not in the enum for attribute #{attr_name}"
|
|
1042
|
+
end
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
# call to_jamf on a single value
|
|
1046
|
+
#
|
|
1047
|
+
def single_to_jamf(raw_value, attr_def)
|
|
1048
|
+
# if the attrib class is a Class,
|
|
1049
|
+
# call its changes_to_jamf or to_jamf method
|
|
1050
|
+
if attr_def[:class].is_a? Class
|
|
1051
|
+
data = raw_value.to_jamf
|
|
1052
|
+
data.is_a?(Hash) && data.empty? ? nil : data
|
|
1053
|
+
|
|
1054
|
+
# otherwise, use the value as-is
|
|
1055
|
+
else
|
|
1056
|
+
raw_value
|
|
1057
|
+
end
|
|
1058
|
+
end
|
|
1059
|
+
|
|
1060
|
+
# Call to_jamf on an array value
|
|
1061
|
+
#
|
|
1062
|
+
def multi_to_jamf(raw_array, attr_def)
|
|
1063
|
+
raw_array ||= []
|
|
1064
|
+
raw_array.map { |raw_value| single_to_jamf(raw_value, attr_def) }.compact
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
# wrapper for class method
|
|
1068
|
+
def validate_attr(attr_name, value)
|
|
1069
|
+
self.class.send :validate_attr, attr_name, value, cnx: @cnx
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
end # class JSONObject
|
|
1073
|
+
|
|
1074
|
+
end # module JAMF
|