source: trunk/dasscm/dasscm@ 289

Last change on this file since 289 was 289, checked in by joergs, on Mar 11, 2009 at 7:49:21 AM

svn_ls: improved handling of single files

  • Property keyword set to id
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 33.5 KB
RevLine 
[186]1#!/usr/bin/perl -w
2
3# $Id: dasscm 289 2009-03-11 06:49:21Z joergs $
4
5use strict;
6
[208]7use Env
[235]8 qw($DASSCM_PROD $DASSCM_REPO $USER $DASSCM_USERNAME $DASSCM_USER $DASSCM_PASSWORD $SHELL);
[186]9use Cwd;
[214]10use Getopt::Long;
[186]11use File::Basename;
[209]12use File::Compare;
13use File::Copy;
[237]14use File::Find;
[186]15use File::stat;
16use File::Path;
[214]17use Term::ReadKey;
[239]18
[234]19#use Data::Dumper;
[186]20
[189]21#####################################################################
22#
[186]23# global
[189]24#
[205]25
[253]26# shell exit codes
[252]27my $RETURN_OK = 0;
28my $RETURN_NOK = 1;
29
[277]30# Nagios return codes
31my $RETURN_WARN = 1;
32my $RETURN_CRIT = 2;
33my $RETURN_UNKNOWN = 3;
34
[220]35
[238]36# documentation file (for usage)
37my $doc_file = "/usr/share/doc/packages/dasscm/dasscm_howto.txt";
38
[253]39# commands that require write access (and therefore a login)
40# to the repository server
[274]41my @COMMANDS_REQUIRE_WRITE = ( "add", "commit" );
[252]42
[215]43# configuration file
[205]44my $config_file = "/etc/dasscm.conf";
[234]45my $config = get_config($config_file);
[205]46my $DASSCM_LOCAL_REPOSITORY_BASE;
47my $DASSCM_REPOSITORY_NAME;
48my $DASSCM_SVN_REPOSITORY;
[247]49my $DASSCM_CHECKOUT_USERNAME;
50my $DASSCM_CHECKOUT_PASSWORD;
[286]51my $DASSCM_PERMISSION_FILE;
[205]52
[238]53# current directory at program start
54my $StartDirectory = cwd();
55
[268]56my $diff = "diff --exclude .svn ";
[205]57my $SVN = "svn ";
58my $svnOptions = "";
59my $svnCheckoutCredentials = "";
60my $svnPasswordCredentials = "";
[275]61
[268]62# flag. Set to true by svn_update
63# This prevents, that svn_update is called multiple times
64my $svnRepositoryIsUptodate = 0;
[205]65
[196]66# command line options get stored in options hash
[205]67my %options = ();
68
[197]69# subcommand, that gets executed (add, commit, ...)
[196]70my $command;
[186]71
[205]72my $verbose = 0;
73
[189]74#####################################################################
75#
[186]76# util functions
[189]77#
[187]78sub usage()
79{
[283]80 print '$Id: dasscm 289 2009-03-11 06:49:21Z joergs $';
81 print "\n\n";
[205]82 print "usage: dasscm <subcommand> [options] [args]\n";
83 print "\n";
84 print "dasscm is intended to help versioning configuration files\n";
85 print "\n";
86 print "Available subcommands:\n";
[215]87 print " help <subcommand>\n";
[205]88 print " init\n";
[274]89 print " login <username>\n";
90 print " up <path>\n";
91 print " ls <path>\n";
92 print " add <path>\n";
93 print " commit <path>\n";
94 print " diff <path>\n";
95 print " status <path>\n";
[277]96 print " check\n";
[274]97 print " cleanup\n";
[215]98 print " permissions\n";
[205]99 print "\n";
[238]100 print "preparation:\n", " if dasscm is already configured,\n",
[236]101 " use 'dasscm login' and then eg. 'add'.\n",
[220]102 " The environment variables\n", " DASSCM_REPO\n", " DASSCM_PROD\n",
103 " DASSCM_USERNAME\n", " DASSCM_PASSWORD\n",
104 " are evaluated, but set automatically by 'dasscm login'.\n", "\n",
[239]105 " If dasscm is not yet configured, read", " $doc_file\n";
[187]106}
107
[233]108sub warning(@)
109{
110 print "Warning: " . join( "\n ", @_ ) . "\n";
111}
112
113sub error(@)
114{
115 print "Error: " . join( "\n ", @_ ) . "\n";
116}
117
118sub fatalerror(@)
119{
[239]120 error(@_);
121
[233]122 #print "Exiting\n";
[239]123 exit 1;
[233]124}
125
[238]126#
127# reading config file and return key/value pairs as hash
128#
[234]129sub get_config
130{
[239]131 my $file = $_[0];
[234]132
[239]133 if ( !$file ) {
[234]134 fatalerror( "failed to open config file" . $file );
135 }
136
137 my $data = {};
138
139 # try to open config file
140 if ( !open( FH, $file ) ) {
141 fatalerror( "failed to open config file" . $file );
142 } else {
143 while (<FH>) {
144 chomp;
145 if (/^#/) {
146 next;
147 }
148 if ( $_ =~ /=/g ) {
[239]149
[238]150 # splitting in 2 fields at maximum
[234]151 my ( $option, $value ) = split( /=/, $_, 2 );
152 $option =~ s/^\s+//g;
153 $option =~ s/\s+$//g;
154 $option =~ s/\"+//g;
155 $value =~ s/^\s+//g;
156 $value =~ s/\s+$//g;
157 $value =~ s/\"+//g;
158
159 if ( length($option) ) {
160 $data->{$option} = $value;
161 }
162 }
163 }
164 }
165 close(FH);
166
167 return $data;
168}
169
[270]170#
171# check and evaluate environment variables
172#
[186]173sub check_env()
174{
[205]175
176 # DASSCM_PROD
177 if ( !$DASSCM_PROD ) {
178 $DASSCM_PROD = "/";
179 }
180
181 if ( !-d $DASSCM_PROD ) {
182 die "DASSCM_PROD ($DASSCM_PROD) is not set to a directory.\n";
183 }
184 if ($verbose) { print "DASSCM_PROD: " . $DASSCM_PROD . "\n"; }
185
186 # DASSCM_REPOSITORY_NAME
[208]187 if ( !$DASSCM_REPOSITORY_NAME ) {
188 die
189 "Variable DASSCM_REPOSITORY_NAME is not defined.\nIt needs to be a unique name.\nNormally the full qualified host name is used.\nUse file $config_file to configure it.\n";
190 }
[205]191
192 # DASSCM_REPO
193 if ( !$DASSCM_REPO ) {
194 if ( $DASSCM_LOCAL_REPOSITORY_BASE && $DASSCM_REPOSITORY_NAME ) {
195 $DASSCM_REPO =
196 $DASSCM_LOCAL_REPOSITORY_BASE . "/" . $DASSCM_REPOSITORY_NAME;
197 } else {
198 die
199 "Envirnonment variable DASSCM_REPO not set.\nSet DASSCM_REPO to the directory of the versioning system checkout for this machine.\n";
200 }
201 }
[215]202 if ($verbose) { print "DASSCM_REPO: " . $DASSCM_REPO . "\n"; }
[205]203
204 #
[248]205 # subversion checkout user
206 #
[252]207 if ( !$DASSCM_CHECKOUT_USERNAME ) {
208 fatalerror(
[248]209 "variable DASSCM_CHECKOUT_USERNAME is not defined.",
[252]210 "Use file $config_file to configure it."
211 );
[248]212 }
213
[252]214 if ( !$DASSCM_CHECKOUT_PASSWORD ) {
215 fatalerror(
[248]216 "variable DASSCM_CHECKOUT_PASSWORD is not defined.",
[252]217 "Use file $config_file to configure it."
218 );
[248]219 }
220
221 #
[239]222 # check if local repository directory exist
[235]223 # (if not creating by init)
[205]224 #
225 if ( $command ne "init" ) {
226 if ( not -d $DASSCM_REPO ) {
227 die
[208]228 "Can't access local repository DASSCM_REPO\n($DASSCM_REPO)\nCheck configuration and execute\n dasscm init\n";
[205]229 }
[208]230
[205]231 #
232 # user settings
233 #
[208]234
[205]235 # DASSCM_USER is legacy. Use DASSCM_USERNAME instead
[208]236 if ( !$DASSCM_USERNAME ) {
237 $DASSCM_USERNAME = $DASSCM_USER;
[205]238 }
239
240 # user root is not allowed for checkins.
241 # if user is root, DASSCM_USER has to be set,
242 # otherwise USER can be used
243 if ( "$USER" eq "root" ) {
[252]244 if ( ( not $DASSCM_USERNAME )
245 and ( grep { m|^$command$| } @COMMANDS_REQUIRE_WRITE ) )
246 {
247
248 #( $command ne "login" ) and ( $command ne "status" ) ) {
249 fatalerror(
250 "Envirnonment variable DASSCM_USERNAME not set.",
251 "Set DASSCM_USERNAME to your subversion user account or",
252 "use 'dasscm login'"
253 );
[205]254 }
255 $svnOptions .= " --no-auth-cache ";
256 } elsif ( !$DASSCM_USERNAME ) {
257 $DASSCM_USERNAME = $USER;
258 }
259
260 #
261 # password
262 #
[208]263 if ($DASSCM_PASSWORD) {
[267]264 $svnPasswordCredentials = " --password '$DASSCM_PASSWORD' ";
[205]265 }
266 }
267
268 #$svnOptions .= " --username $DASSCM_USERNAME "
[186]269}
270
[270]271#
272# has been intendend,
273# to check addtitional parameters.
274# Currently not used.
275#
[186]276sub check_parameter(@)
277{
278}
279
[287]280
[238]281#
[287]282# normalize path namens:
283# - directories should end with "/"
284# - use only single "/"
285#
286sub normalize_path($)
287{
288 my $path = shift || "";
289
290 if( $path =~ m|^/| ) {
291 # full path
292 if( -d $path ) {
293 # ensure, a directory ends with '/'
294 $path .= '/';
295 }
296 } elsif( -d cwd() . '/' . $path ) {
297 # ensure, a directory ends with '/'
298 $path .= '/';
299 }
300
301 # remove double (triple) slashes (/)
302 $path =~ s|/[/]*|/|g;
303
304 return $path;
305}
306
307
308
309#
[239]310# generate from (relative) filename
[238]311# all required file and directory names:
312# $basename, $dirname_prod, $dirname_repo,
313# $filename_prod, $filename_repo
314#
[187]315sub get_filenames(@)
316{
[250]317 my $filename_prod = $_[0] || ".";
[237]318
[238]319 # make filename absolut
[205]320 if ( !( $filename_prod =~ m/^\// ) ) {
[270]321 $filename_prod = cwd() . '/' . $filename_prod;
[205]322 }
[187]323
[239]324 if ( not -r $filename_prod ) {
325 fatalerror( $filename_prod . " is not accessable" );
[233]326 }
[205]327
[274]328 # dirname buggy: eg. "/etc/" is reduced to "/",
329 # "/etc" is used as filename
330 # herefore make sure, that if filename is a directory,
331 # it will end by "/"
[287]332 $filename_prod = normalize_path( $filename_prod );
[238]333
[270]334 ( my $basename, my $dirname_prod ) = fileparse($filename_prod);
335
[238]336 # uses chdir to determine real directory in a unique way
[205]337 chdir $dirname_prod or die $!;
[287]338 $dirname_prod = normalize_path( cwd() );
[238]339 chdir $StartDirectory;
340
[287]341 my $dirname_repo = normalize_path( $DASSCM_REPO . "/" . $dirname_prod );
342 my $filename_repo = normalize_path( "$dirname_repo/$basename" );
343
[214]344 if ($verbose) {
[287]345 print "filename_repo: " . $filename_repo . "\n";
346 print "dirname_repo: " . $dirname_repo ."\n";
347 print "filename_prod: " . $filename_prod . "\n";
348 print "dirname_prod: " . $dirname_prod . "\n";
349 print "basename: " . $basename . "\n";
[214]350 }
[205]351
352 return (
353 $basename, $dirname_prod, $dirname_repo,
354 $filename_prod, $filename_repo
355 );
[187]356}
357
[271]358sub copy_file_to_repository( $ )
359{
360 my $filename = shift;
[256]361
[271]362 (
363 my $basename,
364 my $dirname_prod,
365 my $dirname_repo,
366 my $filename_prod,
367 my $filename_repo
[275]368 )
369 = get_filenames($filename);
[271]370
371 # TODO: are permissions also copied?
372 copy( $filename_prod, $filename_repo )
[275]373 or error "failed to copy $filename_prod to repository: $!";
[271]374}
375
[256]376#
377# creates a file with permissions
378#
[215]379sub generatePermissionList
[209]380{
381
[215]382 # generieren der Zeilen für Permission-Savefile
383 my @files = @_;
384 my @permlist = ();
385 foreach my $file (@files) {
[227]386 $file = "/" . $file;
[239]387 if ( -e $file ) {
388 my $info = stat($file) || die "failed to stat $file: aborting";
389 my $mode = get_type( $info->mode ) & 07777;
[227]390 my $modestring = sprintf( "%04o", $mode );
[278]391 my $uidnumber = $info->uid;
392 my $uid = getpwuid($uidnumber) || $uidnumber;
393 my $gidnumber = $info->gid;
394 my $gid = getgrgid($gidnumber) || $gidnumber;
[227]395 push(
396 @permlist,
397 sprintf( "%-55s %-17s %4d",
[278]398 $file, "${uid}:${gid}", $modestring )
[227]399 );
400 }
[215]401 }
402 return @permlist;
403}
[209]404
[215]405sub get_type
406{
[209]407
[215]408 # Funktion übernommen aus /usr/bin/chkstat
409 my $S_IFLNK = 0120000; # symbolic link
410 my $S_IFREG = 0100000; # regular file
411 my $S_IFDIR = 0040000; # directory
412 my $S_IFCHAR = 0020000; # character device
413 my $S_IFBLK = 0060000; # block device
414 my $S_IFFIFO = 0010000; # fifo
415 my $S_IFSOCK = 0140000; # socket
416 my $S_IFMT = 0170000; # type of file
[209]417
[215]418 my $S_m;
419 if ( ( $_[0] & $S_IFMT ) == $S_IFLNK ) { $S_m = $_[0] - $S_IFLNK; }
420 elsif ( ( $_[0] & $S_IFMT ) == $S_IFREG ) { $S_m = $_[0] - $S_IFREG; }
421 elsif ( ( $_[0] & $S_IFMT ) == $S_IFDIR ) { $S_m = $_[0] - $S_IFDIR; }
422 elsif ( ( $_[0] & $S_IFMT ) == $S_IFCHAR ) { $S_m = $_[0] - $S_IFCHAR; }
423 elsif ( ( $_[0] & $S_IFMT ) == $S_IFBLK ) { $S_m = $_[0] - $S_IFBLK; }
424 elsif ( ( $_[0] & $S_IFMT ) == $S_IFFIFO ) { $S_m = $_[0] - $S_IFFIFO; }
425 elsif ( ( $_[0] & $S_IFMT ) == $S_IFSOCK ) { $S_m = $_[0] - $S_IFSOCK; }
426 $S_m;
[209]427}
428
[186]429sub run_command
430{
[205]431 my $command = shift;
[186]432
[205]433 #print "executing command: " . $command . "\n";
[186]434
[205]435 open( RESULT, $command . ' 2>&1 |' );
436 my @result = <RESULT>;
437 close(RESULT);
438 my $retcode = $? >> 8;
[186]439
[205]440 #print @result;
441 #if( $retcode ) { print "return code: " . $retcode . "\n"; }
[186]442
[205]443 return ( $retcode, @result );
[186]444}
445
[205]446sub run_interactive
447{
[186]448
[208]449 if ($verbose) {
[205]450 print "run_interactive:" . join( " ", @_ ) . "\n";
451 }
[196]452
[205]453 system(@_);
454 if ( $? == -1 ) {
455 printf "failed to execute: $!\n";
456 } elsif ( $? & 127 ) {
457 printf "child died with signal %d, %s coredump\n", ( $? & 127 ),
458 ( $? & 128 ) ? 'with' : 'without';
459 } elsif ( $? >> 8 != 0 ) {
460 printf "child exited with value %d\n", $? >> 8;
461 }
462 return ( $? >> 8 );
463}
464
[247]465sub svn_check_credentials( $$;$$ )
[196]466{
[205]467 my $username = shift;
468 my $password = shift;
469
[252]470 # check silently are allow user interaction?
[247]471 my $interactive = shift || 0;
[220]472
[247]473 # default: exit program, if repository is not accessable
474 # (do not exit for 'init')
475 my $fatalerror = shift || 1;
476
477 print "checking credentials ";
478
[239]479 if ( !$username ) {
480 fatalerror("no username given");
[238]481 }
482
[239]483 if ( !$password ) {
484 fatalerror("no password given");
[238]485 }
486
[247]487 print "for " . $username . "@" . $DASSCM_SVN_REPOSITORY . ": ";
488
[220]489 # Options for "svn info" are not supported by subversion 1.0.0 (SLES9),
490 # therefore switching to "svn status"
491 # ( my $rc_update, my @result ) =
492 # run_command(
493 # "$SVN info --non-interactive --no-auth-cache --username $username --password $password $DASSCM_SVN_REPOSITORY"
494 # );
495 #print @result;
496
[247]497 my $rc_update;
[252]498 if ($interactive) {
499 $rc_update =
500 run_interactive(
[267]501 "$SVN ls --no-auth-cache --username '$username' --password '$password' $DASSCM_SVN_REPOSITORY"
[252]502 );
[247]503 } else {
504 ( $rc_update, my @result ) =
[252]505 run_command(
[267]506 "$SVN ls --non-interactive --no-auth-cache --username '$username' --password '$password' $DASSCM_SVN_REPOSITORY"
[252]507 );
508
[247]509 if ( $rc_update != 0 ) {
510 print "\n", @result;
[252]511 if ($fatalerror) {
[247]512 fatalerror();
513 }
514 return;
515 }
[205]516 }
517
[247]518 # return success
519 return $rc_update == 0;
[196]520}
521
[205]522sub svn_update( ;$ )
523{
[270]524 my $update_path = shift || "";
525
526 # use this flag to do only one update per run
[275]527 if ( !$svnRepositoryIsUptodate ) {
[268]528 ( my $rc_update, my @result ) =
[275]529 run_command(
530 "$SVN update --non-interactive $svnCheckoutCredentials '$DASSCM_REPO/$update_path'"
531 );
[268]532 print @result;
533 if ( $rc_update != 0 ) {
[276]534 error( "failed to update local repository ($update_path)" );
[270]535 } elsif ( not $update_path ) {
[275]536
[270]537 # set this flag if a full update is done
[268]538 $svnRepositoryIsUptodate = 1;
539 }
[205]540 }
[215]541}
[196]542
[271]543sub svn_ls( ;@ )
[215]544{
[270]545 (
546 my $basename,
547 my $dirname_prod,
548 my $dirname_repo,
549 my $filename_prod,
550 my $filename_repo
551 )
552 = get_filenames( $_[0] );
[220]553
[218]554 # svn ls -R is better, but much, much slower
555 # ( my $rc, my @result ) = run_command("$SVN ls --recursive $svnCheckoutCredentials $path");
[270]556
[271]557 my @files = ();
558 my @links = ();
559 my @dirs = ();
560 my @others = ();
561
[289]562 find(
563 {
564 wanted => sub {
565 my $name = normalize_path($File::Find::name);
566 $name =~ s|^$dirname_repo||;
567 #print "($name)\n";# . $File::Find::dir . "\n";
568 if ( not $name ) {
569 # name string is empty (top directory).
570 # do nothing
571 } elsif ( $name =~ m/\.svn/ ) {
[275]572
[289]573 # skip svn meta data
574 } elsif ( -l $_ ) {
[275]575
[289]576 # soft link
577 # important: check for links first
578 # to exclude them from further checks
579 push( @links, $name );
580 } elsif ( -d $_ ) {
581 #if( $name."/" eq dirname_repo ) {
582 # directories
583 push( @dirs, $name );
584 #}
585 } elsif ( -f $_ ) {
[275]586
[289]587 # regular file
588 push( @files, $name );
589 } else {
590 push( @others, $name );
591 }
592 }
593 },
594 ($filename_repo)
595 );
[271]596
[287]597 return ( sort( @dirs, @files ) );
[205]598}
599
[287]600
601
[274]602sub svn_revert( ;$ )
603{
604 my $path = shift || $DASSCM_REPO;
605
[275]606 ( my $rc_update, my @result ) = run_command("$SVN revert -R '$path'");
[274]607
608 if ( $rc_update != 0 ) {
609 print "\n", @result;
[275]610 error("failed to revert subversion repository changes");
[274]611 }
612}
613
[285]614sub svn_remove_unknown_files( ;$ )
615{
616 my $path = shift || $DASSCM_REPO;
617
618 ( my $rc_update, my @result ) = run_command("$SVN status '$path'" );
619
620 if ( $rc_update != 0 ) {
621 print "\n", @result;
622 error("failed to receive subversion repository information");
623 } else {
624 foreach (@result) {
625 if( s/^\? +// ) {
626 chomp;
627 # if file is unknown to subversion (line starts with "?")
628 # remove it
629 print "removing $_\n";
630 unlink( $_ );
631 }
632 }
633 }
634}
635
[268]636sub getModifiedFiles( ;$ )
637{
[270]638 (
639 my $basename,
640 my $dirname_prod,
641 my $dirname_repo,
642 my $filename_prod,
643 my $filename_repo
644 )
645 = get_filenames( $_[0] );
[268]646
[275]647 my @files = svn_ls($filename_prod);
[270]648
[268]649 # stores result from status (cvscheck)
650 my %removedfiles = ();
651 my %changedfiles = ();
[278]652 my %unknownfiles = ();
[268]653
654 # create list of modified files
655 if (@files) {
656
657 foreach my $file (@files) {
658
[271]659 my $realfile = $dirname_prod . $file;
660 my $cvsworkfile = $dirname_repo . $file;
[268]661
662 if ( -d $realfile ) {
[278]663 # directory
664 if( !-d "$cvsworkfile" ) {
665 # real is directory, repository is not. This is a problem
666 $changedfiles{"$realfile"} = $cvsworkfile;
667 }
668 } elsif ( !-e $realfile ) {
669 $removedfiles{"$realfile"} = $cvsworkfile;
[268]670 } elsif ( !-r $realfile ) {
[278]671 # don't have permission to read the file,
672 # can't check it
673 $unknownfiles{"$realfile"} = $cvsworkfile;
[268]674 } else {
675 ( -r "$cvsworkfile" )
[278]676 || fatalerror("failed to read $cvsworkfile");
[268]677 if ( compare( $cvsworkfile, $realfile ) != 0 ) {
678 $changedfiles{"$realfile"} = $cvsworkfile;
679 }
680 }
681 }
682 }
683
[278]684 return ( \%changedfiles, \%removedfiles, \%unknownfiles );
[268]685}
686
[238]687#
688# from an array of files/dirs,
689# generates list of files
690# sorted by type
691#
[237]692sub get_files( @ )
693{
694 my @files = ();
695 my @links = ();
696 my @dirs = ();
697 my @others = ();
698
[239]699 if (@_) {
700 find(
701 {
702 wanted => sub {
703 my $fullname = cwd() . "/" . $_;
704 if ( -l $_ ) {
705
706 # soft link
707 # important: check for links first
708 # to exclude them from further checks
709 push( @links, $fullname );
710 } elsif ( -d $_ ) {
[271]711
712 # directories
[239]713 push( @dirs, $fullname );
714 } elsif ( -f $_ ) {
715
716 # regular file
717 push( @files, $fullname );
718 } else {
719 push( @others, $fullname );
720 }
721 }
722 },
723 @_
724 );
[237]725 }
726
[239]727 # don't rely on others.
[237]728 # If more specific file types are needed,
729 # they will be added
730 return {
[239]731 files => \@files,
732 links => \@links,
733 dirs => \@dirs,
734 others => \@others
735 };
[237]736}
737
[189]738#####################################################################
739#
[186]740# functions
741
742sub help(;@)
743{
[205]744 if ( @_ == 0 ) {
745 usage();
746 } else {
747 print "help for @_: ...\n";
[214]748 usage();
[205]749 }
[186]750}
751
[203]752sub login(@)
753{
[205]754 check_parameter( @_, 1 );
755 check_env();
[203]756
[235]757 my $input_username = $_[0];
[214]758
759 if ( not $input_username ) {
760 my $output_username = "";
761 if ($DASSCM_USERNAME) {
762 $output_username = " ($DASSCM_USERNAME)";
763 }
764
765 print "Enter DASSCM user name", $output_username, ": ";
766 $input_username = <STDIN>;
767 chomp($input_username);
[247]768
769 $input_username = $input_username || $DASSCM_USERNAME;
[205]770 }
[203]771
[205]772 # hidden password input
[247]773 print "Enter password for $input_username: ";
[205]774 ReadMode('noecho');
775 my $input_password = <STDIN>;
776 ReadMode('normal');
777 chomp($input_password);
[220]778 print "\n";
[203]779
[247]780 # checking checkout username/password
[252]781 svn_check_credentials( $DASSCM_CHECKOUT_USERNAME,
782 $DASSCM_CHECKOUT_PASSWORD );
[247]783 print "checkout access okay\n";
[205]784
[247]785 svn_check_credentials( $input_username, $input_password );
786
[205]787 #
788 # set environment variables
789 #
[267]790 $ENV{'DASSCM_USERNAME'} = "$input_username";
791 $ENV{'DASSCM_PASSWORD'} = "$input_password";
[205]792
[209]793 print "subversion access okay\n\n", "DASSCM_USERNAME: $input_username\n",
794 "DASSCM_PASSWORD: (hidden)\n", "DASSCM_PROD: $DASSCM_PROD\n",
795 "DASSCM_REPO: $DASSCM_REPO\n",
[278]796 "Server Repository: $DASSCM_SVN_REPOSITORY\n", "\n";
[205]797
[278]798 status();
799
800 print "\n[dasscm shell]\n\n";
[235]801 my $shell = $SHELL || "bash";
802 exec($shell) or die "failed to start new shell";
[203]803}
804
[260]805#
806# initialize local checkout directory (initial checkout)
807#
[205]808sub init(@)
809{
810 check_parameter( @_, 1 );
811 check_env();
812
[235]813 # don't do repository creation (svn mkdir) here,
814 # because then their must be a lot of prior checks
815
[205]816 # update complete repository
[216]817 # and create permission file
[208]818 my $retcode =
819 run_interactive(
[286]820 "cd $DASSCM_LOCAL_REPOSITORY_BASE; $SVN checkout $svnCheckoutCredentials $svnOptions $DASSCM_SVN_REPOSITORY; mkdir -p `dirname $DASSCM_PERMISSION_FILE`; touch $DASSCM_PERMISSION_FILE"
[208]821 );
[205]822}
823
[215]824sub ls(@)
[186]825{
[205]826 check_parameter( @_, 1 );
827 check_env();
[186]828
[271]829 my @files = svn_ls(@_);
[215]830
[275]831 if (@files) {
[274]832 print join( "\n", @files );
833 print "\n";
834 }
[215]835}
836
837sub update(@)
838{
839 check_parameter( @_, 1 );
840 check_env();
841
842 #
843 # update local repository
844 #
845 svn_update();
846}
847
[237]848#
849# helper function for "add" command
850#
[215]851sub add_helper(@)
852{
[205]853 (
854 my $basename,
855 my $dirname_prod,
856 my $dirname_repo,
857 my $filename_prod,
858 my $filename_repo
859 )
860 = get_filenames( $_[0] );
[186]861
[274]862 mkpath($dirname_repo);
[186]863
[238]864 # TODO: are permissions also copied?
[239]865 copy( $filename_prod, $filename_repo )
866 or error "failed to copy $filename_prod to repository: $!";
[205]867
[274]868 # already checked in?
869 chdir $DASSCM_REPO;
[205]870
[274]871 # also add the path to filename.
872 for my $dir ( split( '/', $dirname_prod ) ) {
873 if ($dir) {
[275]874 my ( $rc, @out ) = run_command("$SVN add --non-recursive '$dir'");
[274]875 if ( $rc > 0 ) {
876 print join( "\n", @out );
[205]877 }
[274]878 chdir $dir;
[205]879 }
880 }
[275]881 my ( $rc, @out ) = run_command("$SVN add '$basename'");
[274]882 if ( $rc > 0 ) {
883 print join( "\n", @out );
884 }
885 chdir $StartDirectory;
886
[215]887}
[205]888
[215]889#
[274]890# adding new files (or directories)
[215]891#
892sub add(@)
893{
894 check_parameter( @_, 1 );
895 check_env();
896
897 #
898 # update local repository
899 #
900 svn_update();
901
[237]902 # get all regular files and links
[239]903 my $href_files = get_files(@_);
[220]904
[237]905 #print Dumper( $href_files );
906
[239]907 my @files = @{ $href_files->{files} };
908 my @links = @{ $href_files->{links} };
[237]909
[239]910 if (@files) {
[237]911 my $number = $#files + 1;
912 print "files to check-in ($number): \n";
913 print join( "\n", @files );
914 print "\n";
915 }
916
[268]917 # TODO: check in links and also link target? At least warn about link target
[239]918 if (@links) {
[237]919 my $number = $#links + 1;
920 print "\n";
921 print "ignoring links ($number):\n";
922 print join( "\n", @links );
923 print "\n";
924 }
925
[238]926 # TODO: confirm
927
[237]928 # copy files one by one to local repository
929 for my $file (@files) {
[239]930
[233]931 # add file
[239]932 add_helper($file);
[233]933 }
934
[215]935 # create new permissions file
936 permissions();
[220]937
[215]938 # add permissions file
[286]939 add_helper($DASSCM_PERMISSION_FILE);
[215]940
[205]941 if ( $options{'message'} ) {
942 $svnOptions .= " --message \"$options{'message'}\" ";
943 }
944
[239]945 # commit calls $EDITOR.
[237]946 # use "interactive" here, to display output
[215]947 my $retcode =
[205]948 run_interactive(
[267]949 "$SVN commit $svnOptions --username '$DASSCM_USERNAME' $svnPasswordCredentials $DASSCM_REPO"
[208]950 );
[205]951
[274]952 # svn commit does not deliever an error return code, if commit is canceld,
953 # so a revert is performed in any case
954 svn_revert();
[186]955}
956
[271]957#
958# checks in all modified files
959#
960sub commit(@)
961{
962 check_parameter( @_, 1 );
963 check_env();
964
965 (
966 my $basename,
967 my $dirname_prod,
968 my $dirname_repo,
969 my $filename_prod,
970 my $filename_repo
[275]971 )
972 = get_filenames( $_[0] );
[271]973
974 #
975 # update local repository
976 #
977 svn_update();
978
[275]979 ( my $refChangedFiles, my $refRemovedFiles ) =
980 getModifiedFiles($filename_prod);
[271]981 my %changedfiles = %{$refChangedFiles};
982 my %removedfiles = %{$refRemovedFiles};
983
[275]984 if (%removedfiles) {
985 my $removedFilesString =
986 '"' . join( '" "', values(%removedfiles) ) . '"';
987 my ( $rc, @out ) = run_command("$SVN rm $removedFilesString");
[271]988 if ( $rc > 0 ) {
989 print join( "\n", @out );
990 }
991 }
992
993 # copy files one by one to local repository
994 for my $file ( keys(%changedfiles) ) {
[275]995 copy_file_to_repository($file);
[271]996 }
997
998 # create new permissions file
999 permissions();
1000
1001 # add permissions file
[286]1002 add_helper($DASSCM_PERMISSION_FILE);
[271]1003
1004 if ( $options{'message'} ) {
1005 $svnOptions .= " --message \"$options{'message'}\" ";
1006 }
1007
1008 # commit calls $EDITOR.
1009 # use "interactive" here, to display output
1010 my $retcode =
1011 run_interactive(
1012 "$SVN commit $svnOptions --username '$DASSCM_USERNAME' $svnPasswordCredentials $DASSCM_REPO"
1013 );
[274]1014
1015 # svn commit does not deliever an error return code, if commit is canceld,
1016 # so a revert is performed in any case
1017 svn_revert();
[271]1018}
1019
[193]1020sub blame(@)
1021{
[205]1022 check_parameter( @_, 1 );
1023 check_env();
[193]1024
[205]1025 (
1026 my $basename,
1027 my $dirname_prod,
1028 my $dirname_repo,
1029 my $filename_prod,
1030 my $filename_repo
1031 )
1032 = get_filenames( $_[0] );
1033
1034 my $retcode = run_interactive("$SVN blame $svnOptions $filename_repo");
[193]1035}
1036
[187]1037sub diff(@)
1038{
[205]1039 check_parameter( @_, 1 );
1040 check_env();
[187]1041
[205]1042 (
1043 my $basename,
1044 my $dirname_prod,
1045 my $dirname_repo,
1046 my $filename_prod,
1047 my $filename_repo
1048 )
1049 = get_filenames( $_[0] );
1050
1051 #print "$basename,$dirname_prod,$dirname_repo\n";
1052
1053 ( my $rc_update, my @result ) = run_command("$SVN update $filename_repo");
1054 if ( $rc_update != 0 ) {
1055 print @result;
1056 die;
1057 }
1058
[238]1059 ( my $rc_diff, my @diff_result ) =
[239]1060 run_command( $diff . " $filename_repo $filename_prod" );
[238]1061
1062 print @diff_result;
[187]1063}
1064
[209]1065sub status(@)
1066{
1067 check_parameter( @_, 1 );
1068 check_env();
1069
[270]1070 (
1071 my $basename,
1072 my $dirname_prod,
1073 my $dirname_repo,
1074 my $filename_prod,
1075 my $filename_repo
1076 )
1077 = get_filenames( $_[0] || "/" );
1078
[252]1079 # return code for the shell
1080 # default: error
1081 my $return_code = $RETURN_NOK;
1082
[209]1083 #
1084 # update local repository
1085 #
[278]1086 #svn_update( $filename_prod );
[209]1087
[278]1088 # check, if permissions have changed
1089 permissions();
1090
1091 # get modified files
1092 ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) =
[275]1093 getModifiedFiles($dirname_prod);
[268]1094 my %changedfiles = %{$refChangedFiles};
1095 my %removedfiles = %{$refRemovedFiles};
[278]1096 my %unknownfiles = %{$refUnknownFiles};
[209]1097
[278]1098 if ( %removedfiles or %changedfiles or %unknownfiles ) {
1099
[215]1100 if (%removedfiles) {
[278]1101 print "DELETED: files found in repository, but not in system:\n";
1102 print join( "\n", ( keys %removedfiles ) ) . "\n\n";
[209]1103 }
1104
[215]1105 if (%changedfiles) {
[278]1106 print "MODIFIED: files differs between repository and system:\n";
1107 print join( "\n", ( keys %changedfiles ) ) . "\n\n";
[209]1108 }
[278]1109
1110 if (%unknownfiles) {
1111 print "UNKNOWN: insufficient permission to check files:\n";
1112 print join( "\n", ( keys %unknownfiles ) ) . "\n\n";
1113 }
1114
[209]1115 } else {
[270]1116 print "no modified files found in $dirname_repo\n";
[252]1117 $return_code = $RETURN_OK;
[209]1118 }
[215]1119
[252]1120 return $return_code;
[215]1121}
[209]1122
[277]1123#
1124# return short status in Nagios plugin conform way
1125#
1126sub check()
1127{
1128 check_env();
1129
1130 # return code for the shell
1131 my $return_code = $RETURN_OK;
1132 my $return_string = "OK: no modified files";
1133
[278]1134 # check, if permissions have changed
1135 permissions();
1136
1137 # get modified files
1138 ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) =
[277]1139 getModifiedFiles( "/" );
1140 my %changedfiles = %{$refChangedFiles};
1141 my %removedfiles = %{$refRemovedFiles};
[278]1142 my %unknownfiles = %{$refUnknownFiles};
[277]1143
1144 if ( %removedfiles or %changedfiles ) {
1145 $return_string = "Warning: ";
1146 if( %changedfiles ) {
1147 $return_string .= "changed: " . join( ", ", ( keys %changedfiles ) ) . ". ";
1148 }
1149 if( %removedfiles ) {
1150 $return_string .= "removed: " . join( ", ", ( keys %removedfiles ) ) . ". ";
1151 }
[278]1152 if (%unknownfiles) {
1153 $return_string .= "unknown: " . join( ", ", ( keys %unknownfiles ) ) . ". ";
1154 }
[277]1155 $return_code = $RETURN_WARN;
1156 }
1157
1158 # addition nagios Service Status
1159 #Critical
1160 #Unknown
1161
1162 print $return_string . "\n";
1163 return $return_code;
1164}
1165
1166
[270]1167sub permissions()
[215]1168{
1169 check_env();
1170
[278]1171 my $return_code = $RETURN_OK;
1172
[215]1173 #
1174 # update local repository
1175 #
1176 #svn_update();
1177
[220]1178 my $dir = $DASSCM_REPO;
[275]1179 my @files = svn_ls("/");
[215]1180
1181 if (@files) {
1182
1183 # generieren der Permissions
1184 my @permissions = generatePermissionList(@files);
1185 my $OUTFILE;
1186 my $tofile = 0; # Status für schreiben in File
[220]1187
[286]1188 if ( -w dirname($DASSCM_PERMISSION_FILE) ) {
[215]1189
1190 # Verzeichnis existiert => schreiben
[286]1191 open( OUTFILE, ">$DASSCM_PERMISSION_FILE" )
1192 || die("failed to write to $DASSCM_PERMISSION_FILE: $!");
[215]1193 $tofile = 1; # Merken, daß in File geschrieben wird
1194 print OUTFILE "#\n";
1195 print OUTFILE "# created by dasscm permissions\n";
[220]1196 print OUTFILE
1197 "# It is intended to be used for restoring permissions\n";
[287]1198 print OUTFILE "#\n";
[215]1199 } else {
1200
[278]1201 if( $command eq "permission" ) {
1202 # Pfad für Sicherungsdatei existiert nicht => schreiben auf stdout
1203 # Alias Filehandle für stdout erzeugen
1204 $return_code = $RETURN_WARN;
1205 *OUTFILE = *STDOUT;
1206 } else {
1207 # TODO: improve this. Check for diff?
1208 $return_code = $RETURN_CRIT;
1209 return $return_code;
1210 }
[215]1211 }
[278]1212
[215]1213 foreach my $line (@permissions) {
1214 print OUTFILE "$line\n";
1215 }
1216
[220]1217 if ($tofile) {
[215]1218 close(OUTFILE);
1219 }
1220 }
[278]1221
1222 return $return_code;
[209]1223}
1224
[274]1225#
1226# remove all uncommited changes in the repository
1227#
1228sub cleanup()
1229{
1230 check_env();
[268]1231
[275]1232 svn_revert($DASSCM_REPO);
[285]1233 svn_remove_unknown_files($DASSCM_REPO);
[274]1234}
1235
[189]1236#####################################################################
1237#
[186]1238# main
[189]1239#
[186]1240
[252]1241my $return_code = $RETURN_OK;
[186]1242my $number_arguments = @ARGV;
1243
[205]1244if ( $number_arguments > 0 ) {
[186]1245
[205]1246 # get subcommand and remove it from @ARGV
1247 $command = $ARGV[0];
1248 shift @ARGV;
[196]1249
[205]1250 $DASSCM_LOCAL_REPOSITORY_BASE = $config->{'DASSCM_LOCAL_REPOSITORY_BASE'};
1251 $DASSCM_REPOSITORY_NAME = $config->{'DASSCM_REPOSITORY_NAME'};
[196]1252
[205]1253 # TODO: check variables
1254 $DASSCM_SVN_REPOSITORY =
1255 $config->{'DASSCM_SVN_REPOSITORY_BASE'} . "/" . $DASSCM_REPOSITORY_NAME;
1256
[247]1257 $DASSCM_CHECKOUT_USERNAME = $config->{'DASSCM_CHECKOUT_USERNAME'};
1258 $DASSCM_CHECKOUT_PASSWORD = $config->{'DASSCM_CHECKOUT_PASSWORD'};
[205]1259
1260 #
1261 # if a user is given by dasscm configuration file, we use it.
1262 # Otherwise we expect that read-only account is configured
1263 # as local subversion configuration.
1264 # If this is also not the case,
1265 # user is required to type username and password.
1266 # This will be stored as local subversion configuration thereafter.
1267 #
1268 if ( $DASSCM_CHECKOUT_USERNAME && $DASSCM_CHECKOUT_PASSWORD ) {
1269 $svnCheckoutCredentials =
1270 " --username $DASSCM_CHECKOUT_USERNAME --password $DASSCM_CHECKOUT_PASSWORD ";
1271 }
1272
[286]1273 $DASSCM_PERMISSION_FILE = $config->{'DASSCM_PERMISSION_FILE'} || "/etc/permissions.d/dasscm.permission_backup";
1274
[205]1275 # get command line options and store them in options hash
[214]1276 my $result = GetOptions( \%options, 'verbose', 'message=s' );
[205]1277
1278 # print options
1279 foreach my $option ( keys %options ) {
[215]1280 print "${option}: $options{$option}\n";
[205]1281 }
1282
[214]1283 # set verbose to command line option
1284 $verbose = $options{'verbose'};
1285
1286 #
1287 # action accordinly to command are taken
1288 # $command is rewritten in standard format,
1289 # so we can test for it later on more simply
1290 #
[205]1291 $_ = $command;
[274]1292 if (m/^help$/i) {
[205]1293 help(@ARGV);
[274]1294 } elsif (m/^login$/i) {
[208]1295 $command = "login";
[205]1296 login(@ARGV);
[274]1297 } elsif (m/^init$/i) {
[208]1298 $command = "init";
[205]1299 init(@ARGV);
[274]1300 } elsif (m/^ls$/i) {
[215]1301 $command = "ls";
1302 ls(@ARGV);
[274]1303 } elsif ( (m/^update$/i) || (m/^up$/i) ) {
[215]1304 $command = "update";
1305 update(@ARGV);
[274]1306 } elsif (m/^add$/i) {
[205]1307 $command = "add";
1308 add(@ARGV);
[274]1309 } elsif ( (m/^commit$/i) || (m/^checkin$/i) || (m/^ci$/i) ) {
[205]1310 $command = "commit";
[271]1311 commit(@ARGV);
[274]1312 } elsif (m/^blame$/i) {
[208]1313 $command = "blame";
[205]1314 blame(@ARGV);
[274]1315 } elsif (m/^diff$/i) {
[208]1316 $command = "diff";
[205]1317 diff(@ARGV);
[274]1318 } elsif ( (m/^status$/i) || (m/^st$/i) ) {
[252]1319 $command = "status";
1320 $return_code = status(@ARGV);
[277]1321 } elsif (m/^check$/i) {
1322 $command = "check";
1323 $return_code = check();
[274]1324 } elsif (m/^permissions$/i) {
[215]1325 $command = "permissions";
[278]1326 $return_code = permissions();
[274]1327 } elsif (m/^cleanup$/i) {
1328 $command = "cleanup";
1329 cleanup();
[205]1330 } else {
[215]1331 print "unknown command: $command\n\n";
[205]1332 usage();
1333 check_env();
[252]1334 $return_code = $RETURN_NOK;
[205]1335 }
1336
1337 # revert
[252]1338
[186]1339}
[252]1340
1341exit $return_code;
Note: See TracBrowser for help on using the repository browser.