source: dasscm/trunk/dasscm@ 800

Last change on this file since 800 was 800, checked in by joergs, on Nov 10, 2009 at 6:00:44 PM

added revert

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