5 =head1 NAME $RCSfile: viewager.cgi,v $
15 Andrew DeFaria <Andrew@ClearSCM.com>
23 Mon Oct 25 11:10:47 PDT 2008
27 $Date: 2011/01/14 16:50:54 $
33 This script serves 4 distinct functions. One function is to find
34 old views and report them to their owners via email so that view cleanup can be
35 done. Another function just does a quick report stdout. Yet another function is
36 to present the list of views in a web page. Finally there is a function
37 (generate) which generates a cache file containing information about views. This
38 function is designed to be run by a scheduler such as cron. Note that the web
39 page function relies on and uses this cache file too.
43 Most Clearcase administrators wrestle with trying to keep the number of views
44 under control. Users often create views but seldom think to remove them. Views
45 grow old and forgotten.
47 Many approaches have been taken, usally emailing the users telling them to clean
48 up their views. This script, viewager.cgi, attempts to encapsulate the task of
49 gathering information about old views, informing users of which of their views
50 are old and presenting reports in the form of a web page showing all views
53 =head1 USAGE Email, Report and Generate modes
55 Usage viewager.cgi: [-u|sage] [-region <region>] [-e|mail]
56 [-a|gethreshold <n>] [-n|brThreshold <n>]
57 [-ac|tion <act>] [-s|ort <field>]
61 -u|sage: Displays usage
62 -region <region>: Region to use when looking for the view
63 -e|mail: Send email to owners of old views
64 -ag|eThreshold: Number of days before a view is considered old
66 -n|brThreshold <n>: Number of views to report. Can be used for say a
67 "top 10" old views. Useful with -action report
68 (Default: Report all views)
69 -ac|tion <act> Valid actions include 'generate' or 'report'.
70 Generate mode merely regenerates the cache file.
71 Report produces a quick report to stdout.
72 -s|ort <field>: Where <field> is one of <tag|ownerName|type|age>
75 -d|ebug: Output debug messages
77 =head1 USAGE Web Page mode
79 Parameters for the web page mode are provided by the CPAN module CGI and are
80 normally passed in as part of the URL. These parameters are specified as
83 sortby=<tag|ownerName|type|age>
84 Note: age will sort in a reverse numerical fashion
87 <username> can be a partial name (e.g. 'defaria')
91 This script seek to handle the general issue of handling old views. In generate
92 mode this script goes through all views collecting data about all of the views
93 and creates a cache file. The reason for this is that this process is length
94 (At one client's site with ~2500 views takes about 1 hour). As such you'd
95 probably want to schedule the running of this for once a day.
97 Once the cache file is created other modes will read that file and report on it.
98 In report mode you can report to stdout. For example, the following will give
99 you a quick "top 10" oldest views:
101 $ viewager.cgi -action report -n 10
103 You may wish to add the following to your conrtabe to generated the cachefile
106 0 0 * * * cd /<DocumentRoot>/viewager && /<path>/viewager.cgi -action=generate
110 Since the method for translating a user's userid into other attributes like
111 the users fullname and email, we rely on a User.pm module to implement a User
112 object that takes a string identifying the user and return useful informaiton
113 about the user, specifically the fullname and email address.
122 use CGI qw (:standard :cgi-lib *table start_Tr end_Tr);
123 use CGI::Carp 'fatalsToBrowser';
127 use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib";
133 use Clearcase::Views;
140 my $VERSION = '$Revision: 1.11 $';
141 ($VERSION) = ($VERSION =~ /\$Revision: (.*) /);
146 $opts{sortby} ||= 'age';
147 $opts{region} ||= $Clearcase::CC->region;
149 my $subtitle = 'View Aging Report';
152 my $port = CGI::server_port;
153 $port = ($port == 80) ? '' : ":$port";
154 my $scriptName = CGI::script_name;
155 $scriptName =~ s/index.cgi//;
156 my $script = 'http://'
157 . $Clearadm::CLEAROPTS{CLEARADM_SERVER}
161 my (%total, $action);
162 my $ageThreshold = 180; # Default number of days a view must be older than
163 my $nbrThreshold; # Number of views threshold - think top 10
165 sub GenerateRegion ($) {
168 verbose "Processing $region";
171 my $views = Clearcase::Views->new ($region);
172 my @Views = $views->views;
175 verbose scalar @Views . " views to process";
179 foreach my $name (@Views) {
182 if (++$i % 100 == 0) {
184 } elsif ($i % 25 == 0) {
188 my $view = Clearcase::View->new ($name, $region);
192 if ($view->webview) {
193 # TODO: There doesn't appear to be a good way to get the gpath for a
194 # webview since it's set to <nogpath>! Here we try to compose one using
195 # $view->host and $view->access_path but this is decidedly Windows centric
196 # and thus not portable. This needs to be fixed!
197 $gpath = '\\\\' . $view->host . '\\' . $view->access_path;
199 # Change any ":" to "$". This is to change things like D:\path -> D$\path.
200 # This assumes we have permissions to access through the administrative
204 $gpath = $view->gpath;
207 # Note if the view server is unreachable (e.g. user puts view on laptop and
208 # the laptop is powered off), then these fields will be undef. Change them
209 # to Unknown. (Should Clearcase::View.pm do this instead?).
210 my $type = $view->type;
215 my $ownerid = $view->owner;
218 $user = User->new ($ownerid);
220 $user->{name} ||= 'Unknown';
222 $ownerid = 'Unknown';
223 $user->{name} = 'Unknown';
229 my $modified_date = $view->modified_date;
231 if ($modified_date) {
232 $modified_date = substr $modified_date, 0, 16;
233 $modified_date =~ s/T/\@/;
236 $age = Age ($modified_date);
237 $ageSuffix = $age != 1 ? 'days' : 'day';
239 $modified_date = 'Unknown';
242 my ($err, $msg) = $clearadm->AddView (
243 system => $view->shost,
244 region => $view->region,
247 ownerName => $user->{name},
248 email => $user->{email},
251 modified_date => $modified_date,
253 ageSuffix => $ageSuffix,
256 error "Unable to add view $name to Clearadm\n$msg", $err
260 verbose "\nProcessed $region";
268 if ($region =~ /all/i) {
269 foreach ($Clearcase::CC->regions) {
273 GenerateRegion $region;
282 $total{'Views processed'} = @views;
286 if ($opts{sort} eq 'age') {
287 # Sort by age numerically decending
288 @sortedViews = sort { $$b{$opts{sortby}} <=> $$a{$opts{sortby}} } @views;
290 @sortedViews = sort { $$a{$opts{sort}} cmp $$b{$opts{sort}} } @views;
293 $total{Reported} = 0;
295 foreach (@sortedViews) {
299 if ($nbrThreshold and $total{Reported} + 1 > $nbrThreshold) or
300 ($view{age} < $ageThreshold);
305 if ($view{type} eq 'dynamic') {
307 } elsif ($view{type} eq 'snapshot') {
309 } elsif ($view{type} eq 'webview') {
312 $total{$view{type}}++;
319 View Name Owner View Type Last Modified Age
320 ------------------------------------- ---------------------- ----------- ---------------- -----------
323 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<< @<<<<<<<<<<<<<<< @>>>> @<<<<
324 $view{tag},$view{owner},$view{type},$view{modified_date},$view{age},$view{ageSuffix}
333 sub FormatTable ($@) {
334 my ($style, @views) = @_;
338 my $nbrViews = @views;
341 font ({-class => 'label'}, 'View type: ') .
342 font ({-class => 'dynamic'}, 'Dyanmic') .
344 font ({-class => 'snapshot'}, 'Snapshot') .
346 font ({-class => 'web'}, 'Web') .
348 font ({-class => 'unknown'}, 'Unknown');
352 my $regionDropdown = start_form (
356 $regionDropdown .= font {-class => 'captionLabel'}, 'Region: ';
357 $regionDropdown .= popup_menu (
359 -values => [$Clearcase::CC->regions],
360 -default => $Clearcase::CC->region,
361 -onchange => 'submit();',
364 $regionDropdown .= end_form;
366 $caption .= start_table {
372 my $registryHost = $Clearcase::CC->registry_host;
374 $registryHost = font {class => 'unknown'}, 'Unknown'
375 unless $registryHost;
377 $caption .= start_Tr;
381 }, font ({-class => 'label'}, 'Registry: '),
382 $registryHost, '<br>',
383 font ({-class => 'label'}, 'Views: '),
395 $caption .= end_table;
397 $table .= start_table {
402 $table .= caption $caption;
403 $table .= start_Tr {-class => 'heading'};
406 # Set defaults if not set already
407 $opts{sortby} ||= 'age';
408 $opts{reverse} ||= 0;
410 my $parms = $opts{user} ? "&user=$opts{user}" : '';
411 $parms .= $opts{reverse} == 1 ? '&reverse=0' : '&reverse=1';
413 if ($style eq 'full') {
414 my $tagLabel = 'Tag ';
415 my $ownerLabel = 'Owner ';
416 my $typeLabel = 'Type ';
417 my $ageLabel = 'Age ';
419 if ($opts{sortby} eq 'tag') {
420 $tagLabel .= $opts{reverse} == 1
421 ? img {src => 'up.png', border => 0}
422 : img {src => 'down.png', border => 0};
423 } elsif ($opts{sortby} eq 'ownerName') {
424 $ownerLabel .= $opts{reverse} == 1
425 ? img {src => 'up.png', border => 0}
426 : img {src => 'down.png', border => 0};
427 } elsif ($opts{sortby} eq 'type') {
428 $typeLabel .= $opts{reverse} == 1
429 ? img {src => 'up.png', border => 0}
430 : img {src => 'down.png', border => 0};
431 } elsif ($opts{sortby} eq 'age') {
432 $ageLabel .= $opts{reverse} == 1
433 ? img {src => 'down.png', border => 0}
434 : img {src => 'up.png', border => 0};
437 $table .= th a {href => "$script?region=$opts{region}&sortby=tag$parms"},
439 $table .= th a {href => "$script?region=$opts{region}&sortby=ownerName$parms"},
441 $table .= th a {href => "$script?region=$opts{region}&sortby=type$parms"},
443 $table .= th a {href => "$script?region=$opts{region}&sortby=age$parms"},
447 $table .= th 'Owner';
453 if ($opts{sortby} eq 'age') {
454 # Sort by age numerically decending
455 @views = $opts{reverse} == 1
456 ? sort { $$a{$opts{sortby}} <=> $$b{$opts{sortby}} } @views
457 : sort { $$b{$opts{sortby}} <=> $$a{$opts{sortby}} } @views
459 @views = $opts{reverse} == 1
460 ? sort { $$b{$opts{sortby}} cmp $$a{$opts{sortby}} } @views
461 : sort { $$a{$opts{sortby}} cmp $$b{$opts{sortby}} } @views
469 my $owner = $view{owner};
471 if ($view{owner} =~ /\S+(\\|\/)(\S+)/) {
475 $owner = $view{ownerName} ? $view{ownerName} : 'Unknown';
477 my $rowClass= $view{age} > $ageThreshold ? 'oldview' : 'view';
488 href => "viewdetails.cgi?tag=$view{tag}®ion=$opts{region}"
493 href => "$script?region=$opts{region}&user=$owner"
504 }, $view{age}, ' ', $view{ageSuffix});
513 # TODO: Add an option to remove views older than a certain date
516 my ($emailTo, @oldViews) = @_;
518 @oldViews = sort { $$b{age} <=> $$a{age} } @oldViews;
520 my $msg = '<style>' . join ("\n", ReadFile 'viewager.css') . '</style>';
522 <h1 align="center">You have old Clearcase Views</h1>
524 <p>Won't you take a moment to review this message and clean up any views you no
527 <p>The following views are owned by you and have not been modified in $ageThreshold
531 $msg .= FormatTable 'partial', @oldViews;
534 <h3>How to remove views you no longer need</h3>
536 <p>There are several ways to remove Clearcase views, depending on the view
537 type and the tools you are using.</p>
540 <p><b>Dynamic Views</b>: If the view is a dynamic view you can use Clearcase
541 Explorer to remove the view. Find the view in your Clearcase Explorer. If
542 it's not there then add it as a standard view shortcut. Then right click on
543 the view shortcut and select <b>Remove View</b> (not <b>Remove View
546 <p><b>Snapshot Views</b>: A snapshot view is a view who's source storage can
547 be located locally. You can remove a snapshot view in a similar manner as a
548 dynamic view, by adding it to Clearcase Explorer if not already present. By
549 doing so you need to tell Clearcase Explorer where the snapshot view storage
552 <p><b>Webviews</b>: Webviews are like snapshot views but stored on the web
553 server. If you are using CCRC or the CCRC plugin to Eclipse you would select
554 the view and then do <b>Environment: Remove Clearcase View</b>.</p>
557 <p>If you have any troubles removing your old views then submit a case and we
558 will be happy to assist you.</p>
560 <h3>But I need for my view to stay around even if it hasn't been modified</h3>
562 <p>If you have a long lasting view who does not get modified but needs to
563 remain, contact us and we can arrange for it to be removed from consideration
564 which will stop it from being reported as old.</p>
568 Your friendly Clearcase Administrator
573 # to => 'Andrew@DeFaria.com',
575 subject => 'Old views',
585 @views = sort { $$a{ownerName} cmp $$b{ownerName} } @views;
588 my $currUser = $views [0]->{ownerName};
596 if ($currUser ne $view{ownerName}) {
597 EmailUser $view{email}, @userViews
600 $currUser = $view{ownerName};
604 if ($view{age} > $ageThreshold) {
605 push @userViews, \%view
606 if !-f "$view{gpath}/ageless";
619 'usage' => sub { Usage },
620 'verbose' => sub { set_verbose },
621 'debug' => sub { set_debug },
628 ) or Usage "Invalid parameter";
632 $opts{region} ||= '';
635 verbose "$FindBin::Script v$VERSION";
637 $clearadm = Clearadm->new;
639 if ($action and $action eq 'generate') {
640 Generate $opts{region};
643 if ($opts{region} and ($opts{region} eq 'Clearcase not installed')) {
645 displayError $opts{region};
650 my @views = $clearadm->FindView (
657 if ($action and $action eq 'report') {
669 display FormatTable 'full', @views;
677 =head1 CONFIGURATION AND ENVIRONMENT
679 DEBUG: If set then $debug is set to this level.
681 VERBOSE: If set then $verbose is set to this level.
683 TRACE: If set then $trace is set to this level.
691 L<CGI::Carp|CGI::Carp>
693 L<Data::Dumper|Data::Dumper>
695 L<File::stat|File::stat>
699 L<Getopt::Long|Getopt::Long>
701 L<Time::localtime|Time::localtime>
703 =head2 ClearSCM Perl Modules
722 <a href="http://clearscm.com/php/scm_man.php?file=clearadm/lib/Clearadm.pm">Clearadm</a><br>
723 <a href="http://clearscm.com/php/scm_man.php?file=clearadm/lib/ClearadmWeb.pm">ClearadmWeb</a><br>
724 <a href="http://clearscm.com/php/scm_man.php?file=lib/Clearcase.pm">Clearcase</a><br>
725 <a href="http://clearscm.com/php/scm_man.php?file=lib/Clearcase/View.pm">Clearcase::View</a><br>
726 <a href="http://clearscm.com/php/scm_man.php?file=lib/Clearcase/Views.pm">Clearcase::Views</a><br>
727 <a href="http://clearscm.com/php/scm_man.php?file=lib/DateUtils.pm">DateUtils</a><br>
728 <a href="http://clearscm.com/php/scm_man.php?file=lib/Display.pm">Display</a><br>
729 <a href="http://clearscm.com/php/scm_man.php?file=lib/Mail.pm">Mail</a><br>
730 <a href="http://clearscm.com/php/scm_man.php?file=lib/Utils.pm">Utils</a><br>
739 =head1 BUGS AND LIMITATIONS
741 There are no known bugs in this script
743 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
745 =head1 LICENSE AND COPYRIGHT
747 Copyright (c) 2010, ClearSCM, Inc. All rights reserved.