source: trunk/dasscm/dasscm@ 282

Last change on this file since 282 was 278, checked in by joergs, on Mar 6, 2009 at 10:38:05 PM

login + status, status + permission. Handle Unknown (unreadable) files. BUGFIX: uid and gid numbers, if no names are available

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