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 views (Default
63 for generate action: all)
64 -e|mail: Send email to owners of old views
65 -ag|eThreshold: Number of days before a view is considered old
67 -n|brThreshold <n>: Number of views to report. Can be used for say a
68 "top 10" old views. Useful with -action report
69 (Default: Report all views)
70 -ac|tion <act> Valid actions include 'generate' or 'report'.
71 Generate mode merely regenerates the cache file.
72 Report produces a quick report to stdout.
73 -s|ort <field>: Where <field> is one of <tag|ownerName|type|age>
76 -d|ebug: Output debug messages
78 =head1 USAGE Web Page mode
80 Parameters for the web page mode are provided by the CPAN module CGI and are
81 normally passed in as part of the URL. These parameters are specified as
84 sortby=<tag|ownerName|type|age>
85 Note: age will sort in a reverse numerical fashion
88 <username> can be a partial name (e.g. 'defaria')
92 This script seek to handle the general issue of handling old views. In generate
93 mode this script goes through all views collecting data about all of the views
94 and creates a cache file. The reason for this is that this process is length
95 (At one client's site with ~2500 views takes about 1 hour). As such you'd
96 probably want to schedule the running of this for once a day.
98 Once the cache file is created other modes will read that file and report on it.
99 In report mode you can report to stdout. For example, the following will give
100 you a quick "top 10" oldest views:
102 $ viewager.cgi -action report -n 10
104 You may wish to add the following to your conrtabe to generated the cachefile
107 0 0 * * * cd /<DocumentRoot>/viewager && /<path>/viewager.cgi -action=generate
111 Since the method for translating a user's userid into other attributes like
112 the users fullname and email, we rely on a User.pm module to implement a User
113 object that takes a string identifying the user and return useful informaiton
114 about the user, specifically the fullname and email address.
123 use CGI qw (:standard :cgi-lib *table start_Tr end_Tr);
124 use CGI::Carp 'fatalsToBrowser';
128 use lib "$FindBin::Bin/lib", "$FindBin::Bin/../lib";
134 use Clearcase::Views;
141 my $VERSION = '$Revision: 1.11 $';
142 ($VERSION) = ($VERSION =~ /\$Revision: (.*) /);
147 $opts{sortby} ||= 'age';
148 $opts{ageThreshold} = 180; # Default number of days a view must be older than
150 my $subtitle = 'View Aging Report';
153 my $port = CGI::server_port;
154 $port = ($port == 80) ? '' : ":$port";
155 my $scriptName = CGI::script_name;
156 $scriptName =~ s/index.cgi//;
157 my $script = 'http://'
158 . $Clearadm::CLEAROPTS{CLEARADM_SERVER}
163 my $nbrThreshold; # Number of views threshold - think top 10
165 sub GenerateRegion ($) {
168 verbose "Processing region $region";
171 my $views = Clearcase::Views->new ($region);
172 my @Views = $views->views;
175 verbose scalar @Views . " views to process";
179 for 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;
217 if ($ownerid =~ /^\w+(\\|\/)(\w+)/) {
218 # TODO: Handle user identification better
219 #$user = User->new ($ownerid);
223 $user->{email} = "$2\@gddsi.com";
225 $ownerid = 'Unknown';
226 $user->{name} = 'Unknown';
227 $user->{email} = 'unknown@gddsi.com';
233 my $modified_date = $view->modified_date;
235 if ($modified_date) {
236 $modified_date = substr $modified_date, 0, 16;
237 $modified_date =~ s/T/\@/;
240 $age = Age ($modified_date);
241 $ageSuffix = $age != 1 ? 'days' : 'day';
243 # $modified_date = 'Unknown';
246 my %oldView = $clearadm->GetView($view->tag, $view->region);
251 system => $view->shost,
252 region => $view->region,
255 ownerName => $user->{name},
256 email => $user->{email},
260 ageSuffix => $ageSuffix,
263 # Some views have not yet been modified
264 $viewRec{modified} = $modified_date if $modified_date;
267 ($err, $msg) = $clearadm->UpdateView($view->tag, $view->region, %viewRec);
269 error "Unable to update view $name in Clearadm\n$msg", $err if $err;
271 ($err, $msg) = $clearadm->AddView (%viewRec);
273 error "Unable to add view $name to Clearadm\n$msg", $err if $err;
277 verbose "\nProcessed region $region";
285 if ($region =~ /all/i) {
286 for ($Clearcase::CC->regions) {
290 GenerateRegion $region;
299 $total{'Views processed'} = @views;
303 if ($opts{sortby} eq 'age') {
304 # Sort by age numerically decending
305 @sortedViews = sort { $$b{$opts{sortby}} <=> $$a{$opts{sortby}} } @views;
307 @sortedViews = sort { $$a{$opts{sortby}} cmp $$b{$opts{sortby}} } @views;
310 $total{Reported} = 0;
316 if ($nbrThreshold and $total{Reported} + 1 > $nbrThreshold) or
317 ($view{age} < $opts{ageThreshold});
322 if ($view{type} eq 'dynamic') {
324 } elsif ($view{type} eq 'snapshot') {
326 } elsif ($view{type} eq 'webview') {
329 $total{$view{type}}++;
336 View Name Owner View Type Last Modified Age
337 ------------------------------------- ---------------------- ----------- ---------------- -----------
340 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<< @<<<<<<<<<<<<<<< @>>>> @<<<<
341 $view{tag},$view{owner},$view{type},$view{modified},$view{age},$view{ageSuffix}
350 sub FormatTable ($@) {
351 my ($style, @views) = @_;
355 my $nbrViews = @views;
358 font ({-class => 'label'}, 'View type: ') .
359 font ({-class => 'dynamic'}, 'Dyanmic') .
361 font ({-class => 'snapshot'}, 'Snapshot') .
363 font ({-class => 'web'}, 'Web') .
365 font ({-class => 'unknown'}, 'Unknown');
369 my $regionDropdown = start_form (
373 $regionDropdown .= font {-class => 'captionLabel'}, 'Region: ';
374 $regionDropdown .= popup_menu (
376 -values => [$Clearcase::CC->regions],
377 -default => $Clearcase::CC->region,
378 -onchange => 'submit();',
381 $regionDropdown .= end_form;
383 $caption .= start_table {
389 $caption .= start_Tr;
393 }, font ({-class => 'label'}, 'Registry: '),
394 setField($Clearcase::CC->registry_host), '<br>',
395 font ({-class => 'label'}, 'Views: '),
407 $caption .= end_table;
409 $table .= start_table {
414 $table .= caption $caption;
415 $table .= start_Tr {-class => 'heading'};
418 # Set defaults if not set already
419 $opts{sortby} ||= 'age';
420 $opts{reverse} ||= 0;
422 my $parms = $opts{user} ? "&user=$opts{user}" : '';
423 $parms .= $opts{reverse} == 1 ? '&reverse=0' : '&reverse=1';
425 if ($style eq 'full') {
426 my $tagLabel = 'Tag ';
427 my $ownerLabel = 'Owner ';
428 my $typeLabel = 'Type ';
429 my $ageLabel = 'Age ';
431 if ($opts{sortby} eq 'tag') {
432 $tagLabel .= $opts{reverse} == 1
433 ? img {src => 'up.png', border => 0}
434 : img {src => 'down.png', border => 0};
435 } elsif ($opts{sortby} eq 'ownerName') {
436 $ownerLabel .= $opts{reverse} == 1
437 ? img {src => 'up.png', border => 0}
438 : img {src => 'down.png', border => 0};
439 } elsif ($opts{sortby} eq 'type') {
440 $typeLabel .= $opts{reverse} == 1
441 ? img {src => 'up.png', border => 0}
442 : img {src => 'down.png', border => 0};
443 } elsif ($opts{sortby} eq 'age') {
444 $ageLabel .= $opts{reverse} == 1
445 ? img {src => 'down.png', border => 0}
446 : img {src => 'up.png', border => 0};
449 $table .= th a {href => "$script?region=$opts{region}&sortby=tag$parms"},
451 $table .= th a {href => "$script?region=$opts{region}&sortby=ownerName$parms"},
453 $table .= th a {href => "$script?region=$opts{region}&sortby=type$parms"},
455 $table .= th a {href => "$script?region=$opts{region}&sortby=age$parms"},
459 $table .= th 'Owner';
465 if ($opts{sortby} eq 'age') {
466 # Sort by age numerically decending
467 @views = $opts{reverse} == 1
468 ? sort { $$a{$opts{sortby}} <=> $$b{$opts{sortby}} } @views
469 : sort { $$b{$opts{sortby}} <=> $$a{$opts{sortby}} } @views;
471 @views = $opts{reverse} == 1
472 ? sort { $$b{$opts{sortby}} cmp $$a{$opts{sortby}} } @views
473 : sort { $$a{$opts{sortby}} cmp $$b{$opts{sortby}} } @views;
481 next if $view{region} ne $opts{region};
483 my $owner = $view{owner};
485 if ($view{owner} =~ /\S+(\\|\/)(\S+)/) {
489 $owner = $view{ownerName} ? $view{ownerName} : 'Unknown';
491 next if $opts{user} and $owner ne $opts{user};
493 my $rowClass= $view{age} > $opts{ageThreshold} ? 'oldview' : 'view';
504 href => "viewdetails.cgi?tag=$view{tag}®ion=$opts{region}"
509 href => "$script?region=$opts{region}&user=$owner"
520 }, $view{age}, ' ', $view{ageSuffix});
529 # TODO: Add an option to remove views older than a certain date
532 my ($emailTo, @oldViews) = @_;
534 @oldViews = sort { $$b{age} <=> $$a{age} } @oldViews;
536 my $msg = '<style>' . join ("\n", ReadFile 'viewager.css') . '</style>';
538 <h1 align="center">You have old Clearcase Views</h1>
540 <p>Won't you take a moment to review this message and clean up any views you no
543 <p>The following views are owned by you and have not been modified in $opts{ageThreshold}
547 $msg .= FormatTable 'partial', @oldViews;
550 <h3>How to remove views you no longer need</h3>
552 <p>There are several ways to remove Clearcase views, depending on the view
553 type and the tools you are using.</p>
556 <p><b>Dynamic Views</b>: If the view is a dynamic view you can use Clearcase
557 Explorer to remove the view. Find the view in your Clearcase Explorer. If
558 it's not there then add it as a standard view shortcut. Then right click on
559 the view shortcut and select <b>Remove View</b> (not <b>Remove View
562 <p><b>Snapshot Views</b>: A snapshot view is a view who's source storage can
563 be located locally. You can remove a snapshot view in a similar manner as a
564 dynamic view, by adding it to Clearcase Explorer if not already present. By
565 doing so you need to tell Clearcase Explorer where the snapshot view storage
568 <p><b>Webviews</b>: Webviews are like snapshot views but stored on the web
569 server. If you are using CCRC or the CCRC plugin to Eclipse you would select
570 the view and then do <b>Environment: Remove Clearcase View</b>.</p>
573 <p>If you have any troubles removing your old views then submit a case and we
574 will be happy to assist you.</p>
576 <h3>But I need for my view to stay around even if it hasn't been modified</h3>
578 <p>If you have a long lasting view who does not get modified but needs to
579 remain, contact us and we can arrange for it to be removed from consideration
580 which will stop it from being reported as old.</p>
584 Your friendly Clearcase Administrator
589 # to => 'Andrew@DeFaria.com',
591 subject => 'Old views',
601 @views = sort { $$a{ownerName} cmp $$b{ownerName} } @views;
604 my $currUser = $views [0]->{ownerName};
612 if ($currUser ne $view{ownerName}) {
613 EmailUser $view{email}, @userViews
616 $currUser = $view{ownerName};
620 if ($view{age} > $opts{ageThreshold}) {
621 push @userViews, \%view
622 if !-f "$view{gpath}/ageless";
635 'usage' => sub { Usage },
636 'verbose' => sub { set_verbose },
637 'debug' => sub { set_debug },
644 ) or Usage "Invalid parameter";
646 # Get options from CGI
649 $opts{$_} = $CGIOpts{$_} for keys %CGIOpts;
654 verbose "$FindBin::Script v$VERSION";
656 $clearadm = Clearadm->new;
658 if ($opts{action} and $opts{action} eq 'generate') {
659 $opts{region} ||= 'all';
661 Generate $opts{region};
662 Stats \%total if $opts{verbose};
664 if ($opts{region} and ($opts{region} eq 'Clearcase not installed')) {
666 displayError $opts{region};
671 $opts{region} ||= $Clearcase::CC->region;
673 my @views = $clearadm->FindView (
680 if ($opts{action} and $opts{action} eq 'report') {
692 display FormatTable 'full', @views;
700 =head1 CONFIGURATION AND ENVIRONMENT
702 DEBUG: If set then $debug is set to this level.
704 VERBOSE: If set then $verbose is set to this level.
706 TRACE: If set then $trace is set to this level.
714 L<CGI::Carp|CGI::Carp>
716 L<Data::Dumper|Data::Dumper>
718 L<File::stat|File::stat>
722 L<Getopt::Long|Getopt::Long>
724 L<Time::localtime|Time::localtime>
726 =head2 ClearSCM Perl Modules
745 <a href="http://clearscm.com/php/scm_man.php?file=clearadm/lib/Clearadm.pm">Clearadm</a><br>
746 <a href="http://clearscm.com/php/scm_man.php?file=clearadm/lib/ClearadmWeb.pm">ClearadmWeb</a><br>
747 <a href="http://clearscm.com/php/scm_man.php?file=lib/Clearcase.pm">Clearcase</a><br>
748 <a href="http://clearscm.com/php/scm_man.php?file=lib/Clearcase/View.pm">Clearcase::View</a><br>
749 <a href="http://clearscm.com/php/scm_man.php?file=lib/Clearcase/Views.pm">Clearcase::Views</a><br>
750 <a href="http://clearscm.com/php/scm_man.php?file=lib/DateUtils.pm">DateUtils</a><br>
751 <a href="http://clearscm.com/php/scm_man.php?file=lib/Display.pm">Display</a><br>
752 <a href="http://clearscm.com/php/scm_man.php?file=lib/Mail.pm">Mail</a><br>
753 <a href="http://clearscm.com/php/scm_man.php?file=lib/Utils.pm">Utils</a><br>
754 <a href="http://clearscm.com/php/scm_man.php?file=clearadm/lib/User.pm">User</a><br>
759 =head1 BUGS AND LIMITATIONS
761 There are no known bugs in this script
763 Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
765 =head1 LICENSE AND COPYRIGHT
767 Copyright (c) 2010, ClearSCM, Inc. All rights reserved.