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
Line 
1#!/usr/bin/perl -w
2
3# $Id: dasscm 289 2009-03-11 06:49:21Z joergs $
4
5use strict;
6
7use Env
8 qw($DASSCM_PROD $DASSCM_REPO $USER $DASSCM_USERNAME $DASSCM_USER $DASSCM_PASSWORD $SHELL);
9use Cwd;
10use Getopt::Long;
11use File::Basename;
12use File::Compare;
13use File::Copy;
14use File::Find;
15use File::stat;
16use File::Path;
17use Term::ReadKey;
18
19#use Data::Dumper;
20
21#####################################################################
22#
23# global
24#
25
26# shell exit codes
27my $RETURN_OK = 0;
28my $RETURN_NOK = 1;
29
30# Nagios return codes
31my $RETURN_WARN = 1;
32my $RETURN_CRIT = 2;
33my $RETURN_UNKNOWN = 3;
34
35
36# documentation file (for usage)
37my $doc_file = "/usr/share/doc/packages/dasscm/dasscm_howto.txt";
38
39# commands that require write access (and therefore a login)
40# to the repository server
41my @COMMANDS_REQUIRE_WRITE = ( "add", "commit" );
42
43# configuration file
44my $config_file = "/etc/dasscm.conf";
45my $config = get_config($config_file);
46my $DASSCM_LOCAL_REPOSITORY_BASE;
47my $DASSCM_REPOSITORY_NAME;
48my $DASSCM_SVN_REPOSITORY;
49my $DASSCM_CHECKOUT_USERNAME;
50my $DASSCM_CHECKOUT_PASSWORD;
51my $DASSCM_PERMISSION_FILE;
52
53# current directory at program start
54my $StartDirectory = cwd();
55
56my $diff = "diff --exclude .svn ";
57my $SVN = "svn ";
58my $svnOptions = "";
59my $svnCheckoutCredentials = "";
60my $svnPasswordCredentials = "";
61
62# flag. Set to true by svn_update
63# This prevents, that svn_update is called multiple times
64my $svnRepositoryIsUptodate = 0;
65
66# command line options get stored in options hash
67my %options = ();
68
69# subcommand, that gets executed (add, commit, ...)
70my $command;
71
72my $verbose = 0;
73
74#####################################################################
75#
76# util functions
77#
78sub usage()
79{
80 print '$Id: dasscm 289 2009-03-11 06:49:21Z joergs $';
81 print "\n\n";
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";
87 print " help <subcommand>\n";
88 print " init\n";
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";
96 print " check\n";
97 print " cleanup\n";
98 print " permissions\n";
99 print "\n";
100 print "preparation:\n", " if dasscm is already configured,\n",
101 " use 'dasscm login' and then eg. 'add'.\n",
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",
105 " If dasscm is not yet configured, read", " $doc_file\n";
106}
107
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{
120 error(@_);
121
122 #print "Exiting\n";
123 exit 1;
124}
125
126#
127# reading config file and return key/value pairs as hash
128#
129sub get_config
130{
131 my $file = $_[0];
132
133 if ( !$file ) {
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 ) {
149
150 # splitting in 2 fields at maximum
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
170#
171# check and evaluate environment variables
172#
173sub check_env()
174{
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
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 }
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 }
202 if ($verbose) { print "DASSCM_REPO: " . $DASSCM_REPO . "\n"; }
203
204 #
205 # subversion checkout user
206 #
207 if ( !$DASSCM_CHECKOUT_USERNAME ) {
208 fatalerror(
209 "variable DASSCM_CHECKOUT_USERNAME is not defined.",
210 "Use file $config_file to configure it."
211 );
212 }
213
214 if ( !$DASSCM_CHECKOUT_PASSWORD ) {
215 fatalerror(
216 "variable DASSCM_CHECKOUT_PASSWORD is not defined.",
217 "Use file $config_file to configure it."
218 );
219 }
220
221 #
222 # check if local repository directory exist
223 # (if not creating by init)
224 #
225 if ( $command ne "init" ) {
226 if ( not -d $DASSCM_REPO ) {
227 die
228 "Can't access local repository DASSCM_REPO\n($DASSCM_REPO)\nCheck configuration and execute\n dasscm init\n";
229 }
230
231 #
232 # user settings
233 #
234
235 # DASSCM_USER is legacy. Use DASSCM_USERNAME instead
236 if ( !$DASSCM_USERNAME ) {
237 $DASSCM_USERNAME = $DASSCM_USER;
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" ) {
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 );
254 }
255 $svnOptions .= " --no-auth-cache ";
256 } elsif ( !$DASSCM_USERNAME ) {
257 $DASSCM_USERNAME = $USER;
258 }
259
260 #
261 # password
262 #
263 if ($DASSCM_PASSWORD) {
264 $svnPasswordCredentials = " --password '$DASSCM_PASSWORD' ";
265 }
266 }
267
268 #$svnOptions .= " --username $DASSCM_USERNAME "
269}
270
271#
272# has been intendend,
273# to check addtitional parameters.
274# Currently not used.
275#
276sub check_parameter(@)
277{
278}
279
280
281#
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#
310# generate from (relative) filename
311# all required file and directory names:
312# $basename, $dirname_prod, $dirname_repo,
313# $filename_prod, $filename_repo
314#
315sub get_filenames(@)
316{
317 my $filename_prod = $_[0] || ".";
318
319 # make filename absolut
320 if ( !( $filename_prod =~ m/^\// ) ) {
321 $filename_prod = cwd() . '/' . $filename_prod;
322 }
323
324 if ( not -r $filename_prod ) {
325 fatalerror( $filename_prod . " is not accessable" );
326 }
327
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 "/"
332 $filename_prod = normalize_path( $filename_prod );
333
334 ( my $basename, my $dirname_prod ) = fileparse($filename_prod);
335
336 # uses chdir to determine real directory in a unique way
337 chdir $dirname_prod or die $!;
338 $dirname_prod = normalize_path( cwd() );
339 chdir $StartDirectory;
340
341 my $dirname_repo = normalize_path( $DASSCM_REPO . "/" . $dirname_prod );
342 my $filename_repo = normalize_path( "$dirname_repo/$basename" );
343
344 if ($verbose) {
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";
350 }
351
352 return (
353 $basename, $dirname_prod, $dirname_repo,
354 $filename_prod, $filename_repo
355 );
356}
357
358sub copy_file_to_repository( $ )
359{
360 my $filename = shift;
361
362 (
363 my $basename,
364 my $dirname_prod,
365 my $dirname_repo,
366 my $filename_prod,
367 my $filename_repo
368 )
369 = get_filenames($filename);
370
371 # TODO: are permissions also copied?
372 copy( $filename_prod, $filename_repo )
373 or error "failed to copy $filename_prod to repository: $!";
374}
375
376#
377# creates a file with permissions
378#
379sub generatePermissionList
380{
381
382 # generieren der Zeilen für Permission-Savefile
383 my @files = @_;
384 my @permlist = ();
385 foreach my $file (@files) {
386 $file = "/" . $file;
387 if ( -e $file ) {
388 my $info = stat($file) || die "failed to stat $file: aborting";
389 my $mode = get_type( $info->mode ) & 07777;
390 my $modestring = sprintf( "%04o", $mode );
391 my $uidnumber = $info->uid;
392 my $uid = getpwuid($uidnumber) || $uidnumber;
393 my $gidnumber = $info->gid;
394 my $gid = getgrgid($gidnumber) || $gidnumber;
395 push(
396 @permlist,
397 sprintf( "%-55s %-17s %4d",
398 $file, "${uid}:${gid}", $modestring )
399 );
400 }
401 }
402 return @permlist;
403}
404
405sub get_type
406{
407
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
417
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;
427}
428
429sub run_command
430{
431 my $command = shift;
432
433 #print "executing command: " . $command . "\n";
434
435 open( RESULT, $command . ' 2>&1 |' );
436 my @result = <RESULT>;
437 close(RESULT);
438 my $retcode = $? >> 8;
439
440 #print @result;
441 #if( $retcode ) { print "return code: " . $retcode . "\n"; }
442
443 return ( $retcode, @result );
444}
445
446sub run_interactive
447{
448
449 if ($verbose) {
450 print "run_interactive:" . join( " ", @_ ) . "\n";
451 }
452
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
465sub svn_check_credentials( $$;$$ )
466{
467 my $username = shift;
468 my $password = shift;
469
470 # check silently are allow user interaction?
471 my $interactive = shift || 0;
472
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
479 if ( !$username ) {
480 fatalerror("no username given");
481 }
482
483 if ( !$password ) {
484 fatalerror("no password given");
485 }
486
487 print "for " . $username . "@" . $DASSCM_SVN_REPOSITORY . ": ";
488
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
497 my $rc_update;
498 if ($interactive) {
499 $rc_update =
500 run_interactive(
501 "$SVN ls --no-auth-cache --username '$username' --password '$password' $DASSCM_SVN_REPOSITORY"
502 );
503 } else {
504 ( $rc_update, my @result ) =
505 run_command(
506 "$SVN ls --non-interactive --no-auth-cache --username '$username' --password '$password' $DASSCM_SVN_REPOSITORY"
507 );
508
509 if ( $rc_update != 0 ) {
510 print "\n", @result;
511 if ($fatalerror) {
512 fatalerror();
513 }
514 return;
515 }
516 }
517
518 # return success
519 return $rc_update == 0;
520}
521
522sub svn_update( ;$ )
523{
524 my $update_path = shift || "";
525
526 # use this flag to do only one update per run
527 if ( !$svnRepositoryIsUptodate ) {
528 ( my $rc_update, my @result ) =
529 run_command(
530 "$SVN update --non-interactive $svnCheckoutCredentials '$DASSCM_REPO/$update_path'"
531 );
532 print @result;
533 if ( $rc_update != 0 ) {
534 error( "failed to update local repository ($update_path)" );
535 } elsif ( not $update_path ) {
536
537 # set this flag if a full update is done
538 $svnRepositoryIsUptodate = 1;
539 }
540 }
541}
542
543sub svn_ls( ;@ )
544{
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] );
553
554 # svn ls -R is better, but much, much slower
555 # ( my $rc, my @result ) = run_command("$SVN ls --recursive $svnCheckoutCredentials $path");
556
557 my @files = ();
558 my @links = ();
559 my @dirs = ();
560 my @others = ();
561
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/ ) {
572
573 # skip svn meta data
574 } elsif ( -l $_ ) {
575
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 $_ ) {
586
587 # regular file
588 push( @files, $name );
589 } else {
590 push( @others, $name );
591 }
592 }
593 },
594 ($filename_repo)
595 );
596
597 return ( sort( @dirs, @files ) );
598}
599
600
601
602sub svn_revert( ;$ )
603{
604 my $path = shift || $DASSCM_REPO;
605
606 ( my $rc_update, my @result ) = run_command("$SVN revert -R '$path'");
607
608 if ( $rc_update != 0 ) {
609 print "\n", @result;
610 error("failed to revert subversion repository changes");
611 }
612}
613
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
636sub getModifiedFiles( ;$ )
637{
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] );
646
647 my @files = svn_ls($filename_prod);
648
649 # stores result from status (cvscheck)
650 my %removedfiles = ();
651 my %changedfiles = ();
652 my %unknownfiles = ();
653
654 # create list of modified files
655 if (@files) {
656
657 foreach my $file (@files) {
658
659 my $realfile = $dirname_prod . $file;
660 my $cvsworkfile = $dirname_repo . $file;
661
662 if ( -d $realfile ) {
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;
670 } elsif ( !-r $realfile ) {
671 # don't have permission to read the file,
672 # can't check it
673 $unknownfiles{"$realfile"} = $cvsworkfile;
674 } else {
675 ( -r "$cvsworkfile" )
676 || fatalerror("failed to read $cvsworkfile");
677 if ( compare( $cvsworkfile, $realfile ) != 0 ) {
678 $changedfiles{"$realfile"} = $cvsworkfile;
679 }
680 }
681 }
682 }
683
684 return ( \%changedfiles, \%removedfiles, \%unknownfiles );
685}
686
687#
688# from an array of files/dirs,
689# generates list of files
690# sorted by type
691#
692sub get_files( @ )
693{
694 my @files = ();
695 my @links = ();
696 my @dirs = ();
697 my @others = ();
698
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 $_ ) {
711
712 # directories
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 );
725 }
726
727 # don't rely on others.
728 # If more specific file types are needed,
729 # they will be added
730 return {
731 files => \@files,
732 links => \@links,
733 dirs => \@dirs,
734 others => \@others
735 };
736}
737
738#####################################################################
739#
740# functions
741
742sub help(;@)
743{
744 if ( @_ == 0 ) {
745 usage();
746 } else {
747 print "help for @_: ...\n";
748 usage();
749 }
750}
751
752sub login(@)
753{
754 check_parameter( @_, 1 );
755 check_env();
756
757 my $input_username = $_[0];
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);
768
769 $input_username = $input_username || $DASSCM_USERNAME;
770 }
771
772 # hidden password input
773 print "Enter password for $input_username: ";
774 ReadMode('noecho');
775 my $input_password = <STDIN>;
776 ReadMode('normal');
777 chomp($input_password);
778 print "\n";
779
780 # checking checkout username/password
781 svn_check_credentials( $DASSCM_CHECKOUT_USERNAME,
782 $DASSCM_CHECKOUT_PASSWORD );
783 print "checkout access okay\n";
784
785 svn_check_credentials( $input_username, $input_password );
786
787 #
788 # set environment variables
789 #
790 $ENV{'DASSCM_USERNAME'} = "$input_username";
791 $ENV{'DASSCM_PASSWORD'} = "$input_password";
792
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",
796 "Server Repository: $DASSCM_SVN_REPOSITORY\n", "\n";
797
798 status();
799
800 print "\n[dasscm shell]\n\n";
801 my $shell = $SHELL || "bash";
802 exec($shell) or die "failed to start new shell";
803}
804
805#
806# initialize local checkout directory (initial checkout)
807#
808sub init(@)
809{
810 check_parameter( @_, 1 );
811 check_env();
812
813 # don't do repository creation (svn mkdir) here,
814 # because then their must be a lot of prior checks
815
816 # update complete repository
817 # and create permission file
818 my $retcode =
819 run_interactive(
820 "cd $DASSCM_LOCAL_REPOSITORY_BASE; $SVN checkout $svnCheckoutCredentials $svnOptions $DASSCM_SVN_REPOSITORY; mkdir -p `dirname $DASSCM_PERMISSION_FILE`; touch $DASSCM_PERMISSION_FILE"
821 );
822}
823
824sub ls(@)
825{
826 check_parameter( @_, 1 );
827 check_env();
828
829 my @files = svn_ls(@_);
830
831 if (@files) {
832 print join( "\n", @files );
833 print "\n";
834 }
835}
836
837sub update(@)
838{
839 check_parameter( @_, 1 );
840 check_env();
841
842 #
843 # update local repository
844 #
845 svn_update();
846}
847
848#
849# helper function for "add" command
850#
851sub add_helper(@)
852{
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] );
861
862 mkpath($dirname_repo);
863
864 # TODO: are permissions also copied?
865 copy( $filename_prod, $filename_repo )
866 or error "failed to copy $filename_prod to repository: $!";
867
868 # already checked in?
869 chdir $DASSCM_REPO;
870
871 # also add the path to filename.
872 for my $dir ( split( '/', $dirname_prod ) ) {
873 if ($dir) {
874 my ( $rc, @out ) = run_command("$SVN add --non-recursive '$dir'");
875 if ( $rc > 0 ) {
876 print join( "\n", @out );
877 }
878 chdir $dir;
879 }
880 }
881 my ( $rc, @out ) = run_command("$SVN add '$basename'");
882 if ( $rc > 0 ) {
883 print join( "\n", @out );
884 }
885 chdir $StartDirectory;
886
887}
888
889#
890# adding new files (or directories)
891#
892sub add(@)
893{
894 check_parameter( @_, 1 );
895 check_env();
896
897 #
898 # update local repository
899 #
900 svn_update();
901
902 # get all regular files and links
903 my $href_files = get_files(@_);
904
905 #print Dumper( $href_files );
906
907 my @files = @{ $href_files->{files} };
908 my @links = @{ $href_files->{links} };
909
910 if (@files) {
911 my $number = $#files + 1;
912 print "files to check-in ($number): \n";
913 print join( "\n", @files );
914 print "\n";
915 }
916
917 # TODO: check in links and also link target? At least warn about link target
918 if (@links) {
919 my $number = $#links + 1;
920 print "\n";
921 print "ignoring links ($number):\n";
922 print join( "\n", @links );
923 print "\n";
924 }
925
926 # TODO: confirm
927
928 # copy files one by one to local repository
929 for my $file (@files) {
930
931 # add file
932 add_helper($file);
933 }
934
935 # create new permissions file
936 permissions();
937
938 # add permissions file
939 add_helper($DASSCM_PERMISSION_FILE);
940
941 if ( $options{'message'} ) {
942 $svnOptions .= " --message \"$options{'message'}\" ";
943 }
944
945 # commit calls $EDITOR.
946 # use "interactive" here, to display output
947 my $retcode =
948 run_interactive(
949 "$SVN commit $svnOptions --username '$DASSCM_USERNAME' $svnPasswordCredentials $DASSCM_REPO"
950 );
951
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();
955}
956
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
971 )
972 = get_filenames( $_[0] );
973
974 #
975 # update local repository
976 #
977 svn_update();
978
979 ( my $refChangedFiles, my $refRemovedFiles ) =
980 getModifiedFiles($filename_prod);
981 my %changedfiles = %{$refChangedFiles};
982 my %removedfiles = %{$refRemovedFiles};
983
984 if (%removedfiles) {
985 my $removedFilesString =
986 '"' . join( '" "', values(%removedfiles) ) . '"';
987 my ( $rc, @out ) = run_command("$SVN rm $removedFilesString");
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) ) {
995 copy_file_to_repository($file);
996 }
997
998 # create new permissions file
999 permissions();
1000
1001 # add permissions file
1002 add_helper($DASSCM_PERMISSION_FILE);
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 );
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();
1018}
1019
1020sub blame(@)
1021{
1022 check_parameter( @_, 1 );
1023 check_env();
1024
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");
1035}
1036
1037sub diff(@)
1038{
1039 check_parameter( @_, 1 );
1040 check_env();
1041
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
1059 ( my $rc_diff, my @diff_result ) =
1060 run_command( $diff . " $filename_repo $filename_prod" );
1061
1062 print @diff_result;
1063}
1064
1065sub status(@)
1066{
1067 check_parameter( @_, 1 );
1068 check_env();
1069
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
1079 # return code for the shell
1080 # default: error
1081 my $return_code = $RETURN_NOK;
1082
1083 #
1084 # update local repository
1085 #
1086 #svn_update( $filename_prod );
1087
1088 # check, if permissions have changed
1089 permissions();
1090
1091 # get modified files
1092 ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) =
1093 getModifiedFiles($dirname_prod);
1094 my %changedfiles = %{$refChangedFiles};
1095 my %removedfiles = %{$refRemovedFiles};
1096 my %unknownfiles = %{$refUnknownFiles};
1097
1098 if ( %removedfiles or %changedfiles or %unknownfiles ) {
1099
1100 if (%removedfiles) {
1101 print "DELETED: files found in repository, but not in system:\n";
1102 print join( "\n", ( keys %removedfiles ) ) . "\n\n";
1103 }
1104
1105 if (%changedfiles) {
1106 print "MODIFIED: files differs between repository and system:\n";
1107 print join( "\n", ( keys %changedfiles ) ) . "\n\n";
1108 }
1109
1110 if (%unknownfiles) {
1111 print "UNKNOWN: insufficient permission to check files:\n";
1112 print join( "\n", ( keys %unknownfiles ) ) . "\n\n";
1113 }
1114
1115 } else {
1116 print "no modified files found in $dirname_repo\n";
1117 $return_code = $RETURN_OK;
1118 }
1119
1120 return $return_code;
1121}
1122
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
1134 # check, if permissions have changed
1135 permissions();
1136
1137 # get modified files
1138 ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) =
1139 getModifiedFiles( "/" );
1140 my %changedfiles = %{$refChangedFiles};
1141 my %removedfiles = %{$refRemovedFiles};
1142 my %unknownfiles = %{$refUnknownFiles};
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 }
1152 if (%unknownfiles) {
1153 $return_string .= "unknown: " . join( ", ", ( keys %unknownfiles ) ) . ". ";
1154 }
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
1167sub permissions()
1168{
1169 check_env();
1170
1171 my $return_code = $RETURN_OK;
1172
1173 #
1174 # update local repository
1175 #
1176 #svn_update();
1177
1178 my $dir = $DASSCM_REPO;
1179 my @files = svn_ls("/");
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
1187
1188 if ( -w dirname($DASSCM_PERMISSION_FILE) ) {
1189
1190 # Verzeichnis existiert => schreiben
1191 open( OUTFILE, ">$DASSCM_PERMISSION_FILE" )
1192 || die("failed to write to $DASSCM_PERMISSION_FILE: $!");
1193 $tofile = 1; # Merken, daß in File geschrieben wird
1194 print OUTFILE "#\n";
1195 print OUTFILE "# created by dasscm permissions\n";
1196 print OUTFILE
1197 "# It is intended to be used for restoring permissions\n";
1198 print OUTFILE "#\n";
1199 } else {
1200
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 }
1211 }
1212
1213 foreach my $line (@permissions) {
1214 print OUTFILE "$line\n";
1215 }
1216
1217 if ($tofile) {
1218 close(OUTFILE);
1219 }
1220 }
1221
1222 return $return_code;
1223}
1224
1225#
1226# remove all uncommited changes in the repository
1227#
1228sub cleanup()
1229{
1230 check_env();
1231
1232 svn_revert($DASSCM_REPO);
1233 svn_remove_unknown_files($DASSCM_REPO);
1234}
1235
1236#####################################################################
1237#
1238# main
1239#
1240
1241my $return_code = $RETURN_OK;
1242my $number_arguments = @ARGV;
1243
1244if ( $number_arguments > 0 ) {
1245
1246 # get subcommand and remove it from @ARGV
1247 $command = $ARGV[0];
1248 shift @ARGV;
1249
1250 $DASSCM_LOCAL_REPOSITORY_BASE = $config->{'DASSCM_LOCAL_REPOSITORY_BASE'};
1251 $DASSCM_REPOSITORY_NAME = $config->{'DASSCM_REPOSITORY_NAME'};
1252
1253 # TODO: check variables
1254 $DASSCM_SVN_REPOSITORY =
1255 $config->{'DASSCM_SVN_REPOSITORY_BASE'} . "/" . $DASSCM_REPOSITORY_NAME;
1256
1257 $DASSCM_CHECKOUT_USERNAME = $config->{'DASSCM_CHECKOUT_USERNAME'};
1258 $DASSCM_CHECKOUT_PASSWORD = $config->{'DASSCM_CHECKOUT_PASSWORD'};
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
1273 $DASSCM_PERMISSION_FILE = $config->{'DASSCM_PERMISSION_FILE'} || "/etc/permissions.d/dasscm.permission_backup";
1274
1275 # get command line options and store them in options hash
1276 my $result = GetOptions( \%options, 'verbose', 'message=s' );
1277
1278 # print options
1279 foreach my $option ( keys %options ) {
1280 print "${option}: $options{$option}\n";
1281 }
1282
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 #
1291 $_ = $command;
1292 if (m/^help$/i) {
1293 help(@ARGV);
1294 } elsif (m/^login$/i) {
1295 $command = "login";
1296 login(@ARGV);
1297 } elsif (m/^init$/i) {
1298 $command = "init";
1299 init(@ARGV);
1300 } elsif (m/^ls$/i) {
1301 $command = "ls";
1302 ls(@ARGV);
1303 } elsif ( (m/^update$/i) || (m/^up$/i) ) {
1304 $command = "update";
1305 update(@ARGV);
1306 } elsif (m/^add$/i) {
1307 $command = "add";
1308 add(@ARGV);
1309 } elsif ( (m/^commit$/i) || (m/^checkin$/i) || (m/^ci$/i) ) {
1310 $command = "commit";
1311 commit(@ARGV);
1312 } elsif (m/^blame$/i) {
1313 $command = "blame";
1314 blame(@ARGV);
1315 } elsif (m/^diff$/i) {
1316 $command = "diff";
1317 diff(@ARGV);
1318 } elsif ( (m/^status$/i) || (m/^st$/i) ) {
1319 $command = "status";
1320 $return_code = status(@ARGV);
1321 } elsif (m/^check$/i) {
1322 $command = "check";
1323 $return_code = check();
1324 } elsif (m/^permissions$/i) {
1325 $command = "permissions";
1326 $return_code = permissions();
1327 } elsif (m/^cleanup$/i) {
1328 $command = "cleanup";
1329 cleanup();
1330 } else {
1331 print "unknown command: $command\n\n";
1332 usage();
1333 check_env();
1334 $return_code = $RETURN_NOK;
1335 }
1336
1337 # revert
1338
1339}
1340
1341exit $return_code;
Note: See TracBrowser for help on using the repository browser.