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,727 @@
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
+ module D3
26
+ module Admin
27
+ module Report
28
+ extend self
29
+
30
+ ### TODO add report of last_usage for expirable rcpts
31
+
32
+ ###### Reports for 'd3admin report'
33
+
34
+ ### Report on all computer receipts for a given basename
35
+ def report_basename_receipts (basename, statuses)
36
+
37
+ unless D3::Package.all_basenames.include? basename
38
+ puts "# No basename '#{basename}' in d3"
39
+ return
40
+ end
41
+
42
+ # get the raw data
43
+ raw_data = computer_receipts_data
44
+ got_ea = D3::CONFIG.report_receipts_ext_attr_name
45
+ if raw_data.nil? or raw_data.empty?
46
+ puts "# No computers with receipts for '#{basename}'"
47
+ return
48
+ end
49
+
50
+ # json leaves status as a string
51
+ statuses = statuses.map{|s| s.to_s}
52
+ # this separates out the frozen filtering from the status filtering
53
+ # statuses are OR'd, all of them are ANDd with frozen
54
+ # and lets us build meaningful header lines
55
+ filter_frozen = statuses.include? "frozen"
56
+ if filter_frozen
57
+ statuses.delete("frozen")
58
+ status_display = " frozen #{statuses.join(" or ")}"
59
+ else
60
+ status_display = " #{statuses.join(" or ")}"
61
+ end
62
+
63
+ # set the title... reporting on which recipts?
64
+ title = "All computers with#{status_display} '#{basename}' receipts"
65
+
66
+ # set the header
67
+ if got_ea
68
+ header = %w{Computer User Edition Status As_of Frozen Installed By}
69
+ else
70
+ header =%w{Computer User Edition Status As_of }
71
+ end # case
72
+
73
+ lines= []
74
+
75
+ raw_data.each do |computer|
76
+ next unless computer
77
+
78
+ # skip computers without this basename
79
+ next unless computer[:rcpts] and computer[:rcpts].keys.include?(basename)
80
+
81
+ rcpt = computer[:rcpts][basename]
82
+
83
+ # if we were asked for frozen, skip rcpts not frozen
84
+ if filter_frozen
85
+ next unless rcpt[:frozen]
86
+ end
87
+
88
+ # if we were asked for certain statuses,
89
+ # skip rcpts without that status
90
+ unless statuses.empty?
91
+ next unless statuses.include? rcpt[:status]
92
+ end
93
+
94
+ # build a line for this rcpt
95
+ rcpt_line = []
96
+ rcpt_line << computer[:computer]
97
+ rcpt_line << computer[:user]
98
+ rcpt_line << "#{basename}-#{rcpt[:version]}-#{rcpt[:revision]}"
99
+ rcpt_line << rcpt[:status]
100
+ rcpt_line << (computer[:as_of] ? computer[:as_of].strftime("%Y-%m-%d") : nil)
101
+
102
+ if got_ea
103
+ rcpt_line << (rcpt[:frozen] ? "frozen" : "-")
104
+ rcpt_line << (rcpt[:installed_at] ? rcpt[:installed_at].strftime("%Y-%m-%d") : nil)
105
+ rcpt_line << rcpt[:admin]
106
+ end # if rcpt[:installed_at]
107
+
108
+ lines << rcpt_line
109
+ end # raw_data.each do |computer|
110
+
111
+ if lines.empty?
112
+ puts "# No#{status_display} receipts for '#{basename}' were found"
113
+ else
114
+ D3.less_text D3.generate_report(lines.sort_by{|c| c[0]}, header_row: header, title: title)
115
+ end # if lines emtpy?
116
+
117
+ end # report_basename_receipts (basename, statuses)
118
+
119
+ ### Show a report of all current d3 rcpts on a given computer
120
+ ###
121
+ ### @param computer[String] the name of the computer to report on
122
+ ###
123
+ ### @param statuses[Array] the statuses to report on, all if empty
124
+ ###
125
+ ### @return [void]
126
+ ###
127
+ def report_single_computer_receipts (computer_name, statuses)
128
+
129
+ unless JSS::Computer.all_names.include? computer_name
130
+ puts "# No computer named '#{computer_name}' in Casper"
131
+ return
132
+ end
133
+
134
+ computer = JSS::Computer.new name: computer_name
135
+
136
+ ea_name = D3::CONFIG.report_receipts_ext_attr_name
137
+
138
+ # data from EA?
139
+ if ea_name
140
+ ea_data = computer.extension_attributes.select{|ea| ea[:name] == ea_name}.first[:value]
141
+ if ea_data.empty?
142
+ puts "No d3 receipts on computer '#{computer_name}'"
143
+ return false
144
+ elsif not ea_data.start_with?('{')
145
+ puts "The '#{ea_name}' extention attribute data for computer '#{computer_name}' is bad"
146
+ return false
147
+ end # if ea_data.empty?
148
+
149
+ rcpt_data = JSON.parse ea_data , :symbolize_names => true
150
+
151
+ # no EA, use casper rcpts
152
+ else
153
+ pkg_filenames_to_ids = D3::Package.all_filenames.invert
154
+ rcpt_data = {}
155
+ computer.software[:installed_by_casper].each do |jrcpt|
156
+ rcpt_id = pkg_filenames_to_ids[jrcpt]
157
+ # some might be zipped
158
+ rcpt_id ||= pkg_filenames_to_ids[jrcpt + ".zip"]
159
+
160
+ next unless rcpt_id
161
+ pkg_data = D3::Package.package_data[rcpt_id]
162
+ next unless pkg_data
163
+
164
+ rcpt_data[pkg_data[:basename]] = {:version =>pkg_data[:version], :revision => pkg_data[:revision], :status => pkg_data[:status]}
165
+ end # computer.software[:installed_by_casper].each
166
+
167
+ end # if ea_name ... else
168
+
169
+ # now rcpt_data is a hash of hashes {basename => { version, etc...} }
170
+
171
+ # start building the report
172
+
173
+ # title
174
+ last_recon = computer.last_recon.strftime("%Y-%m-%d")
175
+ title = "Receipts on '#{computer_name}' (user: #{computer.username}) as of #{last_recon}"
176
+
177
+ # header...
178
+ if ea_name
179
+ header = %w{Edition Status As_of Frozen Installed By }
180
+ else
181
+ header = %w{Edition Status As_of }
182
+ end # case
183
+
184
+ # json leaves status as a string
185
+ statuses = statuses.map{|s| s.to_s}
186
+ # this separates out the frozen filtering from the status filtering
187
+ # statuses are OR'd, all of them are ANDd with frozen
188
+ # and lets us build meaningful header lines
189
+ filter_frozen = statuses.include? "frozen"
190
+ if filter_frozen
191
+ statuses.delete("frozen")
192
+ status_display = " frozen #{statuses.join(" or ")}"
193
+ else
194
+ status_display = " #{statuses.join(" or ")}"
195
+ end
196
+
197
+
198
+ lines = []
199
+ # sort by basename
200
+ rcpt_data.keys.sort.each do |basename|
201
+ rcpt = rcpt_data[basename]
202
+ # skip unwanted stati
203
+ unless statuses.empty?
204
+ next unless statuses.include? rcpt[:status]
205
+ end
206
+ # skip thawed if needed
207
+ if filter_frozen
208
+ next unless rcpt[:frozen]
209
+ end
210
+ rcpt_line = []
211
+ rcpt_line << "#{basename}-#{rcpt[:version]}-#{rcpt[:revision]}"
212
+ rcpt_line << rcpt[:status]
213
+ rcpt_line << computer.last_recon.strftime("%Y-%m-%d")
214
+ if ea_name
215
+ rcpt_line << (rcpt[:frozen] ? "frozen" : "-")
216
+ rcpt_line << Time.parse(rcpt[:installed_at]).strftime("%Y-%m-%d")
217
+ rcpt_line << rcpt[:admin]
218
+ end # rcpt[:installed_at]
219
+ lines << rcpt_line
220
+ end # rcpt_data.keys.sort do |basename|
221
+
222
+ if lines.empty?
223
+ statuses<<("frozen") if filter_frozen
224
+ stati = statuses.empty? ? '' : " #{ statuses.join(' or ')}"
225
+ puts "# No#{stati} receipts on '#{computer_name}'"
226
+ else
227
+ D3.less_text D3.generate_report lines, header_row: header, title: title
228
+ end
229
+ end # report_single_computer_receipts
230
+
231
+ ### Report a basename in all computers' puppy queues
232
+ ###
233
+ ### @param basename[String]
234
+ ###
235
+ ### @param statuses[Array<String,Symbol>]
236
+ ###
237
+ ### @return [void]
238
+ ###
239
+ def report_puppy_queues (basename, statuses)
240
+
241
+ report_data = Report.computer_puppyq_data
242
+ unless report_data
243
+ puts "Reports of pending puppies require a special Extension Attribute. Please see the d3 documentation"
244
+ return false
245
+ end
246
+
247
+ # json loads symbols as strings
248
+ statuses = statuses.map{|s| s.to_s}
249
+ status_display = " #{statuses.join(", ")}"
250
+
251
+ title = "All computers with '#{basename}' in the puppy queue"
252
+ header = %w{Computer User Edition Status Queued By As-of}
253
+ lines = []
254
+
255
+ report_data.each do |computer_to_report|
256
+ this_pup = computer_to_report[:pups][basename]
257
+ # skip if we don't have this basename
258
+ next unless this_pup
259
+ # skip unwanted statuses
260
+ unless statuses.empty?
261
+ next unless statuses.include? this_pup[:status]
262
+ end
263
+ edition = "#{basename}-#{this_pup[:version]}-#{this_pup[:revision]}"
264
+ qd_at = Time.parse(this_pup[:queued_at]).strftime "%Y-%m-%d"
265
+ as_of = Time.parse(computer_to_report[:as_of]).strftime "%Y-%m-%d"
266
+ lines << [computer_to_report[:computer], computer_to_report[:user], edition, this_pup[:status], qd_at, this_pup[:admin], as_of]
267
+ end # report_data.each do |computer_to_report|
268
+ if lines.empty?
269
+ puts "# No computers with '#{basename}' queued."
270
+ else
271
+ D3.less_text D3.generate_report lines, header_row: header, title: title
272
+ end # if lines emtpy?
273
+ end # ef puppy_installs (basenames)
274
+
275
+ ### Report a single computer's puppy queue
276
+ ###
277
+ ### @param computer_name[String]
278
+ ###
279
+ ### @param statuses[Array<String,Symbol>]
280
+ ###
281
+ ### @return [void]
282
+ ###
283
+ def report_single_puppy_queue (computer_name, statuses)
284
+ ea_name = D3::CONFIG.report_puppyq_ext_attr_name
285
+
286
+ unless ea_name
287
+ puts "Reports of pending puppies require a special Extension Attribute. Please see the d3 documentation"
288
+ return false
289
+ end
290
+
291
+ unless JSS::Computer.all_names.include? computer_name
292
+ puts "No computer named '#{computer_name}' in Casper"
293
+ return false
294
+ end
295
+
296
+ computer = JSS::Computer.new name: computer_name
297
+ ea_data = computer.extension_attributes.select{|ea| ea[:name] == ea_name}.first[:value]
298
+ if ea_data.empty?
299
+ puts "No puppies in the queue on computer '#{computer_name}'"
300
+ return false
301
+ elsif not ea_data.start_with?('{')
302
+ puts "The '#{ea_name}' extention attribute data for computer '#{computer_name}' is bad"
303
+ return false
304
+ end
305
+
306
+ title = "All items in the puppy queue on '#{computer_name}' (user: #{computer.username})"
307
+ header = %w{Edition Status Queued By As-of}
308
+ lines = []
309
+ ea_data = JSON.parse ea_data, :symbolize_names => true
310
+
311
+ # in json data, symbols became strings
312
+ statuses = statues.map{|s| s.to_s}
313
+ status_display = " #{statuses.join(", ")}"
314
+
315
+ ea_data.each do |basename,pup|
316
+ next unless statuses.include? pup[:status]
317
+ edition = "#{basename}-#{pup[:version]}-#{pup[:revision]}"
318
+ qa = Time.parse(pup[:queued_at]).strftime "%Y-%m-%d"
319
+ as_of = computer.last_recon.strftime s"%Y-%m-%d"
320
+ lines << [edition, pup[:status], qa, pup[:admin], as_of]
321
+ end
322
+ D3.less_text D3.generate_report lines, header_row: header, title: title
323
+
324
+ end #report_single_puppy_queue (computer_name, statuses)
325
+
326
+
327
+ ###### Lists for d3admin walkthru
328
+
329
+ ### Show a list of all package editions, pkg names and filenames
330
+ ### known to d3 along with their status
331
+ ###
332
+ ### @return [void]
333
+ ###
334
+ def show_existing_package_ids
335
+ # get them in alphabetical order
336
+ #sorted_pkgs = D3::Package.package_data.values.sort{|a,b| a[:name] <=> b[:name]}
337
+ sorted_pkgs = D3::Package.package_data.values.sort_by{|p| p[:name].downcase}
338
+
339
+ # here's the columns we care about
340
+ header = %w{ edition pkg_name filename JSS_id status}
341
+
342
+ # map each one to an array of desired data
343
+ lines = sorted_pkgs.map{|p|
344
+ [ p[:edition],
345
+ p[:name],
346
+ D3::Package.all_filenames[p[:id]],
347
+ p[:id],
348
+ p[:status]
349
+ ] }
350
+
351
+
352
+ D3.less_text D3.generate_report lines, header_row: header, title: "Packages in d3"
353
+ end
354
+
355
+ ### Show a list of all basenames known to d3
356
+ ### along with the status of the most recent package with that basename
357
+ ###
358
+ ### This is generally used with walkthrus.
359
+ ###
360
+ def show_all_basenames_and_editions
361
+ sorted_data = D3::Package.package_data.values.sort_by{|p| p[:edition] }
362
+
363
+ # here's the columns we care about
364
+ header = %w{basename edition status}
365
+
366
+ # map each one to an array of desired data
367
+ lines = sorted_data.map{ |p| [ p[:basename], p[:edition], p[:status]] }
368
+
369
+ D3.less_text D3.generate_report lines, header_row: header, title: "Basenames and Editions in d3."
370
+ end
371
+
372
+ ### Show a list of JSS package names that are NOT in d3.
373
+ ###
374
+ ### @return [void]
375
+ def show_pkgs_available_for_import
376
+ lines = (JSS::Package.all_names - D3::Package.all_names).sort.map{|p| [p]}
377
+ header = ['Package name']
378
+ D3.less_text D3.generate_report lines, header_row: header, title: "JSS Packages available for importing to d3"
379
+ end
380
+
381
+ ### Show a list of computers in the JSS, to select one for reporting
382
+ ###
383
+ ### @return [void]
384
+ ###
385
+ def show_available_computers_for_reports
386
+ lines = JSS::Computer.all_names.sort.map{|c| [c]}
387
+ header = ['Computer name']
388
+ D3.less_text D3.generate_report lines, header_row: header, title: "Computers in the JSS"
389
+ end
390
+
391
+ ###### Lists for 'd3admin search'
392
+
393
+ ### Display a list of pkgs on the server
394
+ ###
395
+ ### @param title[String] the title of the displayed list
396
+ ###
397
+ ### @param ids[Array] an array of pkgs id's about which to
398
+ ### display info.
399
+ ###
400
+ ### @param no_match_text[String] the text to display when there are no ids
401
+ ### @return [void]
402
+ ###
403
+ def display_package_list (title, ids, no_match_text = "No matchings packages", show_scope = false )
404
+ date_fmt = "%Y-%m-%d"
405
+ header = show_scope ? %w{ Edition Status Auto_Groups Excluded_Groups } : %w{ Edition Status Added By Released By }
406
+ lines = []
407
+ ids.each do |pkgid|
408
+ p = D3::Package.package_data[pkgid]
409
+ next unless p
410
+ if show_scope
411
+ auto_gs = p[:auto_groups].empty? ? "-none-" : p[:auto_groups].join(",")
412
+ excl_gs = p[:excluded_groups].empty? ? "-none-" : p[:excluded_groups].join(",")
413
+ lines << [p[:edition], p[:status], auto_gs, excl_gs]
414
+ else
415
+ date_added = p[:added_date] ? p[:added_date].strftime(date_fmt) : "-"
416
+ date_released = p[:release_date] ? p[:release_date].strftime(date_fmt) : "-"
417
+ rel_by = p[:released_by] ? p[:released_by] : "-"
418
+ lines << [p[:edition], p[:status], date_added, p[:added_by], date_released, rel_by]
419
+ end # if show_scope
420
+ end
421
+
422
+ if lines.empty?
423
+ puts no_match_text
424
+ puts # empty line between
425
+ return
426
+ end
427
+ lines.sort_by! {|l| l[0]}
428
+ D3.less_text D3.generate_report(lines, header_row: header, title: title)
429
+ puts # empty line between
430
+ end # display_package_list
431
+
432
+
433
+
434
+ ### Show a list of pkgs from the d3admin 'search' action
435
+ ###
436
+ ### @param basename[String] the basename of pkgs to show
437
+ ###
438
+ ### @param statuses[Array<String>] only show these statuses
439
+ ###
440
+ ### @return [void]
441
+ ###
442
+ def list_packages (basename = nil , statuses = [])
443
+ pkg_data = D3::Package.package_data
444
+
445
+ if basename
446
+ title = "All '#{basename}' packages in d3"
447
+ ids = pkg_data.values.select{|p| p[:basename] == basename }.map{|p| p[:id]}
448
+ else
449
+ title = "All packages in d3"
450
+ ids = pkg_data.keys
451
+ end # if basename
452
+
453
+ unless statuses.empty?
454
+ title += " with status #{statuses.join(' or ')}"
455
+ statuses = statuses.map{|s| s.to_sym}
456
+ status_display = " #{statuses.join(", ")}"
457
+ ids = ids.select{|pid| statuses.include? pkg_data[pid][:status] }
458
+ end # if what_to_show == :all
459
+
460
+ display_package_list title, ids, "No matching packages"
461
+
462
+ end # def list_packages
463
+
464
+
465
+ ### Show a list of all packages with their scoped groups
466
+ ###
467
+ ### @param statuses[Array<String>] only show these statuses
468
+ ###
469
+ ### @return [void]
470
+ ###
471
+ def list_all_pkgs_with_scope (statuses)
472
+ title = "Group Scoping for all packages"
473
+ title += " with status #{statuses.join(' or ')}" unless statuses.empty?
474
+
475
+ if statuses.empty?
476
+ ids = D3::Package.all_ids
477
+ else
478
+ ids = D3::Package.package_data.values.select{|pd| statuses.include? pd[:status].to_s }.map{|pd| pd[:id]}
479
+ end
480
+ D3::Admin::Report.display_package_list title, ids, 'No Matching Groups', :show_scope
481
+
482
+ end
483
+
484
+ ### list packages that auto-install onto machines
485
+ ### in one or more given groups
486
+ ###
487
+ ### @param groups[String,Array<String>] the group or groups to show.
488
+ ###
489
+ ### @return [void]
490
+ ###
491
+ def list_scoped_installs(group, statuses, scope = :auto)
492
+ scope_text = scope == :auto ? "auto-install" : "are excluded for"
493
+ title = "Packages that #{scope_text} for group '#{group}'"
494
+
495
+ if JSS::ComputerGroup.all_names.include? group or D3::STANDARD_AUTO_GROUP == group
496
+ ids = scope == :auto ? D3::Package.auto_install_ids_for_group(group) : D3::Package.exclude_ids_for_group(group)
497
+
498
+ unless statuses.empty?
499
+ title += " with status #{statuses.join(' or ')}"
500
+ statuses = statuses.map{|s| s.to_sym}
501
+ status_display = " #{statuses.join(", ")}"
502
+ ids = ids.select{|pid| statuses.include? D3::Package.package_data[pid][:status] }
503
+ end # if what_to_show == :all
504
+
505
+ no_match_text = "No packages #{scope_text} for group '#{group}'"
506
+ # no such group
507
+ else
508
+
509
+ ids = []
510
+ no_match_text = "No computer group named '#{group}'"
511
+ end # if JSS::ComputerGroup.all_names.include? group
512
+ display_package_list title, ids, no_match_text
513
+
514
+ end # list_scoped_installs
515
+
516
+
517
+
518
+
519
+ ###### Data gathering
520
+
521
+ ### Reconnect to both the API and DB with a much larger timeout, and
522
+ ### using an alternate DB server if one is defined.
523
+ ###
524
+ ### @return [Hash<String>] the hostnames of the connected JSS & MySQL servers
525
+ ###
526
+ def connect_for_reports
527
+ api = D3::Admin::Auth.rw_credentials :jss
528
+ db = D3::Admin::Auth.rw_credentials :db
529
+ D3.connect_for_reports api[:user], api[:password], db[:user], db[:password]
530
+ end # connect for report
531
+
532
+ ### Get the raw data for a client-install report, from the EA if available
533
+ ### or from the JAMF receipts if not.
534
+ ###
535
+ ### Returns an array of the report data (see
536
+ ### client_install_ea_report_data and client_install_jamf_rcpt_report_data)
537
+ ###
538
+ ### @return [Array<Hash>] The data for doing client install reports
539
+ ###
540
+ def computer_receipts_data
541
+ puts "Querying Casper for receipt data..."
542
+
543
+ the_data = computer_receipts_ea_data
544
+ return the_data if the_data
545
+ return computer_receipts_jamf_data
546
+ end
547
+
548
+ ### Get the latest data from the D3::CONFIG.report_receipts_ext_attr_name
549
+ ### if that EA exists, nil otherwise
550
+ ###
551
+ ### The result is an Array of Hashes, one for each computer in Casper.
552
+ ### Each hash contains these keys:
553
+ ### :computer - the name of the computer
554
+ ### :user - the name of the comptuer's user
555
+ ### :as_of - the Time when the data was gathered
556
+ ### :rcpts - a Hash of receipt data for the computer, keyed by basename.
557
+ ###
558
+ ### Each receipt in the :rcpts hash contains these keys
559
+ ### :version
560
+ ### :revision
561
+ ### :status
562
+ ### :installed_at
563
+ ### :admin
564
+ ### :frozen
565
+ ### :manual
566
+ ### :custom_expiration
567
+ ### :last_usage
568
+ ###
569
+ ### @return [Array<Hash>, nil] The data from the extension attribute, nil
570
+ ### if we aren't configured for the EA.
571
+ ###
572
+ def computer_receipts_ea_data
573
+ return nil unless D3::CONFIG.report_receipts_ext_attr_name
574
+ connect_for_reports
575
+
576
+ ea = JSS::ComputerExtensionAttribute.new :name => D3::CONFIG.report_receipts_ext_attr_name
577
+
578
+ # while we could get the data via the API by calling: result = ea.latest_values
579
+ # but thats very slow, because it creates a temporary AdvancedSearch,
580
+ # and retrieves it's results, and API access is always pretty slow
581
+ # Going directly to SQL is WAY faster and since this is D3, we can.
582
+
583
+ q = <<-ENDQ
584
+ SELECT c.computer_id, c.computer_name, c.username, c.last_report_date_epoch AS as_of, eav.value_on_client AS value
585
+ FROM computers_denormalized c
586
+ JOIN extension_attribute_values eav
587
+ ON c.last_report_id = eav.report_id
588
+ WHERE eav.extension_attribute_id = #{ea.id}
589
+ ENDQ
590
+
591
+ result = JSS::DB_CNX.db.query q
592
+
593
+ report_data = []
594
+ result.each_hash do |computer_ea_result|
595
+ computer_data = {}
596
+ computer_data[:computer] = computer_ea_result["computer_name"].to_s
597
+ computer_data[:user] = computer_ea_result["username"]
598
+ computer_data[:as_of] = (JSS.epoch_to_time computer_ea_result["as_of"])
599
+ rcpts ={}
600
+ if computer_ea_result['value'].start_with? '{'
601
+ rcpt_data = JSON.parse computer_ea_result['value'], :symbolize_names => true
602
+ rcpt_data.each do |basename, raw|
603
+ this_r = {}
604
+ this_r[:version] = raw[:version]
605
+ this_r[:revision] = raw[:revision].to_i
606
+ this_r[:status] = raw[:status]
607
+ this_r[:installed_at] = raw[:installed_at] ? Time.parse(raw[:installed_at]) : nil
608
+ this_r[:admin] = raw[:admin]
609
+ this_r[:frozen] = raw[:frozen]
610
+ this_r[:manual] = raw[:manual]
611
+ this_r[:custom_expiration] = raw[:custom_expiration]
612
+ this_r[:last_usage] = raw[:last_usage] ? Time.parse(raw[:last_usage]) : nil
613
+ # the basename got symbolized, so re-string it
614
+ rcpts[basename.to_s] = this_r
615
+ end # rcpt_data.each do |basename, raw|
616
+ end # if ea_result['value'].start_with? '{'
617
+ computer_data[:rcpts] = rcpts
618
+ report_data << computer_data
619
+
620
+ end # result.each_hash do |ea_result|
621
+
622
+ return report_data
623
+ end # def ea_report_data
624
+
625
+ ### get the latest receipt data from Caspers receipts table
626
+ ### This is used if the D3::CONFIG.report_receipts_ext_attr_name is not set
627
+ ### and the data it returns is less useful.
628
+ ###
629
+ ### The result is and Array of Hashes, one for each computer in Casper.
630
+ ### Each hash contains these keys:
631
+ ### :computer - the name of the computer
632
+ ### :user - the name of the comptuer's user
633
+ ### :as_of - the Time when the data was gathered
634
+ ### :rcpts - a Hash of receipt data for the computer, keyed by basename.
635
+ ###
636
+ ### Each receipt in the :rcpts hash contains these keys
637
+ ### :version
638
+ ### :revision
639
+ ### :status
640
+ ###
641
+ ### @return [Array<Hash>] The data from the jamf receipts
642
+ ###
643
+ def computer_receipts_jamf_data
644
+ q = <<-ENDQ
645
+ SELECT c.computer_name, c.username, GROUP_CONCAT(r.package_name) AS jamf_receipts, c.last_report_date_epoch AS as_of
646
+ FROM computers_denormalized c JOIN package_receipts r ON c.computer_id = r.computer_id
647
+ WHERE r.type = 'jamf'
648
+ GROUP BY c.computer_id
649
+ ENDQ
650
+
651
+ res = JSS::DB_CNX.db.query q
652
+
653
+ report_data = []
654
+
655
+ pkg_filenames_to_ids = D3::Package.all_filenames.invert
656
+
657
+ res.each_hash do |record|
658
+ computer_data = {:computer => record['computer_name']}
659
+ computer_data[:as_of] = JSS.epoch_to_time record['as_of']
660
+ computer_data[:user] = record['username']
661
+ computer_data[:rcpts] = {}
662
+ record['jamf_receipts'].split(',').each do |jrcpt|
663
+
664
+ rcpt_id = pkg_filenames_to_ids[jrcpt]
665
+ # some might be zipped
666
+ rcpt_id ||= pkg_filenames_to_ids[jrcpt + ".zip"]
667
+
668
+ next unless rcpt_id
669
+ pkg_data = D3::Package.package_data[rcpt_id]
670
+ next unless pkg_data
671
+ computer_data[:rcpts][pkg_data[:basename]] = {:version =>pkg_data[:version], :revision => pkg_data[:revision], :status => pkg_data[:status]}
672
+ end #record['jamf_receipts'].split(',').each do
673
+ report_data << computer_data
674
+ end # res.each_hash do |record|
675
+ res.free
676
+ return report_data
677
+ end # def jamf_rcpt_report_data
678
+
679
+ ### get the latest puppy queue data from the puppy q EA, if available.
680
+ def computer_puppyq_data
681
+ return nil unless D3::CONFIG.report_puppyq_ext_attr_name
682
+ ea = JSS::ComputerExtensionAttribute.new :name => D3::CONFIG.report_puppyq_ext_attr_name
683
+ q = <<-ENDQ
684
+ SELECT c.computer_id, c.computer_name, c.username, c.last_report_date_epoch AS as_of, eav.value_on_client AS value
685
+ FROM computers_denormalized c
686
+ JOIN extension_attribute_values eav
687
+ ON c.last_report_id = eav.report_id
688
+ WHERE eav.extension_attribute_id = #{ea.id}
689
+ ENDQ
690
+
691
+ result = JSS::DB_CNX.db.query q
692
+
693
+ report_data = []
694
+ result.each_hash do |ea_result|
695
+ pups ={}
696
+ if ea_result['value'].start_with? '{"'
697
+ # the ea contains the full receipt YAML.
698
+ # for now we only need this subset of it.
699
+ pup_data = JSON.parse ea_result['value'], :symbolize_names => true
700
+ pup_data.each do |basename, raw|
701
+ this_p = {}
702
+ this_p[:version] = raw[:version]
703
+ this_p[:revision] = raw[:revision].to_i
704
+ this_p[:status] = raw[:status].to_sym
705
+ this_p[:queued_at] = raw[:queued_at] ? Time.parse(raw[:queued_at]) : nil
706
+ this_p[:admin] = raw[:admin]
707
+ this_p[:custom_expiration] = raw[:custom_expiration]
708
+ pups[basename] = this_p
709
+ end # each do r
710
+
711
+ end # if ea_result['value'].start_with? '{"'
712
+ report_data << {
713
+ :computer => ea_result["computer_name"],
714
+ :user => ea_result["username"],
715
+ :pups => pups,
716
+ :as_of => (JSS.epoch_to_time ea_result["as_of"])
717
+ }
718
+
719
+ end # result.each_hash do |ea_result|
720
+
721
+ return report_data
722
+ end # def client_puppyq_report_data
723
+
724
+ end # module Report
725
+ end # module Admin
726
+ end # module D3
727
+