ruby-jss 0.6.3

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.

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