ruby-jss 0.6.3

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.

Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +7 -0
  3. data/CHANGES.md +112 -0
  4. data/LICENSE.txt +174 -0
  5. data/README.md +426 -0
  6. data/THANKS.md +6 -0
  7. data/bin/cgrouper +485 -0
  8. data/bin/subnet-update +400 -0
  9. data/lib/jss-api.rb +2 -0
  10. data/lib/jss.rb +190 -0
  11. data/lib/jss/api_connection.rb +410 -0
  12. data/lib/jss/api_object.rb +616 -0
  13. data/lib/jss/api_object/advanced_search.rb +389 -0
  14. data/lib/jss/api_object/advanced_search/advanced_computer_search.rb +95 -0
  15. data/lib/jss/api_object/advanced_search/advanced_mobile_device_search.rb +96 -0
  16. data/lib/jss/api_object/advanced_search/advanced_user_search.rb +95 -0
  17. data/lib/jss/api_object/building.rb +92 -0
  18. data/lib/jss/api_object/category.rb +147 -0
  19. data/lib/jss/api_object/computer.rb +852 -0
  20. data/lib/jss/api_object/creatable.rb +98 -0
  21. data/lib/jss/api_object/criteriable.rb +189 -0
  22. data/lib/jss/api_object/criteriable/criteria.rb +231 -0
  23. data/lib/jss/api_object/criteriable/criterion.rb +228 -0
  24. data/lib/jss/api_object/department.rb +93 -0
  25. data/lib/jss/api_object/distribution_point.rb +560 -0
  26. data/lib/jss/api_object/extendable.rb +221 -0
  27. data/lib/jss/api_object/extension_attribute.rb +466 -0
  28. data/lib/jss/api_object/extension_attribute/computer_extension_attribute.rb +362 -0
  29. data/lib/jss/api_object/extension_attribute/mobile_device_extension_attribute.rb +189 -0
  30. data/lib/jss/api_object/extension_attribute/user_extension_attribute.rb +117 -0
  31. data/lib/jss/api_object/group.rb +380 -0
  32. data/lib/jss/api_object/group/computer_group.rb +124 -0
  33. data/lib/jss/api_object/group/mobile_device_group.rb +139 -0
  34. data/lib/jss/api_object/group/user_group.rb +139 -0
  35. data/lib/jss/api_object/ldap_server.rb +535 -0
  36. data/lib/jss/api_object/locatable.rb +286 -0
  37. data/lib/jss/api_object/matchable.rb +97 -0
  38. data/lib/jss/api_object/mobile_device.rb +556 -0
  39. data/lib/jss/api_object/netboot_server.rb +148 -0
  40. data/lib/jss/api_object/network_segment.rb +414 -0
  41. data/lib/jss/api_object/osx_configuration_profile.rb +262 -0
  42. data/lib/jss/api_object/package.rb +839 -0
  43. data/lib/jss/api_object/peripheral.rb +335 -0
  44. data/lib/jss/api_object/peripheral_type.rb +295 -0
  45. data/lib/jss/api_object/policy.rb +898 -0
  46. data/lib/jss/api_object/purchasable.rb +316 -0
  47. data/lib/jss/api_object/removable_macaddr.rb +98 -0
  48. data/lib/jss/api_object/scopable.rb +136 -0
  49. data/lib/jss/api_object/scopable/scope.rb +621 -0
  50. data/lib/jss/api_object/script.rb +631 -0
  51. data/lib/jss/api_object/self_servable.rb +356 -0
  52. data/lib/jss/api_object/site.rb +93 -0
  53. data/lib/jss/api_object/software_update_server.rb +109 -0
  54. data/lib/jss/api_object/updatable.rb +117 -0
  55. data/lib/jss/api_object/uploadable.rb +138 -0
  56. data/lib/jss/api_object/user.rb +272 -0
  57. data/lib/jss/client.rb +504 -0
  58. data/lib/jss/compatibility.rb +66 -0
  59. data/lib/jss/composer.rb +185 -0
  60. data/lib/jss/configuration.rb +306 -0
  61. data/lib/jss/db_connection.rb +298 -0
  62. data/lib/jss/exceptions.rb +95 -0
  63. data/lib/jss/ruby_extensions.rb +35 -0
  64. data/lib/jss/ruby_extensions/filetest.rb +43 -0
  65. data/lib/jss/ruby_extensions/hash.rb +79 -0
  66. data/lib/jss/ruby_extensions/ipaddr.rb +91 -0
  67. data/lib/jss/ruby_extensions/pathname.rb +77 -0
  68. data/lib/jss/ruby_extensions/string.rb +59 -0
  69. data/lib/jss/ruby_extensions/time.rb +63 -0
  70. data/lib/jss/server.rb +108 -0
  71. data/lib/jss/utility.rb +478 -0
  72. data/lib/jss/version.rb +31 -0
  73. metadata +187 -0
@@ -0,0 +1,262 @@
1
+ ### Copyright 2016 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
+
26
+ ###
27
+ module JSS
28
+
29
+ #####################################
30
+ ### Module Variables
31
+ #####################################
32
+
33
+ #####################################
34
+ ### Module Methods
35
+ #####################################
36
+
37
+
38
+ #####################################
39
+ ### Classes
40
+ #####################################
41
+
42
+ ###
43
+ ### An OS X Configuration Profile in the JSS.
44
+ ###
45
+ ### Note that the profile payloads and the profile UUID cannot be edited or updated with this via this class.
46
+ ### Use the web UI.
47
+ ###
48
+ ### @see JSS::APIObject
49
+ ###
50
+ class OSXConfigurationProfile < JSS::APIObject
51
+
52
+ #####################################
53
+ ### Mix-Ins
54
+ #####################################
55
+ include JSS::Updatable
56
+ include JSS::Scopable
57
+ include JSS::SelfServable
58
+
59
+ #####################################
60
+ ### Class Methods
61
+ #####################################
62
+
63
+ #####################################
64
+ ### Class Constants
65
+ #####################################
66
+
67
+ ### The base for REST resources of this class
68
+ RSRC_BASE = "osxconfigurationprofiles"
69
+
70
+ ### the hash key used for the JSON list output of all objects in the JSS
71
+ RSRC_LIST_KEY = :os_x_configuration_profiles
72
+
73
+ ### The hash key used for the JSON object output.
74
+ ### It's also used in various error messages
75
+ RSRC_OBJECT_KEY = :os_x_configuration_profile
76
+
77
+ ### these keys, as well as :id and :name, are present in valid API JSON data for this class
78
+ VALID_DATA_KEYS = [:distribution_method, :scope, :redeploy_on_update]
79
+
80
+ ### Our scopes deal with computers
81
+ SCOPE_TARGET_KEY = :computers
82
+
83
+ ### Our SelfService happens on OSX
84
+ SELF_SERVICE_TARGET = :osx
85
+
86
+ ### Our SelfService deploys profiles
87
+ SELF_SERVICE_PAYLOAD = :profile
88
+
89
+ ### The possible values for the :distribution_method
90
+ DISTRIBUTION_METHODS = ["Install Automatically", "Make Available in Self Service"]
91
+
92
+ SELF_SERVICE_DIST_METHOD = "Make Available in Self Service"
93
+
94
+ ### The possible values for :level
95
+ LEVELS = ["user", "computer"]
96
+
97
+
98
+ #####################################
99
+ ### Attributes
100
+ #####################################
101
+
102
+ ### @return [String] the description of this profile
103
+ attr_reader :description
104
+
105
+ ### @return [String] the distribution_method of this profile
106
+ attr_reader :distribution_method
107
+
108
+ ### @return [Boolean] can the user remove this profile
109
+ attr_reader :user_removable
110
+
111
+ ### @return [String] the level (user/computer) of this profile
112
+ attr_reader :level
113
+
114
+ ### @return [String] the uuid of this profile. NOT Updatable
115
+ attr_reader :uuid
116
+
117
+ ### @return [Boolean] Should this profile be redeployed when an inventory update happens?
118
+ attr_reader :redeploy_on_update
119
+
120
+ ### @return [String] the plist containing the payloads for this profile. NOT Updatable
121
+ attr_reader :payloads
122
+
123
+ #####################################
124
+ ### Constructor
125
+ #####################################
126
+
127
+ ###
128
+ ### See JSS::APIObject#initialize
129
+ ###
130
+ def initialize (args = {})
131
+
132
+ super
133
+
134
+ @description = @main_subset[:description]
135
+ @distribution_method = @main_subset[:distribution_method]
136
+ @user_removable = @main_subset[:user_removable]
137
+ @level = @main_subset[:level]
138
+ @uuid = @main_subset[:uuid]
139
+ @redeploy_on_update = @main_subset[:redeploy_on_update]
140
+ @payloads = @main_subset[:payloads]
141
+
142
+ self.parse_scope
143
+ self.parse_self_service
144
+
145
+ end
146
+
147
+ #####################################
148
+ ### Public Instance Methods
149
+ #####################################
150
+
151
+ ###
152
+ ### @param new_val[String] the new discription
153
+ ###
154
+ ### @return [void]
155
+ ###
156
+ def description= (new_val)
157
+ return nil if @self_service_description == new_val
158
+ @description = new_val.strip!
159
+ @need_to_update = true
160
+ end
161
+
162
+
163
+ ###
164
+ ### @param new_val[String] how should this be distributed to clients?
165
+ ###
166
+ ### @return [void]
167
+ ###
168
+ def distribution_method= (new_val)
169
+ return nil if @distribution_method == new_val
170
+ raise JSS::InvalidDataError, "New value must be one of '#{DISTRIBUTION_METHODS.join("' '")}'" unless DISTRIBUTION_METHODS.include? new_val
171
+ @distribution_method = new_val
172
+ @need_to_update = true
173
+ end
174
+
175
+ ###
176
+ ### @param new_val[Boolean] should the user be able to remove this?
177
+ ###
178
+ ### @return [void]
179
+ ###
180
+ def user_removable= (new_val)
181
+ return nil if @self_service_feature_on_main_page == new_val
182
+ raise JSS::InvalidDataError, "Distribution method must be '#{SELF_SERVICE_DIST_METHOD}' to let the user remove it." unless in_self_service?
183
+ raise JSS::InvalidDataError, "New value must be true or false" unless JSS::TRUE_FALSE.include? new_val
184
+ @user_removable = new_val
185
+ @need_to_update = true
186
+ end
187
+
188
+ ###
189
+ ### @param new_val[String] the new level for this profile (user/computer)
190
+ ###
191
+ ### @return [void]
192
+ ###
193
+ def level= (new_val)
194
+ return nil if @level == new_val
195
+ raise JSS::InvalidDataError, "New value must be one of '#{LEVELS.join("' '")}'" unless LEVELS.include? new_val
196
+ @level = new_val
197
+ @need_to_update = true
198
+ end
199
+
200
+
201
+ ###
202
+ ### @return [Boolean] is this profile available in Self Service?
203
+ ###
204
+ def in_self_service?
205
+ @distribution_method == SELF_SERVICE_DIST_METHOD
206
+ end
207
+
208
+
209
+ ###
210
+ ### @return [Boolean] is this profile removable by the user?
211
+ ###
212
+ def user_removable?
213
+ @user_removable
214
+ end
215
+
216
+
217
+ ###
218
+ ### @return [Hash] The payload plist parsed into a Ruby hash
219
+ ###
220
+ def parsed_payloads
221
+ Plist.parse_xml @payloads
222
+ end
223
+
224
+ ###
225
+ ### @return [Array<Hash>] the individual payloads from the payload Plist
226
+ ###
227
+ def payload_content
228
+ parsed_payloads['PayloadContent']
229
+ end
230
+
231
+ ###
232
+ ### @return [Array<String>] the PayloadType of each payload (e.g. com.apple.caldav.account)
233
+ ###
234
+ def payload_types
235
+ payload_content.map{|p| p['PayloadType'] }
236
+ end
237
+
238
+ #####################################
239
+ ### Private Instance Methods
240
+ #####################################
241
+ private
242
+
243
+ def rest_xml
244
+ doc = REXML::Document.new
245
+
246
+ obj = doc.add_element RSRC_OBJECT_KEY.to_s
247
+ gen = obj.add_element('general')
248
+ gen.add_element('description').text = @description
249
+ gen.add_element('distribution_method').text = @distribution_method
250
+ gen.add_element('user_removable').text = @user_removable
251
+ gen.add_element('level').text = @level
252
+ gen.add_element('redeploy_on_update').text = @redeploy_on_update
253
+
254
+ obj << @scope.scope_xml
255
+ obj << self_service_xml
256
+
257
+ return doc.to_s
258
+ end
259
+
260
+ end # class OSXConfigurationProfile
261
+
262
+ end # module
@@ -0,0 +1,839 @@
1
+ ### Copyright 2016 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
+ ###
26
+ module JSS
27
+
28
+ #####################################
29
+ ### Module Constants
30
+ #####################################
31
+
32
+ #####################################
33
+ ### Module Variables
34
+ #####################################
35
+
36
+ #####################################
37
+ ### Module Methods
38
+ #####################################
39
+
40
+ #####################################
41
+ ### Classes
42
+ #####################################
43
+
44
+ ###
45
+ ### A Package in the JSS
46
+ ###
47
+ ### Also the API provides no access to the package's
48
+ ### file list (index), so indexing must be done separately (usually via Casper Admin)
49
+ ###
50
+ ###
51
+ ### @see JSS::APIObject
52
+ ###
53
+ class Package < JSS::APIObject
54
+
55
+ #####################################
56
+ ### Mix-Ins
57
+ #####################################
58
+
59
+ include JSS::Creatable
60
+ include JSS::Updatable
61
+
62
+ #####################################
63
+ ### Class Methods
64
+ #####################################
65
+
66
+ #####################################
67
+ ### Class Constants
68
+ #####################################
69
+
70
+ ### The base for REST resources of this class
71
+ RSRC_BASE = "packages"
72
+
73
+ ### the hash key used for the JSON list output of all objects in the JSS
74
+ RSRC_LIST_KEY = :packages
75
+
76
+ ### The hash key used for the JSON object output.
77
+ ### It's also used in various error messages
78
+ RSRC_OBJECT_KEY = :package
79
+
80
+ ### these keys, as well as :id and :name, are present in valid API JSON data for this class
81
+ VALID_DATA_KEYS = [:fill_existing_users, :fill_user_template, :reboot_required ]
82
+
83
+ ### The pkg storage folder on the distribution point
84
+ DIST_POINT_PKGS_FOLDER = "Packages"
85
+
86
+ ### The possible values for cpu_type (required_processor) in a JSS package
87
+ CPU_TYPES = ["None", "x86", "ppc"]
88
+
89
+ # TO DO - this is redundant with DEFAULT_PROCESSOR, but both are in use
90
+ # clean them up!
91
+ ### which is default? there must be one to make a new pkg
92
+ DEFAULT_CPU_TYPE = "None"
93
+
94
+ ### the possible priorities
95
+ PRIORITIES = (1..20)
96
+
97
+ ### the default priority, since one is needed for making new pkgs
98
+ DEFAULT_PRIORITY = 10
99
+
100
+ ### by default, no processor requirement
101
+ DEFAULT_PROCESSOR = "None"
102
+
103
+ ### When we shouldn't install anything (e.g. switch w/package)
104
+ DO_NOT_INSTALL = "Do Not Install"
105
+
106
+ ### The table in the database for this object
107
+ DB_TABLE = "packages"
108
+
109
+ #####################################
110
+ ### Class Variables
111
+ #####################################
112
+
113
+ #####################################
114
+ ### Class Methods
115
+ #####################################
116
+
117
+ #####################################
118
+ ### Attributes
119
+ #####################################
120
+
121
+ ### @return [String] the filename of the .pkg, .mpkg, or .dmg on the Casper server
122
+ attr_reader :filename
123
+
124
+ ### @return [Pathname] the local receipt when this pkg is installed
125
+ attr_reader :receipt
126
+
127
+ ### @return [Boolean] does this item 'Fill Existing Users' when jamf installs it?
128
+ attr_reader :fill_existing_users
129
+
130
+ ### @return [Boolean] does this pkg also get install in the OS user homedir template
131
+ attr_reader :fill_user_template
132
+
133
+ ### @return [Boolean] does this item require a reboot after installation? If so, it'll be a puppy-install in d3
134
+ attr_reader :reboot_required
135
+
136
+ ### @return [Array<String>] the OS versions this can be installed onto. For all minor versions, the format is 10.5.x
137
+ attr_reader :os_requirements
138
+
139
+ ### @return [String] limit installation to these architectures: 'x86', 'ppc', 'None'
140
+ attr_reader :required_processor
141
+
142
+ ### @return [String] the name of a pkg to install (or "Do Not Install") when this pkg can't be installed
143
+ attr_reader :switch_with_package
144
+
145
+ ### @return [Boolean] can this item be uninstalled? Some, e.g. OS Updates, can't
146
+ attr_reader :allow_uninstalled
147
+
148
+ ### @return [String] the category of this pkg, stored in the JSS as the id number from the categories table
149
+ attr_reader :category
150
+
151
+ ### @return [String] the info field for this pkg - stores d3's basename & swupdate values
152
+ attr_reader :info
153
+
154
+ ### @return [String] the notes field for this pkg
155
+ attr_reader :notes
156
+
157
+ ### @return [Boolean] only install this pkg if it's available in the commandline softwareupdate.
158
+ attr_reader :install_if_reported_available
159
+
160
+ ### @return [Boolean] should this pkg be installed on the boot volume during imaging
161
+ attr_reader :boot_volume_required
162
+
163
+ ### @return [Integer] Priority to use for deploying or uninstalling the package
164
+ attr_reader :priority
165
+
166
+ ### @return [Boolean] does this pkg cause a notification to be sent on self-heal?
167
+ attr_reader :send_notification
168
+
169
+
170
+ ###
171
+ ### @see JSS::APIObject#initialize
172
+ ###
173
+ def initialize (args = {})
174
+
175
+ super
176
+
177
+ ### now we have pkg_data with something in it, so fill out the instance vars
178
+ @allow_uninstalled = @init_data[:allow_uninstalled]
179
+ @boot_volume_required = @init_data[:boot_volume_required]
180
+ @category = JSS::APIObject.get_name(@init_data[:category])
181
+ @category = nil if @category.to_s.casecmp("No category assigned") == 0
182
+ @filename = @init_data[:filename] || @init_data[:name]
183
+ @fill_existing_users = @init_data[:fill_existing_users]
184
+ @fill_user_template = @init_data[:fill_user_template]
185
+ @info = @init_data[:info]
186
+ @install_if_reported_available = @init_data[:install_if_reported_available]
187
+ @notes = @init_data[:notes]
188
+ @os_requirements = @init_data[:os_requirements].split(/\s*,\s*/) if @init_data[:os_requirements]
189
+ @os_requirements ||= []
190
+
191
+ @priority = @init_data[:priority] || DEFAULT_PRIORITY
192
+ @reboot_required = @init_data[:reboot_required]
193
+ @required_processor = @init_data[:required_processor] || DEFAULT_CPU_TYPE
194
+ @required_processor = nil if @required_processor.to_s.casecmp('none') == 0
195
+ @send_notification = @init_data[:send_notification]
196
+ @switch_with_package = @init_data[:switch_with_package] || DO_NOT_INSTALL
197
+
198
+ # the receipt is the filename with any .zip extension removed.
199
+ @receipt = @filename ? (JSS::Client::RECEIPTS_FOLDER + @filename.to_s.sub(/.zip$/, '')) : nil
200
+ end # init
201
+
202
+
203
+
204
+ ###
205
+ ### Change the 'allow to be uninstalled' field in the JSS
206
+ ### NOTE The package must be indexed before this works. Right now, that means
207
+ ### using CasperAdmin.app
208
+ ###
209
+ ### @param new_val[Boolean]
210
+ ###
211
+ ### @return [void]
212
+ ###
213
+ def allow_uninstalled= (new_val)
214
+ return nil if new_val == @allow_uninstalled
215
+
216
+ ### removable? defaults to false
217
+ ### even though we usually want to be able to ununstall things, it would be
218
+ ### dangerous to do on things like OS updates, so it must be turned on explicitly.
219
+ ### packages must be indexed with Casper Admin in order to be uninstalled.
220
+ new_val = false if new_val.to_s.empty?
221
+ raise JSS::InvalidDataError, "allow_uninstalled must be boolean 'true' or 'false'" unless JSS::TRUE_FALSE.include? new_val
222
+
223
+ @allow_uninstalled= new_val
224
+ @need_to_update = true
225
+
226
+ end
227
+
228
+
229
+ ###
230
+ ### Change the boot volume required field in the JSS
231
+ ###
232
+ ### @param new_val[Boolean]
233
+ ###
234
+ ### @return [void]
235
+ ###
236
+ def boot_volume_required=(new_val)
237
+ return nil if new_val == @boot_volume_required
238
+ new_val = false if new_val.to_s.empty?
239
+ raise JSS::InvalidDataError, "install_if_reported_available must be boolean true or false" unless JSS::TRUE_FALSE.include? new_val
240
+ @boot_volume_required = new_val
241
+ @need_to_update = true
242
+ end
243
+
244
+
245
+ ###
246
+ ### Change the category in the JSS
247
+ ###
248
+ ### @param new_val[String] must be one listed by 'JSS::Category.all_names'
249
+ ###
250
+ ### @return [void]
251
+ ###
252
+ def category= (new_val)
253
+ return nil if new_val == @category
254
+ new_val = nil if new_val == ''
255
+ new_val ||= JSS::Category::DEFAULT_CATEGORY
256
+ raise JSS::InvalidDataError, "Category #{new_val} is not known to the JSS" unless JSS::Category.all_names.include? new_val
257
+ @category = new_val
258
+ @need_to_update = true
259
+ end
260
+
261
+ ###
262
+ ### Change the package filename.
263
+ ### Setting it to nil or empty will make it match the display name
264
+ ###
265
+ ### @param new_val[String]
266
+ ###
267
+ ### @return [void]
268
+ ###
269
+ def filename= (new_val)
270
+ new_val = nil if new_val == ''
271
+ new_val ||= @name
272
+ return nil if new_val == @filename
273
+ $stderr.puts "WARNING: you must manualy change the filename on the Distribution Point(s)" if @in_jss
274
+ @filename = new_val
275
+ @need_to_update = true
276
+ end
277
+
278
+
279
+ ###
280
+ ### Change the Fill Existing Users value
281
+ ###
282
+ ### @param new_val[Boolean]
283
+ ###
284
+ ### @return [void]
285
+ ###
286
+ def fill_existing_users= (new_val)
287
+ return nil if new_val == @fill_existing_users
288
+ new_val = false if new_val.to_s.empty?
289
+ raise JSS::InvalidDataError, "fill_existing_users must be boolean 'true' or 'false'" unless JSS::TRUE_FALSE.include? new_val
290
+ @fill_existing_users = new_val
291
+ @need_to_update = true
292
+ end
293
+
294
+ ###
295
+ ### Change the fill_user_template value
296
+ ###
297
+ ### @param new_val[Boolean]
298
+ ###
299
+ ### @return [void]
300
+ ###
301
+ def fill_user_template= (new_val)
302
+ return nil if new_val == @fill_user_template
303
+ new_val = false if new_val.to_s.empty?
304
+ raise JSS::InvalidDataError, "fill_user_template must be boolean 'true' or 'false'" unless JSS::TRUE_FALSE.include? new_val
305
+ @fill_user_template = new_val
306
+ @need_to_update = true
307
+ end
308
+
309
+
310
+
311
+ ###
312
+ ### Change the info field in the JSS.
313
+ ###
314
+ ### @param new_val[String]
315
+ ###
316
+ ### @return [void]
317
+ ###
318
+ def info= (new_val)
319
+ return nil if new_val == @info
320
+ ### line breaks should be \r
321
+ new_val = new_val.to_s.gsub(/\n/, "\r")
322
+ @info = new_val
323
+ @need_to_update = true
324
+ end
325
+
326
+
327
+ ###
328
+ ### Change the if_in_swupdate field in the JSS
329
+ ###
330
+ ### @param new_val[Boolean]
331
+ ###
332
+ ### @return [void]
333
+ ###
334
+ def install_if_reported_available= (new_val)
335
+ return nil if new_val == @install_if_reported_available
336
+ new_val = false if new_val.to_s.empty?
337
+ raise JSS::InvalidDataError, "install_if_reported_available must be boolean true or false" unless JSS::TRUE_FALSE.include? new_val
338
+ @install_if_reported_available = new_val
339
+ @need_to_update = true
340
+ end
341
+
342
+
343
+
344
+ ###
345
+ ### Change the notes field in the JSS.NewLines are converted \r.
346
+ ###
347
+ ### @param new_val[String]
348
+ ###
349
+ ### @return [void]
350
+ ###
351
+ def notes= (new_val)
352
+ return nil if new_val == @notes
353
+ ### line breaks should be \r
354
+ new_val = new_val.to_s.gsub(/\n/, "\r")
355
+ @notes = new_val
356
+ @need_to_update = true
357
+ end
358
+
359
+ ###
360
+ ### Change the os_requirements field in the JSS
361
+ ### E.g. 10.5, 10.5.3, 10.6.x
362
+ ###
363
+ ### @param new_val[String,Array] comma-separated string, or array of os versions
364
+ ###
365
+ ### @return [void]
366
+ ###
367
+ ### Extra feature: Minumum OS's can now be specified as a
368
+ ### string using the notation ">=10.6.7".
369
+ ###
370
+ ### @see JSS.expand_min_os
371
+ ###
372
+ def os_requirements= (new_val)
373
+ ### nil should be an empty array
374
+ new_val = [] if new_val.to_s.empty?
375
+
376
+ ### if any value starts with >=, expand it
377
+ case new_val
378
+ when String
379
+ new_val = JSS.expand_min_os(new_val) if new_val =~ /^>=/
380
+ when Array
381
+ new_val.map!{|a| a =~ /^>=/ ? JSS.expand_min_os(a) : a }
382
+ new_val.flatten!
383
+ new_val.uniq!
384
+ else
385
+ raise JSS::InvalidDataError, "os_requirements must be a String or an Array of strings"
386
+ end
387
+ ### get the array version
388
+ @os_requirements = JSS.to_s_and_a(new_val)[:arrayform]
389
+ @need_to_update = true
390
+ end
391
+
392
+ ### Is a given OS OK for this package based on its
393
+ ### @os_requirements?
394
+ ###
395
+ ### @param os[String] the os to check, defaults to
396
+ ### the os of the current machine.
397
+ ###
398
+ ### @return [Boolean] can this pkg be installed with the os
399
+ ### given?
400
+ ###
401
+ def os_ok? (os = nil)
402
+ JSS.os_ok? @os_requirements, os
403
+ end
404
+
405
+
406
+ ###
407
+ ### Change the priority field in the JSS
408
+ ###
409
+ ### @param new_val[Integer] one of PRIORITIES
410
+ ###
411
+ ### @return [void]
412
+ ###
413
+ def priority= (new_val)
414
+ return nil if new_val == @priority
415
+ new_val = DEFAULT_PRIORITY if new_val.to_s.empty?
416
+ raise JSS::InvalidDataError, ":priority must be an integer from 1-20" unless PRIORITIES.include? new_val
417
+ @priority = new_val
418
+ @need_to_update = true
419
+ end
420
+
421
+ ###
422
+ ### Change the reboot-required field in the JSS
423
+ ###
424
+ ### @param new_val[Boolean]
425
+ ###
426
+ ### @return [void]
427
+ ###
428
+ def reboot_required= (new_val)
429
+ return nil if new_val == @reboot_required
430
+ new_val = false if new_val.to_s.empty?
431
+ raise JSS::InvalidDataError, "reboot must be boolean 'true' or 'false'" unless JSS::TRUE_FALSE.include? new_val
432
+ @reboot_required = new_val
433
+ @need_to_update = true
434
+ end
435
+
436
+
437
+
438
+ ###
439
+ ### Change the required processor field in the JSS
440
+ ###
441
+ ### @param new_val[String] one of {CPU_TYPES}
442
+ ###
443
+ ### @return [void]
444
+ ###
445
+ def required_processor= (new_val)
446
+ return nil if new_val == @required_processor
447
+
448
+ new_val = DEFAULT_PROCESSOR if new_val.to_s.empty?
449
+ raise JSS::InvalidDataError, "Required_processor must be one of: #{CPU_TYPES.join ', '}" unless CPU_TYPES.include? new_val
450
+
451
+ @required_processor = new_val
452
+ @need_to_update = true
453
+ end
454
+
455
+ ### Is a given processor OK for this package based on its
456
+ ### @required_processor?
457
+ ###
458
+ ### @param processor[String] the processor to check, defaults to
459
+ ### the processor of the current machine.
460
+ ###
461
+ ### @return [Boolean] can this pkg be installed with the processor
462
+ ### given?
463
+ ###
464
+ def processor_ok? (processor = nil)
465
+ JSS.processor_ok? @required_processor, processor
466
+ end
467
+
468
+
469
+
470
+ ### Change the notify field in the JSS
471
+ ###
472
+ ### @param new_val[Boolean]
473
+ ###
474
+ ### @return [void]
475
+ ###
476
+ def send_notification= (new_val)
477
+ return nil if new_val == @send_notification
478
+ new_val = false if new_val.to_s.empty?
479
+ raise JSS::InvalidDataError, "send_notification must be boolean true or false" unless JSS::TRUE_FALSE.include? new_val
480
+ @send_notification = new_val
481
+ @need_to_update = true
482
+ end
483
+
484
+
485
+ ### Change which pkg should be installed if this one can't.
486
+ ###
487
+ ### @param new_val[String] the name of an existing package or "Do Not Install"
488
+ ###
489
+ ### @return [void]
490
+ ###
491
+ def switch_with_package= (new_val)
492
+ return nil if new_val == @switch_with_package
493
+ new_val = nil if new_val.to_s.empty?
494
+
495
+ raise JSS::NoSuchItemError, "No package named '#{new_val}' exists in the JSS" if new_val and not self.class.all_names.include? new_val
496
+
497
+ new_val ||= DO_NOT_INSTALL
498
+ @switch_with_package = new_val
499
+ @need_to_update = true
500
+ end
501
+
502
+ ###
503
+ ### Is this packaged installed on the current machine (via casper)?
504
+ ### We just look for the receipt, which is the @filename less any possible .zip extension.
505
+ ###
506
+ ### @return [Boolean]
507
+ ###
508
+ def installed?
509
+ @receipt.file?
510
+ end
511
+
512
+ ###
513
+ ### Upload a locally-readable file to the master distribution point.
514
+ ### If the file is a directory (like a bundle .pk/.mpkg) it will be zipped before
515
+ ### uploading and the @filename will be adjusted accordingly
516
+ ###
517
+ ### If you'll be uploading several files you can specify unmount as false, and do it manually when all
518
+ ### are finished with JSS::DistributionPoint.master_distribution_point.unmount
519
+ ###
520
+ ### @param local_file_path[String,Pathname] the local path to the file to be uploaded
521
+ ###
522
+ ### @param rw_pw[String,Symbol] the password for the read/write account on the master Distribution Point,
523
+ ### or :prompt, or :stdin# where # is the line of stdin containing the password See {JSS::DistributionPoint#mount}
524
+ ###
525
+ ### @param unmount[Boolean] whether or not ot unount the distribution point when finished.
526
+ ###
527
+ ### @return [void]
528
+ ###
529
+ def upload_master_file (local_file_path, rw_pw, unmount = true)
530
+
531
+ raise JSS::NoSuchItemError, "Please create this package in the JSS before uploading it." unless @in_jss
532
+
533
+ mdp = JSS::DistributionPoint.master_distribution_point
534
+ destination = mdp.mount(rw_pw, :rw) +"#{DIST_POINT_PKGS_FOLDER}/#{@filename}"
535
+
536
+ local_path = Pathname.new local_file_path
537
+ raise JSS::NoSuchItemError, "Local file '#{@local_file}' doesn't exist" unless local_path.exist?
538
+
539
+ ### should we zip it?
540
+ if local_path.directory?
541
+ begin
542
+ zipdir = Pathname.new "/tmp/jssgemtmp-#{Time.new.strftime "%Y%m%d%H%M%S"}-#{$$}"
543
+ zipdir.mkpath
544
+ zipdir.chmod 0700
545
+ zipfile = zipdir + (local_path.basename.to_s + ".zip")
546
+
547
+ ### go to the same dir as the local file
548
+ wd = Dir.pwd
549
+ Dir.chdir local_path.parent
550
+
551
+ ### the contents of the zip file have to have the same name as the zip file itself (minus the .zip)
552
+ ### so temporarily rename the source
553
+ local_path.rename(local_path.parent + @filename)
554
+ raise "There was a problem zipping the pkg bundle" unless system "/usr/bin/zip -qr '#{zipfile}' '#{@filename}'"
555
+
556
+ ensure
557
+ ### rename the source to the original name
558
+ (local_path.parent + @filename).rename local_path if (local_path.parent + @filename).exist?
559
+ ### go back where we started
560
+ Dir.chdir wd
561
+ end # begin
562
+
563
+ ### update our info
564
+ local_path = zipfile
565
+
566
+ self.filename = zipfile.basename.to_s
567
+
568
+ end # if directory
569
+ self.update
570
+ FileUtils.copy_entry local_path, destination
571
+
572
+ mdp.unmount if unmount
573
+ end # upload
574
+
575
+ ###
576
+ ### Delete the filename from the master distribution point, if it exists.
577
+ ###
578
+ ### If you'll be uploading several files you can specify unmount as false, and do it manually when all
579
+ ### are finished.
580
+ ###
581
+ ### @param rw_pw[String] the password for the read/write account on the master Distribution Point
582
+ ### or :prompt, or :stdin# where # is the line of stdin containing the password. See {JSS::DistributionPoint#mount}
583
+ ###
584
+ ### @param unmount[Boolean] whether or not ot unount the distribution point when finished.
585
+ ###
586
+ ### @return [Boolean] was the file deleted?
587
+ ###
588
+ def delete_master_file (rw_pw, unmount = true)
589
+ mdp = JSS::DistributionPoint.master_distribution_point
590
+ file = mdp.mount(rw_pw, :rw) +"#{DIST_POINT_PKGS_FOLDER}/#{@filename}"
591
+ if file.exist?
592
+ file.delete
593
+ did_it = true
594
+ else
595
+ did_it = false
596
+ end # if exists
597
+ mdp.unmount if unmount
598
+ return did_it
599
+ end # delete master file
600
+
601
+
602
+ ### Install this package via the jamf binary 'install' command from the
603
+ ### distribution point for this machine.
604
+ ### See {JSS::DistributionPoint.my_distribution_point}
605
+ ###
606
+ ### @note This code must be run as root to install packages
607
+ ###
608
+ ### The read-only or http passwd for the dist. point must be provided,
609
+ ### except for non-authenticated http downloads)
610
+ ###
611
+ ### @param args[Hash] the arguments for installation
612
+ ###
613
+ ### @option args :ro_pw[String] the read-only or http password for the
614
+ ### distribution point for the local machine
615
+ ### (http will be used if available, and may not need a pw)
616
+ ###
617
+ ### @option args :target[String,Pathname] The drive on which to install
618
+ ### the package, defaults to '/'
619
+ ###
620
+ ### @option args :verbose [Boolean] be verbose to stdout, defaults to false
621
+ ###
622
+ ### @option args :feu[Boolean] fill existing users, defaults to false
623
+ ###
624
+ ### @option args :fut[Boolean] fill user template, defaults to false
625
+ ###
626
+ ### @option args :unmount[Boolean] unmount the distribution point when
627
+ ### finished?(if we mounted it), defaults to false
628
+ ###
629
+ ### @option args :no_http[Boolean] don't use http downloads even if they
630
+ ### are enabled for the dist. point.
631
+ ###
632
+ ### @option args :alt_download_url [String] Use this url for an http
633
+ ### download, regardless of distribution point settings. This can be used
634
+ ### to access Cloud Distribution Points if the fileshare isn't available.
635
+ ### The URL should already be ur
636
+ ### The package filename will be removed or appended as needed.
637
+ ###
638
+ ### @return [Boolean] did the jamf install succeed?
639
+ ###
640
+ ### @todo deal with cert-based https authentication in dist points
641
+ ###
642
+ def install (args = {})
643
+
644
+ raise JSS::UnsupportedError, "You must have root privileges to install packages" unless JSS.superuser?
645
+
646
+ args[:target] ||= '/'
647
+
648
+ ro_pw = args[:ro_pw]
649
+
650
+ # as of Casper 9.72, with http downloads, the jamf binary requires
651
+ # the filename must be at the end of the -path url, but before 9.72
652
+ # it can't be.
653
+ # e.g.
654
+ # in <9.72: jamf install -package foo.pkg -path http://mycasper.myorg.edu/CasperShare/Packages
655
+ # but
656
+ # in >=9.72: jamf install -package foo.pkg -path http://mycasper.myorg.edu/CasperShare/Packages/foo.pkg
657
+ #
658
+ append_at_vers = JSS.parse_jss_version("9.72")[:version]
659
+ our_vers = JSS.parse_jss_version(JSS::API.server.raw_version)[:version]
660
+ no_filename_in_url = (our_vers < append_at_vers)
661
+
662
+ # use a provided alternative url for an http download
663
+ if args[:alt_download_url]
664
+
665
+ # we'll re-add the filename below if needed.
666
+ src_path = args[:alt_download_url].chomp "/#{@filename}"
667
+
668
+ # use our appropriate dist. point for download
669
+ else
670
+ mdp = JSS::DistributionPoint.my_distribution_point
671
+
672
+ ### how do we access our dist. point? with http?
673
+ if mdp.http_downloads_enabled and (not args[:no_http])
674
+ using_http = true
675
+ src_path = mdp.http_url
676
+ if mdp.username_password_required
677
+ raise JSS::MissingDataError, "No password provided for http download" unless ro_pw
678
+ raise JSS::InvaldDatatError, "Incorrect password for http access to distribution point." unless mdp.check_pw(:http, ro_pw)
679
+ # insert the name and pw into the uri
680
+ reserved_chars = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}]") # we'll escape all the chars that aren't unreserved
681
+ src_path = src_path.sub(%r{(https?://)(\S)}, "#{$1}#{URI.escape mdp.http_username,reserved_chars}:#{URI.escape ro_pw, reserved_chars}@#{$2}")
682
+ end
683
+
684
+ # or with filesharing?
685
+ else
686
+ using_http = false
687
+ src_path = mdp.mount(ro_pw)
688
+ end
689
+
690
+ # look at the pkgs folder
691
+ src_path += "#{DIST_POINT_PKGS_FOLDER}"
692
+ end # if args[:alt_download_url]
693
+
694
+ src_path += "/#{@filename}" unless no_filename_in_url
695
+
696
+
697
+ ### are we doing "fill existing users" or "fill user template"?
698
+ do_feu = args[:feu] ? "-feu" : ""
699
+ do_fut = args[:fut] ? "-fut" : ""
700
+
701
+ ### the install args for jamf
702
+ command_args = "-package '#{@filename}' -path '#{src_path}' -target '#{args[:target]}' #{do_feu} #{do_fut} -showProgress -verbose"
703
+
704
+ ### run it via a client cmd
705
+ install_out = JSS::Client.run_jamf :install, command_args, args[:verbose]
706
+
707
+ install_out =~ %r{<exitCode>(\d+)</exitCode>}
708
+ install_exit = $1 ? $1.to_i : nil
709
+ install_exit ||= $?.exitstatus
710
+
711
+
712
+ if (args.include? :unmount)
713
+ mdp.unmount unless using_http
714
+ end
715
+
716
+ return install_exit == 0 ? true : false
717
+ end
718
+
719
+ ###
720
+ ### @note This code must be run as root to uninstall packages
721
+ ###
722
+ ### Causes the pkg to be uninstalled via the jamf command.
723
+ ###
724
+ ### @param args[Hash] the arguments for installation
725
+ ###
726
+ ### @option args :target[String,Pathname] The drive from which to uninstall the package, defaults to '/'
727
+ ###
728
+ ### @option args :verbose[Boolean] be verbose to stdout, defaults to false
729
+ ###
730
+ ### @option args :feu[Boolean] fill existing users, defaults to false
731
+ ###
732
+ ### @option args :fut[Boolean] fill user template, defaults to false
733
+ ###
734
+ ### @return [Process::Status] the result of the 'jamf uninstall' command
735
+ ###
736
+ def uninstall (args = {})
737
+
738
+ raise JSS::UnsupportedError, \
739
+ "This package cannot be uninstalled. Please use CasperAdmin to index it and allow uninstalls" unless removable?
740
+ raise JSS::UnsupportedError, "You must have root privileges to uninstall packages" unless JSS.superuser?
741
+ args[:target] ||= '/'
742
+
743
+ ### are we doing "fill existing users" or "fill user template"?
744
+ do_feu = args[:feu] ? "-feu" : ""
745
+ do_fut = args[:fut] ? "-fut" : ""
746
+
747
+ ### use jamf binary to uninstall the pkg
748
+ jamf_opts = "-target '#{args[:target]}' -id '#{@id}' #{do_feu} #{do_fut}"
749
+
750
+ ### run it via a client
751
+ uninstall_out = JSS::Client.run_jamf "uninstall", jamf_opts, args[:verbose]
752
+
753
+ return $?
754
+ end
755
+
756
+
757
+
758
+ ### What type of package is this?
759
+ ###
760
+ ### @return [Symbol] :pkg or :dmg or:unknown
761
+ ###
762
+ def type
763
+ case @filename
764
+ when /\.m?pkg(\.zip)?$/ then :pkg
765
+ when /\.dmg$/ then :dmg
766
+ else :unknown
767
+ end
768
+ end
769
+
770
+
771
+ ################################
772
+ ### Private Instance Methods
773
+ ################################
774
+
775
+ private
776
+
777
+
778
+ ###
779
+ ### Return the REST XML for this pkg, with the current values,
780
+ ### for saving or updating
781
+ ###
782
+ def rest_xml
783
+ doc = REXML::Document.new APIConnection::XML_HEADER
784
+ pkg = doc.add_element "package"
785
+ pkg.add_element('allow_uninstalled').text = @allow_uninstalled
786
+ pkg.add_element('boot_volume_required').text = @boot_volume_required
787
+ pkg.add_element('category').text = @category.to_s.casecmp("No category assigned") == 0 ? "" : @category
788
+ pkg.add_element('filename').text = @filename
789
+ pkg.add_element('fill_existing_users').text = @fill_existing_users
790
+ pkg.add_element('fill_user_template').text = @fill_user_template
791
+ pkg.add_element('info').text = @info
792
+ pkg.add_element('install_if_reported_available').text = @install_if_reported_available
793
+ pkg.add_element('name').text = @name
794
+ pkg.add_element('notes').text = @notes
795
+ pkg.add_element('os_requirements').text = JSS.to_s_and_a(@os_requirements)[:stringform]
796
+ pkg.add_element('priority').text = @priority
797
+ pkg.add_element('reboot_required').text = @reboot_required
798
+ pkg.add_element('required_processor').text = @required_processor.to_s.empty? ? "None" : @required_processor
799
+ pkg.add_element('send_notification').text = @send_notification
800
+ pkg.add_element('switch_with_package').text = @switch_with_package
801
+ return doc.to_s
802
+ end # rest xml
803
+
804
+ public
805
+
806
+ # aliases under their methods seem to confuse the YARD documenter, so I'm putting them all here.
807
+ alias feu fill_existing_users
808
+ alias feu? fill_existing_users
809
+ alias fut fill_user_template
810
+ alias fut? fill_user_template
811
+ alias reboot reboot_required
812
+ alias reboot? reboot_required
813
+ alias oses os_requirements
814
+ alias cpu_type required_processor
815
+ alias removable allow_uninstalled
816
+ alias removable? allow_uninstalled
817
+ alias if_in_swupdate install_if_reported_available
818
+ alias if_in_swupdate? install_if_reported_available
819
+ alias boot boot_volume_required
820
+ alias boot? boot_volume_required
821
+ alias notify send_notification
822
+
823
+ alias removable= allow_uninstalled=
824
+ alias boot= boot_volume_required=
825
+ alias feu= fill_existing_users=
826
+ alias fut= fill_user_template=
827
+ alias if_in_swupdate= install_if_reported_available=
828
+ alias oses= os_requirements=
829
+ alias reboot= reboot_required=
830
+ alias cpu_type= required_processor=
831
+ alias notify= send_notification=
832
+
833
+
834
+
835
+
836
+
837
+ end # class Package
838
+
839
+ end # module jss