depot3 0.0.0a1 → 3.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +55 -1
  3. data/bin/d3 +323 -0
  4. data/bin/d3admin +1011 -0
  5. data/bin/d3helper +354 -0
  6. data/bin/puppytime +334 -0
  7. data/data/d3/com.pixar.d3.RepoMan.plist +23 -0
  8. data/data/d3/d3.conf.example +507 -0
  9. data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftAppKit.dylib +0 -0
  10. data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftCore.dylib +0 -0
  11. data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftCoreData.dylib +0 -0
  12. data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftCoreGraphics.dylib +0 -0
  13. data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftCoreImage.dylib +0 -0
  14. data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftDarwin.dylib +0 -0
  15. data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftDispatch.dylib +0 -0
  16. data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftFoundation.dylib +0 -0
  17. data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftObjectiveC.dylib +0 -0
  18. data/data/d3/d3RepoMan.app/Contents/Info.plist +56 -0
  19. data/data/d3/d3RepoMan.app/Contents/MacOS/d3RepoMan +0 -0
  20. data/data/d3/d3RepoMan.app/Contents/PkgInfo +1 -0
  21. data/data/d3/d3RepoMan.app/Contents/Resources/Base.lproj/MainMenu.nib +0 -0
  22. data/data/d3/d3RepoMan.app/Contents/Resources/last-foreground-times-template.plist +5 -0
  23. data/data/d3/d3RepoMan.app/Contents/_CodeSignature/CodeResources +214 -0
  24. data/data/d3/puppytime/ImageLicenses.txt +165 -0
  25. data/data/d3/puppytime/notification_image +1 -0
  26. data/data/d3/puppytime/opt_out_image +1 -0
  27. data/data/d3/puppytime/slideshow/2008-07-11_White_German_Shepherd_pup_chilling_at_the_Coker_Arboretum.jpg +0 -0
  28. data/data/d3/puppytime/slideshow/2009-04-21_APBT_pup_on_deck.jpg +0 -0
  29. data/data/d3/puppytime/slideshow/A_puppy_Yorkie.jpg +0 -0
  30. data/data/d3/puppytime/slideshow/Alert_Pug_Puppy.jpg +0 -0
  31. data/data/d3/puppytime/slideshow/Australian_Cattle_Dog_puppies_04.JPG +0 -0
  32. data/data/d3/puppytime/slideshow/Beagle_puppy_Cadet.jpg +0 -0
  33. data/data/d3/puppytime/slideshow/Bernese_Mountain_Dog.jpg +0 -0
  34. data/data/d3/puppytime/slideshow/Bloodhound_Puppy.jpg +0 -0
  35. data/data/d3/puppytime/slideshow/Boston_terrier_with_toy.jpg +0 -0
  36. data/data/d3/puppytime/slideshow/Boxer_puppy_fawn_portrai.jpg +0 -0
  37. data/data/d3/puppytime/slideshow/Caracal_kitten.jpg +0 -0
  38. data/data/d3/puppytime/slideshow/Chihuahua_&_Doberman_Pup.jpg +0 -0
  39. data/data/d3/puppytime/slideshow/Cuccioli_di_Margot_a_35_gg_Basenjis.jpg +0 -0
  40. data/data/d3/puppytime/slideshow/Dalmatian_puppy_03.jpg +0 -0
  41. data/data/d3/puppytime/slideshow/GoldenRetrieverPuppyDaisyParker.JPG +0 -0
  42. data/data/d3/puppytime/slideshow/Green_eyed_beige_Chihuahua.jpg +0 -0
  43. data/data/d3/puppytime/slideshow/Let_Sleeping_Dogs_Lie.jpg +0 -0
  44. data/data/d3/puppytime/slideshow/Meatball_-_French_Bulldog_Puppy.jpg +0 -0
  45. data/data/d3/puppytime/slideshow/Oola_-_9_weeks.jpg +0 -0
  46. data/data/d3/puppytime/slideshow/Pancho0008.JPG +0 -0
  47. data/data/d3/puppytime/slideshow/Pomeranian_orange-sable_Coco.jpg +0 -0
  48. data/data/d3/puppytime/slideshow/Pug_puppy_001.jpg +0 -0
  49. data/data/d3/puppytime/slideshow/Puggle_puppy_6_weeks.JPG +0 -0
  50. data/data/d3/puppytime/slideshow/Puli_kan.jpg +0 -0
  51. data/data/d3/puppytime/slideshow/Puppy_French_Bulldog.jpg +0 -0
  52. data/data/d3/puppytime/slideshow/Rocco_the_Bulldog.jpg +0 -0
  53. data/data/d3/puppytime/slideshow/Rottweiler_Face.jpg +0 -0
  54. data/data/d3/puppytime/slideshow/Saint_Bernard_puppy.jpg +0 -0
  55. data/data/d3/puppytime/slideshow/Scottish_froment.jpg +0 -0
  56. data/data/d3/puppytime/slideshow/Shar_pei_puppy_(age_2_months).jpg +0 -0
  57. data/data/d3/puppytime/slideshow/Shiba-Inu_beim_Spielen_im_Schnee.JPG +0 -0
  58. data/data/d3/puppytime/slideshow/Smooth-coat_Border_Collie_puppy..jpg +0 -0
  59. data/data/d3/puppytime/slideshow/Smooth_Dachshund_puppies.jpg +0 -0
  60. data/data/d3/puppytime/slideshow/Snow_dog.jpg +0 -0
  61. data/data/d3/puppytime/slideshow/Taylor_the_Pembroke_Welsh_Corgi.png +0 -0
  62. data/data/d3/puppytime/slideshow/Weim_Pups_001.jpg +0 -0
  63. data/data/d3/puppytime/slideshow/Westie_pups.jpg +0 -0
  64. data/data/d3/puppytime/slideshow/Yellow_Labrador_puppies_(4165737325).jpg +0 -0
  65. data/lib/d3/admin/add.rb +451 -0
  66. data/lib/d3/admin/auth.rb +470 -0
  67. data/lib/d3/admin/edit.rb +297 -0
  68. data/lib/d3/admin/help.rb +396 -0
  69. data/lib/d3/admin/interactive.rb +972 -0
  70. data/lib/d3/admin/options.rb +454 -0
  71. data/lib/d3/admin/prefs.rb +204 -0
  72. data/lib/d3/admin/report.rb +727 -0
  73. data/lib/d3/admin/state.rb +42 -0
  74. data/lib/d3/admin/validate.rb +413 -0
  75. data/lib/d3/admin.rb +42 -0
  76. data/lib/d3/basename.rb +217 -0
  77. data/lib/d3/client/auth.rb +108 -0
  78. data/lib/d3/client/class_methods.rb +766 -0
  79. data/lib/d3/client/class_variables.rb +47 -0
  80. data/lib/d3/client/cli.rb +187 -0
  81. data/lib/d3/client/environment.rb +134 -0
  82. data/lib/d3/client/help.rb +110 -0
  83. data/lib/d3/client/lists.rb +314 -0
  84. data/lib/d3/client/receipt.rb +1173 -0
  85. data/lib/d3/client.rb +45 -0
  86. data/lib/d3/configuration.rb +319 -0
  87. data/lib/d3/constants.rb +60 -0
  88. data/lib/d3/database.rb +488 -0
  89. data/lib/d3/exceptions.rb +44 -0
  90. data/lib/d3/log.rb +271 -0
  91. data/lib/d3/package/aliases.rb +80 -0
  92. data/lib/d3/package/attributes.rb +97 -0
  93. data/lib/d3/package/class_methods.rb +817 -0
  94. data/lib/d3/package/class_variables.rb +46 -0
  95. data/lib/d3/package/client_actions.rb +293 -0
  96. data/lib/d3/package/constants.rb +58 -0
  97. data/lib/d3/package/constructor.rb +191 -0
  98. data/lib/d3/package/getters.rb +164 -0
  99. data/lib/d3/package/mixins.rb +39 -0
  100. data/lib/d3/package/private_methods.rb +227 -0
  101. data/lib/d3/package/questions.rb +95 -0
  102. data/lib/d3/package/server_actions.rb +683 -0
  103. data/lib/d3/package/setters.rb +326 -0
  104. data/lib/d3/package/validate.rb +448 -0
  105. data/lib/d3/package.rb +51 -0
  106. data/lib/d3/puppytime/pending_puppy.rb +108 -0
  107. data/lib/d3/puppytime/puppy_queue.rb +274 -0
  108. data/lib/d3/puppytime.rb +68 -0
  109. data/lib/d3/state.rb +105 -0
  110. data/lib/d3/utility.rb +325 -0
  111. data/lib/d3/version.rb +1 -1
  112. metadata +162 -9
@@ -0,0 +1,683 @@
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 D3
27
+ class Package < JSS::Package
28
+
29
+ ### Create this package in the JSS if needed, and in d3
30
+ ###
31
+ ### @return [Integer] the JSS id of the package
32
+ ###
33
+ def create
34
+
35
+ # if it's already there, just return
36
+ return @id if @in_d3
37
+
38
+ # gotta know who did this
39
+ raise JSS::MissingDataError, "An admin name must be set before creating this new d3 package. Use #admin= " unless @admin
40
+
41
+ # create the JSS package if needed
42
+ super unless @in_jss
43
+
44
+ # who and when are we adding this pkg?
45
+ @added_date = Time.now
46
+ @added_by = @admin
47
+
48
+ # change status from unsaved to pilot
49
+ @status = :pilot
50
+
51
+ # loop through the field definitions, and
52
+ # use them to get data for the insert statement
53
+ field_names = []
54
+ sql_values = []
55
+ P_FIELDS.each_pair do |key,field_def|
56
+ field_names << field_def[:field_name]
57
+ # nils and empty strings become NULL
58
+ sql_values << (self.send(key).to_s.empty? ? 'NULL' : "'#{to_sql(key)}'")
59
+ end # do |key,field_def
60
+
61
+
62
+ # use the two arrays to build the SQL statement
63
+ stmt = JSS::DB_CNX.db.prepare <<-ENDINSERT
64
+ INSERT INTO #{P_TABLE[:table_name]} (
65
+ #{field_names.join(",\n ")}
66
+ ) VALUES (
67
+ #{sql_values.join(",\n ")}
68
+ )
69
+ ENDINSERT
70
+
71
+ # Execute it to create the record
72
+ stmt_result = stmt.execute
73
+
74
+
75
+ # while we're writing to the db, mark any missing packages as missing
76
+ mark_missing_packages
77
+
78
+
79
+ @in_d3 = true
80
+ return @id
81
+ end # create
82
+
83
+ ### Update this package in the JSS and in d3
84
+ ###
85
+ ### @return [Integer] the JSS id of the package
86
+ ###
87
+ def update
88
+
89
+ # we might be importing an existing JSS pkg to d3, which
90
+ # means we need to create the d3 record, but the JSS record needs updating
91
+ create if @importing and (not @in_d3)
92
+
93
+ # update the JSS first, if needed
94
+ super
95
+
96
+ # and return unless we need to do something.
97
+ return unless @need_to_update_d3
98
+
99
+ # Loop thru the field defs to build the SQL update statement
100
+ new_vals = []
101
+ P_FIELDS.each_pair do |key,field_def|
102
+
103
+ # start builing the SET clause values, e.g. "basename = 'foobar'"
104
+ field_val = "#{field_def[:field_name]} = "
105
+
106
+ # finish the SET clause value
107
+ field_val << (self.send(key).to_s.empty? ? 'NULL' : "'#{to_sql(key)}'")
108
+
109
+ # add it to the array
110
+ new_vals << field_val
111
+ end # do |key,field_def
112
+
113
+ # use the new_vals array to create the update statement
114
+ stmt = JSS::DB_CNX.db.prepare <<-ENDUPDATE
115
+ UPDATE #{P_TABLE[:table_name]} SET
116
+ #{new_vals.join(", ")}
117
+ WHERE
118
+ #{P_FIELDS[:id][:field_name]} = #{@id}
119
+ ENDUPDATE
120
+
121
+ # Execute it to update the record
122
+ stmt_result = stmt.execute
123
+
124
+ # while we're writing to the db, mark any missing packages as missing
125
+ mark_missing_packages
126
+
127
+ return @id
128
+
129
+ end # update
130
+
131
+ ### An alias for both save and update
132
+ ###
133
+ def save
134
+ if @in_jss
135
+ update # this will create the d3 data if needed
136
+ else
137
+ create
138
+ end
139
+ end # save
140
+
141
+ ### Make this package the live one for its basename
142
+ ###
143
+ ### @param admin[String] the name of the admin doing this.
144
+ ###
145
+ ### @return [void]
146
+ ###
147
+ def make_live(admin = @admin)
148
+
149
+ return :live if @status == :live
150
+
151
+ # gotta know who did this
152
+ raise JSS::MissingDataError, "An admin name must be set before making this d3 package live. Use the admin= method." if admin.to_s.empty?
153
+ @admin = admin
154
+
155
+ # who and when are we making this pkg live?
156
+ @release_date = Time.now
157
+ @released_by = @admin
158
+
159
+ id_field = P_FIELDS[:id][:field_name]
160
+ status_field = P_FIELDS[:status][:field_name]
161
+ basename_field = P_FIELDS[:basename][:field_name]
162
+ rel_date_field = P_FIELDS[:release_date][:field_name]
163
+ rel_by_field = P_FIELDS[:released_by][:field_name]
164
+
165
+
166
+
167
+ # if any OLDER pkg is live for this basename, make it deprecated
168
+ q = <<-ENDUPDATE
169
+ UPDATE #{P_TABLE[:table_name]}
170
+ SET #{status_field} = '#{P_FIELDS[:status][:to_sql].call(:deprecated)}'
171
+ WHERE #{basename_field} = '#{to_sql :basename}'
172
+ AND #{status_field} = '#{P_FIELDS[:status][:to_sql].call(:live)}'
173
+ AND #{id_field} < '#{to_sql(:id)}'
174
+ ENDUPDATE
175
+ stmt = JSS::DB_CNX.db.prepare q
176
+ stmt_result = stmt.execute
177
+
178
+ # now make any older pilot pkgs for this basename :skipped
179
+ q = <<-ENDUPDATE
180
+ UPDATE #{P_TABLE[:table_name]}
181
+ SET #{status_field} = '#{P_FIELDS[:status][:to_sql].call(:skipped)}'
182
+ WHERE #{basename_field} = '#{to_sql :basename}'
183
+ AND #{id_field} < #{to_sql(:id)}
184
+ AND #{status_field} = '#{P_FIELDS[:status][:to_sql].call(:pilot)}'
185
+ ENDUPDATE
186
+ stmt = JSS::DB_CNX.db.prepare q
187
+ stmt_result = stmt.execute
188
+
189
+
190
+ # any NEWER pkgs for this basename, become pilot (perhaps again)
191
+ # This is for when we re-enliven an old pkg
192
+ q = <<-ENDUPDATE
193
+ UPDATE #{P_TABLE[:table_name]}
194
+ SET #{status_field} = '#{P_FIELDS[:status][:to_sql].call(:pilot)}'
195
+ WHERE #{basename_field} = '#{to_sql :basename}'
196
+ AND #{id_field} > '#{to_sql(:id)}'
197
+ ENDUPDATE
198
+ stmt = JSS::DB_CNX.db.prepare q
199
+ stmt_result = stmt.execute
200
+
201
+ # now make this pkg live
202
+ @status = :live
203
+ q = <<-ENDUPDATE
204
+ UPDATE #{P_TABLE[:table_name]} SET
205
+ #{status_field} = '#{to_sql :status}',
206
+ #{rel_by_field} = '#{to_sql :released_by}',
207
+ #{rel_date_field} = '#{to_sql :release_date}'
208
+ WHERE #{id_field} = #{@id}
209
+ ENDUPDATE
210
+ stmt = JSS::DB_CNX.db.prepare q
211
+ stmt_result = stmt.execute
212
+
213
+ # update our knowledge of the world
214
+ self.class.package_data :refresh
215
+
216
+ # while we're writing to the db, mark any missing packages as missing
217
+ mark_missing_packages
218
+
219
+ puts "Done '#{edition}' is now live."
220
+
221
+ # auto_clean if we should
222
+ auto_clean(admin) if D3::CONFIG.admin_auto_clean
223
+
224
+ # run any post-make-live script if needed
225
+ run_make_live_script
226
+
227
+ end # make live
228
+
229
+ ### Add or replace a pre- or post- script for this package.
230
+ ###
231
+ ### This adds a new script to the JSS, and the sets this package to
232
+ ### use it.
233
+ ###
234
+ ### If the desired script already exists in the JSS, use an appropriate setter method:
235
+ ### {#pre_install_script_id=}, {#post_install_script_id=}, {#pre_remove_script_id=}, {#post_remove_script_id=},
236
+ ### {#pre_install_script_name=},{#post_install_script_name=}, {#pre_remove_script_name=}, {#post_remove_script_name=}
237
+ ###
238
+ ### @param args[Hash]
239
+ ###
240
+ ### @option args :script_type[Symbol] which script to set? One of :pre_install, :post_install, :pre_remove, :post_remove
241
+ ###
242
+ ### @option args :source[String,Pathname] the script code, or a full path to a file containing the script code.
243
+ ### If the value is a String and doesn't start with a /, it's considered to be the script code.
244
+ ###
245
+ ### @option args :script_name[String] the name of the new script in the JSS. Defaults to "<basename>-d3<script_type>-YYYYmmddHHMMSS"
246
+ ###
247
+ ### @option args :script_category[String] the name of the JSS category for this script. Defaults to the value of D3:Package::DFT_SCRIPT_CATEGORY
248
+ ###
249
+ ### @option args :delete_current[Boolean] if this new script is replacing one for this pkg, should the old one be deleted from the JSS?
250
+ ###
251
+ ### @return [Integer] the id of the newly created JSS::Script.
252
+ ###
253
+ def new_script (args = {})
254
+
255
+ raise JSS::InvalidDataError, ":script_type must be one of :#{SCRIPT_TYPES.join(', :')}" unless SCRIPT_TYPES.include? args[:script_type]
256
+
257
+ args[:script_category] ||= D3::CONFIG.jss_default_script_category
258
+ if args[:script_category]
259
+ raise JSS::NoSuchItemError, "No such category '#{args[:script_category]}' in the JSS." unless JSS::Category.all_names.include? args[:script_category]
260
+ end
261
+
262
+ args[:script_name] ||= "#{@basename}-d3#{args[:script_type]}-#{Time.now.strftime('%Y%m%d%H%M%S')}"
263
+
264
+ file_source = nil
265
+
266
+ file_source = case args[:source]
267
+ when Pathname
268
+ args[:source]
269
+ when String
270
+ Pathname.new(args[:source]) if args[:source].start_with? "/"
271
+ else
272
+ raise JSS::InvalidDataError, ":source must be a full path (Pathname or String), or a String containing the script code."
273
+ end # case
274
+
275
+ if file_source
276
+ raise JSS::MissingDataError, "The file #{file_source} is missing or unreadable." unless file_source.readable?
277
+ code = file_source.read
278
+ else
279
+ code = args[:source]
280
+ end
281
+
282
+ # get the new script into the JSS
283
+ script = JSS::Script.new :id => :new, :name => args[:script_name]
284
+ script.contents = code
285
+ script.category = args[:script_category]
286
+ new_script_id = script.save
287
+
288
+ # update our knowledge of all JSS scripts so the next steps don't fail.
289
+ JSS::Script.all :refresh
290
+
291
+ case args[:script_type]
292
+ when :pre_install
293
+ old_script_id = pre_install_script_id
294
+ self.pre_install_script_id = new_script_id
295
+ when :post_install
296
+ old_script_id = post_install_script_id
297
+ self.post_install_script_id = new_script_id
298
+ when :pre_remove
299
+ old_script_id = pre_remove_script_id
300
+ self.pre_remove_script_id = new_script_id
301
+ when :post_remove
302
+ old_script_id = post_remove_script_id
303
+ self.post_remove_script_id = new_script_id
304
+ end
305
+
306
+ # delete the old?
307
+ if args[:delete_current] and old_script_id
308
+ JSS::Script.new(:id => old_script_id).delete if JSS::Script.all_ids.include? old_script_id
309
+ end
310
+
311
+ new_script_id
312
+ end # new_script
313
+
314
+ ### Perform any auto_cleanup, if the config says we should
315
+ ###
316
+ ### @param admin[String] the admin doing the make-live
317
+ ###
318
+ ### @return [void]
319
+ ###
320
+ def auto_clean (admin)
321
+
322
+ ### safety
323
+ return unless D3::CONFIG.admin_auto_clean
324
+
325
+ puts "Starting auto-clean of old packages for '#{@basename}'"
326
+
327
+ #### First the deprecated pkgs
328
+
329
+ # the id's of the deprecated pkgs for this basename, in numerical order
330
+ # the last ones are the newest.
331
+ deprecated_ids = D3::Package.deprecated_data.values.select{|dp| dp[:basename] == @basename}
332
+ deprecated_ids.map!{|dp| dp[:id] }.sort!
333
+
334
+ # keeping any?
335
+ number_deprecated_to_keep = D3::CONFIG.admin_auto_clean_keep_deprecated
336
+ number_deprecated_to_keep ||= 0
337
+
338
+ puts "Keeping #{number_deprecated_to_keep} deprecated packages."
339
+
340
+ # 'pop' pulls them off the end
341
+ deprecated_ids_to_keep = []
342
+ number_deprecated_to_keep.times{ deprecated_ids_to_keep << deprecated_ids.pop }
343
+ deprecated_ids_to_keep.compact!
344
+
345
+ # delete them if we should
346
+ deprecated_ids.each do |id|
347
+ next if deprecated_ids_to_keep.include? id
348
+ victim = D3::Package.new(:id => id)
349
+ victim.delete(
350
+ admin: admin,
351
+ delete_scripts: true,
352
+ keep_in_jss: false,
353
+ rwpw: D3::Admin::Auth.rw_credentials(:dist)[:password]
354
+ )
355
+ puts "Deleted deprecated package: #{victim.edition}, id:#{victim.id}, filename: #{victim.filename}."
356
+ end
357
+
358
+ #### then the skipped pkgs
359
+
360
+ skipped_ids = D3::Package.skipped_data.values.select{|sp| sp[:basename] == @basename}
361
+ skipped_ids.map!{|sp| sp[:id] }.sort!
362
+
363
+ # keep the ones newer than the just-deprecated pkg?
364
+ if D3::CONFIG.admin_auto_clean_keep_latest_pilots
365
+ deprecated_ids = D3::Package.deprecated_data(:refresh).values.select{|dp| dp[:basename] == @basename}
366
+ deprecated_ids.map!{|dp| dp[:id] }
367
+ just_deprecated = deprecated_ids.max
368
+ just_deprecated ||= 0
369
+ skipped_ids_to_keep = skipped_ids.select{|id| id > just_deprecated }
370
+ puts "Keeping most recent pilot packages as skipped."
371
+ # no, delete them all
372
+ else
373
+ skipped_ids_to_keep = []
374
+ puts "Not keeping any pilot or skipped packages."
375
+ end
376
+
377
+ # delete them if we should
378
+ skipped_ids.each do |id|
379
+ next if skipped_ids_to_keep.include? id
380
+ victim = D3::Package.new(:id => id)
381
+ victim.delete(
382
+ admin: admin,
383
+ delete_scripts: true,
384
+ keep_in_jss: false,
385
+ rwpw: D3::Admin::Auth.rw_credentials(:dist)[:password]
386
+ )
387
+ puts "Deleted skipped package: #{victim.edition}, id:#{victim.id}, filename: #{victim.filename}."
388
+ end
389
+ puts "Finished auto-clean of old packages for '#{@basename}'"
390
+ return true
391
+ end
392
+
393
+ ### Delete this package from d3, possibly leaving it in the JSS
394
+ ###
395
+ ### @param keep_in_jss[Boolean] should we keep the JSS package around? defaults to false
396
+ ###
397
+ ### @param delete_scripts[Boolean] should the related scripts also be deleted?
398
+ ###
399
+ ### @param admin[String] who's doing this?
400
+ ###
401
+ ### @param rwpw[String] the read-write for the master distr. point
402
+ ###
403
+ ### @return [Array<String>] a textual list of scripts delted and not
404
+ ### deleted because they're in use by other d3 pkgs or casper policies
405
+ ### (empty if delete_scripts is false)
406
+ ###
407
+ def delete (keep_in_jss: false, delete_scripts: false, admin: @admin, rwpw: nil)
408
+
409
+ unless keep_in_jss
410
+ # raise an exception if any polcies are using this pkg.
411
+ pols = policy_ids
412
+ unless pols.empty?
413
+ names = pols.map{|pid| JSS::Policy.map_all_ids_to(:name)[pid]}.join(', ')
414
+ raise JSS::UnsupportedError, "Can't delete package from JSS, used by these policies: #{names} "
415
+ end # unless pols.empty
416
+ end # unles keep in jss
417
+
418
+ # use @ admin if its defined and needed
419
+ admin ||= @admin
420
+
421
+ # if delete_scripts
422
+ script_actions = delete_scripts ? delete_pkg_scripts : []
423
+
424
+ # delete it from the pakcages table
425
+ stmt = JSS::DB_CNX.db.prepare "DELETE FROM #{P_TABLE[:table_name]} WHERE #{P_FIELDS[:id][:field_name]} = '#{@id}'"
426
+ stmt_result = stmt.execute
427
+
428
+ @status = :deleted
429
+
430
+ # delete it from the JSS unless asked not to
431
+ unless keep_in_jss
432
+ super delete_file: true, rw_pw: rwpw, unmount: false
433
+ end
434
+
435
+ # while we're writing to the db, mark any missing packages as missing
436
+ mark_missing_packages
437
+
438
+ # update our knowledge of the world
439
+ D3::Package.package_data :refresh
440
+
441
+ return script_actions
442
+ end
443
+
444
+ ### Learn the apple package id's installed by this pkg by
445
+ ### querying the package on the current dist. point. This is primarily used
446
+ ### for importing or repairing packages already on the server.
447
+ ###
448
+ ### When adding new packages, the {#upload_master_file} method will query the
449
+ ### data before uploading the file.
450
+ ###
451
+ ###
452
+ ### @param dist_pw[String] the read-only or read-write password for the dist. point for this machine
453
+ ###
454
+ ### @param unmount[Boolean] should the dist.point be unmounted when done?
455
+ ###
456
+ ### @return [void]
457
+ ###
458
+ def update_apple_receipt_data(dist_pw, unmount = true)
459
+ return nil if @filename.end_with? ".dmg"
460
+ raise JSS::NoSuchItemError, "Please create this package on the server before updating the Apple receipt data" unless @in_jss
461
+
462
+ mdp = JSS::DistributionPoint.my_distribution_point
463
+ raise JSS::MissingDataError, "Missing :dist_pw for distrib. point '#{mdp.name}'" unless dist_pw
464
+
465
+ # try the passwd both with ro and rw
466
+ begin
467
+ mnt_path = mdp.mount(dist_pw, :ro)
468
+ rescue JSS::InvalidDataError
469
+ mnt_path = mdp.mount(dist_pw, :rw)
470
+ end
471
+
472
+ pkg_path = mnt_path + JSS::Package::DIST_POINT_PKGS_FOLDER + @filename
473
+ raise JSS::NoSuchItemError, "Package file #{@filename} doesn't exist on the current dist. point." unless pkg_path.exist?
474
+
475
+ pkg_to_query = pkg_path
476
+
477
+ # do we need to unzip a bundle pkg?
478
+ if @filename.end_with? ".zip"
479
+ work_dir = Pathname.new Dir.mktmpdir
480
+ unless system "/usr/bin/unzip -qq -o -d #{Shellwords.escape work_dir.to_s} #{Shellwords.escape pkg_path.to_s}"
481
+ raise RuntimeError, "Failed to unzip bundle pkg #{@filename}"
482
+ end #system
483
+ pkg_to_query = work_dir + @filename.sub(/\.zip$/, '')
484
+ cleanup_work_dir = true
485
+ end # if @filename.end_with? ".zip"
486
+
487
+ @apple_receipt_data = D3::Package.receipt_data_from_pkg(pkg_to_query)
488
+ @need_to_update_d3 = true unless @initializing
489
+ work_dir.rmtree if cleanup_work_dir
490
+ mdp.unmount if unmount
491
+ end # update_apple_receipt_data
492
+
493
+
494
+ ### Mark missing packages as so on the server
495
+ ###
496
+ ### This should run any time we write to the d3_packages table
497
+ ###
498
+ ### @return [void]
499
+ def mark_missing_packages
500
+ missing_ids = self.class.missing_data.keys
501
+ unless missing_ids.empty?
502
+ q = <<-ENDUPDATE
503
+ UPDATE #{P_TABLE[:table_name]}
504
+ SET #{ P_FIELDS[:status][:field_name]} = '#{P_FIELDS[:status][:to_sql].call(:missing)}'
505
+ WHERE #{P_FIELDS[:id][:field_name]} IN (#{missing_ids.join(',')})
506
+ ENDUPDATE
507
+ stmt = JSS::DB_CNX.db.prepare q
508
+ stmt_result = stmt.execute
509
+ end # unless empty
510
+ end # mark missing pkgs
511
+
512
+ ### Upload a locally-readable file to the master distribution point.
513
+ ### If the file is a directory (like a bundle .pk/.mpkg) it will be zipped before
514
+ ### uploading and the @filename will be adjusted accordingly
515
+ ###
516
+ ### If you'll be uploading several files you can specify unmount as false, and do it manually when all
517
+ ### are finished with JSS::DistributionPoint.master_distribution_point.unmount
518
+ ###
519
+ ### This method is mostly performed by the parent class, see {JSS::Package.upload_master_file}.
520
+ ### Before calling super, this method populates @apple_receipt_data with info
521
+ ### from the local file.
522
+ ###
523
+ ### @param local_file_path[String,Pathname] the local path to the file to be uploaded
524
+ ###
525
+ ### @param rw_pw[String,Symbol] the password for the read/write account on the master Distribution Point,
526
+ ### or :prompt, or :stdin# where # is the line of stdin containing the password See {JSS::DistributionPoint#mount}
527
+ ###
528
+ ### @param unmount[Boolean] whether or not ot unount the distribution point when finished.
529
+ ###
530
+ ### @return [void]
531
+ ###
532
+ def upload_master_file (local_file_path, rw_pw, unmount = true)
533
+ raise JSS::NoSuchItemError, "Please create this package in d3 before uploading it." unless @in_d3
534
+ if local_file_path.to_s =~ PKG_RE
535
+ @apple_receipt_data = D3::Package.receipt_data_from_pkg(local_file_path)
536
+ @need_to_update_d3 = true
537
+ self.save
538
+ end # if local_file_path.to_s =~ PKG_RE
539
+
540
+ super
541
+ end
542
+
543
+ ### Create, or re-create, the BOM index records for this
544
+ ### Package in the JSS Database.
545
+ ###
546
+ ### This is the equivalent of clicking the "index" button
547
+ ### in Casper Admin.app, and is necessary for Casper to
548
+ ### be able to uninstall items. It can only happen after the
549
+ ### item has already been saved to the JSS and has an
550
+ ### id in the database.
551
+ ###
552
+ ### @param args[Hash] The arguments for the method
553
+ ###
554
+ ### @option args :local_filepath[String,Pathname] the path
555
+ ### to a local copy of the installer pkg/dmg
556
+ ###
557
+ ### @option args :ro_pw[String] the read-only password
558
+ ### for the AFP/SMB share of the master distribution point.
559
+ ###
560
+ ### @return [void]
561
+ ###
562
+ def mk_index(args = {})
563
+
564
+ raise JSS::NoSuchItemError, "Please create this package in the JSS before indexing it." unless @in_jss
565
+ raise JSS::InvalidConnectionError, "Indexing a package requires a database connection. Use JSS::DB_CNX.connect" unless JSS::DB_CNX.connected?
566
+
567
+ if args[:local_filepath]
568
+ file_to_index = Pathname.new(args[:local_filepath])
569
+
570
+ elsif args[:ro_pw]
571
+ mdp = JSS::DistributionPoint.master_distribution_point
572
+ file_to_index = mdp.mount(args[:ro_pw], :ro) +"#{DIST_POINT_PKGS_FOLDER}/#{@filename}"
573
+ if file_to_index.to_s.end_with? ".zip"
574
+ tmpdir = Pathname.new "/tmp/jss-tmp-#{$$}"
575
+ system "/usr/bin/unzip '#{thing_to_index}' -d '#{tmpdir}'"
576
+ file_to_index = tmpdir + file_to_index.basename.to_s.sub(/.zip$/, '')
577
+ end
578
+
579
+ else
580
+ raise JSS::InvalidDataError, "Need a :local_filepath or :ro_pw"
581
+ end
582
+
583
+ # get the index data
584
+ # is it an (m)pkg?
585
+ if file_to_index.to_s =~ /\.m?pkg$/
586
+ bom_lines = ''
587
+
588
+ # if the thing is a pkg bundle, find and read all the bom files it contains
589
+ if (file_to_index + "Contents").directory?
590
+ (file_to_index + "Contents").find do |path|
591
+ bom_lines += `echo; /usr/bin/lsbom -p fugTsMc '#{path}'` if path.to_s =~ /\.bom$/
592
+ end # do path
593
+
594
+ else
595
+ # else its a flat file - so do it using pkgutil
596
+ bom_files = `/usr/sbin/pkgutil --bom '#{file_to_index}'`
597
+ bom_files.split("\n").each do |file|
598
+ bom_lines += `/usr/bin/lsbom -p fugTsMc '#{file}'`
599
+ end
600
+ end # .directory?
601
+
602
+ elsif file_to_index.to_s =~ /\.dmg$/
603
+
604
+ # if its a .dmg, mount it, make a tmp bom file, and read that
605
+ mnt_line = `/usr/bin/hdiutil attach -readonly -nobrowse -noautoopen -owners on '#{file_to_index}'`.lines.last
606
+ mnt_point = Pathname.new mnt_line.split("\t").last.chomp
607
+ raise FileServiceError, "There was a problem mounting the image #{file_to_index}" unless mnt_point.mountpoint?
608
+
609
+ tmp_bom = "/tmp/#{@filename}.#{$$}.bom"
610
+ system "/usr/bin/mkbom '#{mnt_point}' '#{tmp_bom}'"
611
+ bom_lines = `/usr/bin/lsbom -p fugTsMc '#{tmp_bom}'`
612
+
613
+ system "/usr/bin/hdiutil detach '#{mnt_point}'"
614
+ system "rm -rf '#{tmp_bom}'"
615
+
616
+ else
617
+ raise JSS::InvalidDataError, "#{@filename} is doesn't looks like a .pkg or .dmg. Try Casper Admin to index it."
618
+ end # if filename .pkg
619
+
620
+ # If there are no bomlines (perhaps a payloadless pkg?) just return
621
+ return true if bom_lines.empty?
622
+
623
+ # split the bom lines
624
+ index_records = bom_lines.split "\n"
625
+
626
+ # reset our lists of files
627
+ @index = []
628
+ @file_list = []
629
+
630
+ # the start of the SQL insert statement
631
+ insert_stmt = "INSERT INTO package_contents (package_id,file,owner_name,group_name,modification_date,size,mode,checksum) VALUES"
632
+ insert_vals = []
633
+
634
+ # loop through the bom data and make a new record for each line
635
+ index_records.each do |line|
636
+ next if line.empty?
637
+
638
+ #break out the data for each item
639
+ (path,uid,gid,modtime,size,mode,checksum) = line.split "\t"
640
+
641
+ # if the path is just a dot (usually the first one)
642
+ # make it a /
643
+ if path == "."
644
+ clean_path = "/"
645
+ elsif path.start_with? "."
646
+ clean_path = path.sub ".", ""
647
+ else
648
+ clean_path = path
649
+ end
650
+
651
+ # rebuild our local lists of files
652
+ @index << { 'path' => clean_path,
653
+ 'uid' => uid,
654
+ 'gif' => gid,
655
+ 'modtime' => modtime,
656
+ 'size' => size,
657
+ 'mode' => mode }
658
+
659
+ @file_list << clean_path unless mode.start_with? "d"
660
+
661
+ # JSS stores modtime as string w/o the weekday
662
+ modtime.gsub!(/^(Sun|Mon||Tue|Wed|Thu|Fri|Sat) /, '') if defined? modtime
663
+
664
+ insert_vals << "('#{@id}','#{Mysql.quote clean_path}','#{uid}','#{gid}','#{modtime}','#{size}','#{mode}','#{checksum}')"
665
+
666
+ end # do line
667
+
668
+ # first delete any existing index records for this pkg
669
+ stmt = JSS::DB_CNX.db.prepare "DELETE FROM #{PKG_CONTENTS_TABLE} WHERE package_id = #{@id}"
670
+ stmt_result = stmt.execute
671
+
672
+ # now insert the new values
673
+ stmt = JSS::DB_CNX.db.prepare(insert_stmt + " " + insert_vals.join(','))
674
+ stmt_result = stmt.execute
675
+
676
+ # while we're writing to the db, mark any missing packages as missing
677
+ mark_missing_packages
678
+
679
+ return true
680
+ end #mk_index
681
+
682
+ end # class Package
683
+ end # module D3