depot3 0.0.0a1 → 3.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +55 -1
- data/bin/d3 +323 -0
- data/bin/d3admin +1011 -0
- data/bin/d3helper +354 -0
- data/bin/puppytime +334 -0
- data/data/d3/com.pixar.d3.RepoMan.plist +23 -0
- data/data/d3/d3.conf.example +507 -0
- data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftAppKit.dylib +0 -0
- data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftCore.dylib +0 -0
- data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftCoreData.dylib +0 -0
- data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftCoreGraphics.dylib +0 -0
- data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftCoreImage.dylib +0 -0
- data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftDarwin.dylib +0 -0
- data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftDispatch.dylib +0 -0
- data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftFoundation.dylib +0 -0
- data/data/d3/d3RepoMan.app/Contents/Frameworks/libswiftObjectiveC.dylib +0 -0
- data/data/d3/d3RepoMan.app/Contents/Info.plist +56 -0
- data/data/d3/d3RepoMan.app/Contents/MacOS/d3RepoMan +0 -0
- data/data/d3/d3RepoMan.app/Contents/PkgInfo +1 -0
- data/data/d3/d3RepoMan.app/Contents/Resources/Base.lproj/MainMenu.nib +0 -0
- data/data/d3/d3RepoMan.app/Contents/Resources/last-foreground-times-template.plist +5 -0
- data/data/d3/d3RepoMan.app/Contents/_CodeSignature/CodeResources +214 -0
- data/data/d3/puppytime/ImageLicenses.txt +165 -0
- data/data/d3/puppytime/notification_image +1 -0
- data/data/d3/puppytime/opt_out_image +1 -0
- data/data/d3/puppytime/slideshow/2008-07-11_White_German_Shepherd_pup_chilling_at_the_Coker_Arboretum.jpg +0 -0
- data/data/d3/puppytime/slideshow/2009-04-21_APBT_pup_on_deck.jpg +0 -0
- data/data/d3/puppytime/slideshow/A_puppy_Yorkie.jpg +0 -0
- data/data/d3/puppytime/slideshow/Alert_Pug_Puppy.jpg +0 -0
- data/data/d3/puppytime/slideshow/Australian_Cattle_Dog_puppies_04.JPG +0 -0
- data/data/d3/puppytime/slideshow/Beagle_puppy_Cadet.jpg +0 -0
- data/data/d3/puppytime/slideshow/Bernese_Mountain_Dog.jpg +0 -0
- data/data/d3/puppytime/slideshow/Bloodhound_Puppy.jpg +0 -0
- data/data/d3/puppytime/slideshow/Boston_terrier_with_toy.jpg +0 -0
- data/data/d3/puppytime/slideshow/Boxer_puppy_fawn_portrai.jpg +0 -0
- data/data/d3/puppytime/slideshow/Caracal_kitten.jpg +0 -0
- data/data/d3/puppytime/slideshow/Chihuahua_&_Doberman_Pup.jpg +0 -0
- data/data/d3/puppytime/slideshow/Cuccioli_di_Margot_a_35_gg_Basenjis.jpg +0 -0
- data/data/d3/puppytime/slideshow/Dalmatian_puppy_03.jpg +0 -0
- data/data/d3/puppytime/slideshow/GoldenRetrieverPuppyDaisyParker.JPG +0 -0
- data/data/d3/puppytime/slideshow/Green_eyed_beige_Chihuahua.jpg +0 -0
- data/data/d3/puppytime/slideshow/Let_Sleeping_Dogs_Lie.jpg +0 -0
- data/data/d3/puppytime/slideshow/Meatball_-_French_Bulldog_Puppy.jpg +0 -0
- data/data/d3/puppytime/slideshow/Oola_-_9_weeks.jpg +0 -0
- data/data/d3/puppytime/slideshow/Pancho0008.JPG +0 -0
- data/data/d3/puppytime/slideshow/Pomeranian_orange-sable_Coco.jpg +0 -0
- data/data/d3/puppytime/slideshow/Pug_puppy_001.jpg +0 -0
- data/data/d3/puppytime/slideshow/Puggle_puppy_6_weeks.JPG +0 -0
- data/data/d3/puppytime/slideshow/Puli_kan.jpg +0 -0
- data/data/d3/puppytime/slideshow/Puppy_French_Bulldog.jpg +0 -0
- data/data/d3/puppytime/slideshow/Rocco_the_Bulldog.jpg +0 -0
- data/data/d3/puppytime/slideshow/Rottweiler_Face.jpg +0 -0
- data/data/d3/puppytime/slideshow/Saint_Bernard_puppy.jpg +0 -0
- data/data/d3/puppytime/slideshow/Scottish_froment.jpg +0 -0
- data/data/d3/puppytime/slideshow/Shar_pei_puppy_(age_2_months).jpg +0 -0
- data/data/d3/puppytime/slideshow/Shiba-Inu_beim_Spielen_im_Schnee.JPG +0 -0
- data/data/d3/puppytime/slideshow/Smooth-coat_Border_Collie_puppy..jpg +0 -0
- data/data/d3/puppytime/slideshow/Smooth_Dachshund_puppies.jpg +0 -0
- data/data/d3/puppytime/slideshow/Snow_dog.jpg +0 -0
- data/data/d3/puppytime/slideshow/Taylor_the_Pembroke_Welsh_Corgi.png +0 -0
- data/data/d3/puppytime/slideshow/Weim_Pups_001.jpg +0 -0
- data/data/d3/puppytime/slideshow/Westie_pups.jpg +0 -0
- data/data/d3/puppytime/slideshow/Yellow_Labrador_puppies_(4165737325).jpg +0 -0
- data/lib/d3/admin/add.rb +451 -0
- data/lib/d3/admin/auth.rb +470 -0
- data/lib/d3/admin/edit.rb +297 -0
- data/lib/d3/admin/help.rb +396 -0
- data/lib/d3/admin/interactive.rb +972 -0
- data/lib/d3/admin/options.rb +454 -0
- data/lib/d3/admin/prefs.rb +204 -0
- data/lib/d3/admin/report.rb +727 -0
- data/lib/d3/admin/state.rb +42 -0
- data/lib/d3/admin/validate.rb +413 -0
- data/lib/d3/admin.rb +42 -0
- data/lib/d3/basename.rb +217 -0
- data/lib/d3/client/auth.rb +108 -0
- data/lib/d3/client/class_methods.rb +766 -0
- data/lib/d3/client/class_variables.rb +47 -0
- data/lib/d3/client/cli.rb +187 -0
- data/lib/d3/client/environment.rb +134 -0
- data/lib/d3/client/help.rb +110 -0
- data/lib/d3/client/lists.rb +314 -0
- data/lib/d3/client/receipt.rb +1173 -0
- data/lib/d3/client.rb +45 -0
- data/lib/d3/configuration.rb +319 -0
- data/lib/d3/constants.rb +60 -0
- data/lib/d3/database.rb +488 -0
- data/lib/d3/exceptions.rb +44 -0
- data/lib/d3/log.rb +271 -0
- data/lib/d3/package/aliases.rb +80 -0
- data/lib/d3/package/attributes.rb +97 -0
- data/lib/d3/package/class_methods.rb +817 -0
- data/lib/d3/package/class_variables.rb +46 -0
- data/lib/d3/package/client_actions.rb +293 -0
- data/lib/d3/package/constants.rb +58 -0
- data/lib/d3/package/constructor.rb +191 -0
- data/lib/d3/package/getters.rb +164 -0
- data/lib/d3/package/mixins.rb +39 -0
- data/lib/d3/package/private_methods.rb +227 -0
- data/lib/d3/package/questions.rb +95 -0
- data/lib/d3/package/server_actions.rb +683 -0
- data/lib/d3/package/setters.rb +326 -0
- data/lib/d3/package/validate.rb +448 -0
- data/lib/d3/package.rb +51 -0
- data/lib/d3/puppytime/pending_puppy.rb +108 -0
- data/lib/d3/puppytime/puppy_queue.rb +274 -0
- data/lib/d3/puppytime.rb +68 -0
- data/lib/d3/state.rb +105 -0
- data/lib/d3/utility.rb +325 -0
- data/lib/d3/version.rb +1 -1
- 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
|
+
|