source: dasscm/trunk/dasscm@ 801

Last change on this file since 801 was 801, checked in by joergs, on Nov 10, 2009 at 6:05:01 PM

cleanup before revert, perltidy

  • Property keyword set to id
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 35.9 KB
Line 
1#!/usr/bin/perl -w
2
3# $Id: dasscm 801 2009-11-10 17:05:01Z 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 801 2009-11-10 17:05:01Z 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
339 # uses chdir to determine real directory in a unique way
340 chdir $dirname_prod
341 or fatalerror( "failed to access directory $dirname_prod: " . $! );
342 $dirname_prod = normalize_path( cwd() );
343 chdir $StartDirectory;
344 }
345
346 my $dirname_repo = normalize_path( $DASSCM_REPO . "/" . $dirname_prod );
347 my $filename_repo = normalize_path("$dirname_repo/$basename");
348
349 if ($verbose) {
350 print "filename_repo: " . $filename_repo . "\n";
351 print "dirname_repo: " . $dirname_repo . "\n";
352 print "filename_prod: " . $filename_prod . "\n";
353 print "dirname_prod: " . $dirname_prod . "\n";
354 print "basename: " . $basename . "\n";
355 }
356
357 return (
358 $basename, $dirname_prod, $dirname_repo,
359 $filename_prod, $filename_repo
360 );
361}
362
363sub copy_file_to_repository( $ )
364{
365 my $filename = shift;
366
367 (
368 my $basename,
369 my $dirname_prod,
370 my $dirname_repo,
371 my $filename_prod,
372 my $filename_repo
373 ) = get_filenames($filename);
374
375 # TODO: are permissions also copied?
376 copy( $filename_prod, $filename_repo )
377 or error "failed to copy $filename_prod to repository: $!";
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 = run_interactive(
504 "$SVN ls --no-auth-cache --username '$username' --password '$password' $DASSCM_SVN_REPOSITORY"
505 );
506 } else {
507 ( $rc_update, my @result ) = run_command(
508 "$SVN ls --non-interactive --no-auth-cache --username '$username' --password '$password' $DASSCM_SVN_REPOSITORY"
509 );
510
511 if ( $rc_update != 0 ) {
512 print "\n", @result;
513 if ($fatalerror) {
514 fatalerror();
515 }
516 return;
517 }
518 }
519
520 # return success
521 return $rc_update == 0;
522}
523
524sub svn_update( ;$ )
525{
526 my $update_path = shift || "";
527
528 # return value
529 my $update_ok = 1;
530
531 # use this flag to do only one update per run
532 if ( !$svnRepositoryIsUptodate ) {
533 ( my $rc_update, my @result ) = run_command(
534 "$SVN update --non-interactive $svnCheckoutCredentials '$DASSCM_REPO/$update_path'"
535 );
536 print @result;
537 if ( $rc_update != 0 ) {
538 error("failed to update local repository ($update_path)");
539 $update_ok = 0;
540 } elsif ( not $update_path ) {
541
542 # set this flag if a full update is done
543 $svnRepositoryIsUptodate = 1;
544 }
545 }
546 return $update_ok;
547}
548
549sub svn_ls( ;@ )
550{
551 (
552 my $basename,
553 my $dirname_prod,
554 my $dirname_repo,
555 my $filename_prod,
556 my $filename_repo
557 ) = get_filenames( $_[0] );
558
559 # svn ls -R is better, but much, much slower
560 # ( my $rc, my @result ) = run_command("$SVN ls --recursive $svnCheckoutCredentials $path");
561
562 my @files = ();
563 my @links = ();
564 my @dirs = ();
565 my @others = ();
566
567 find(
568 {
569 wanted => sub {
570 my $name = normalize_path($File::Find::name);
571 $name =~ s|^$dirname_repo||;
572
573 #print "($name)\n";# . $File::Find::dir . "\n";
574 if ( not $name ) {
575
576 # name string is empty (top directory).
577 # do nothing
578 } elsif ( $name =~ m/\.svn/ ) {
579
580 # skip svn meta data
581 } elsif ( -l $_ ) {
582
583 # soft link
584 # important: check for links first
585 # to exclude them from further checks
586 push( @links, $name );
587 } elsif ( -d $_ ) {
588
589 # directories
590 push( @dirs, $name );
591 } elsif ( -f $_ ) {
592
593 # regular file
594 push( @files, $name );
595 } else {
596 push( @others, $name );
597 }
598 }
599 },
600 ($filename_repo)
601 );
602
603 return ( sort( @dirs, @files ) );
604}
605
606sub svn_revert( ;$ )
607{
608 my $path = shift || $DASSCM_REPO;
609
610 ( my $rc_update, my @result ) = run_command("$SVN revert -R '$path'");
611
612 if ( $rc_update != 0 ) {
613 print "\n", @result;
614 error("failed to revert subversion repository changes");
615 }
616}
617
618sub svn_remove_unknown_files( ;$ )
619{
620 my $path = shift || $DASSCM_REPO;
621
622 ( my $rc_update, my @result ) = run_command("$SVN status '$path'");
623
624 if ( $rc_update != 0 ) {
625 print "\n", @result;
626 error("failed to receive subversion repository information");
627 } else {
628 foreach (@result) {
629 if (s/^\? +//) {
630 chomp;
631
632 # if file is unknown to subversion (line starts with "?")
633 # remove it
634 print "removing $_\n";
635
636 # unlink doesn't work recursive, there "rm -rf" is used
637 #unlink($_);
638 system("rm -rf $_");
639 }
640 }
641 }
642}
643
644sub getModifiedFiles( ;$ )
645{
646 (
647 my $basename,
648 my $dirname_prod,
649 my $dirname_repo,
650 my $filename_prod,
651 my $filename_repo
652 ) = get_filenames( $_[0] );
653
654 my @files = svn_ls($filename_prod);
655
656 # stores result from status (cvscheck)
657 my %removedfiles = ();
658 my %changedfiles = ();
659 my %unknownfiles = ();
660
661 # create list of modified files
662 if (@files) {
663
664 foreach my $file (@files) {
665
666 my $realfile = $dirname_prod . $file;
667 my $cvsworkfile = $dirname_repo . $file;
668
669 if ( -d $realfile ) {
670
671 # directory
672 if ( !-d "$cvsworkfile" ) {
673
674 # real is directory, repository is not. This is a problem
675 $changedfiles{"$realfile"} = $cvsworkfile;
676 }
677 } elsif ( !-e $realfile ) {
678 $removedfiles{"$realfile"} = $cvsworkfile;
679 } elsif ( !-r $realfile ) {
680
681 # don't have permission to read the file,
682 # can't check it
683 $unknownfiles{"$realfile"} = $cvsworkfile;
684 } else {
685 ( -r "$cvsworkfile" )
686 || fatalerror("failed to read $cvsworkfile");
687 if ( compare( $cvsworkfile, $realfile ) != 0 ) {
688 $changedfiles{"$realfile"} = $cvsworkfile;
689 }
690 }
691 }
692 }
693
694 return ( \%changedfiles, \%removedfiles, \%unknownfiles );
695}
696
697#
698# from an array of files/dirs,
699# generates list of files
700# sorted by type
701#
702sub get_files( @ )
703{
704 my @files = ();
705 my @links = ();
706 my @dirs = ();
707 my @others = ();
708
709 if (@_) {
710 find(
711 {
712 wanted => sub {
713 my $fullname = cwd() . "/" . $_;
714 if ( -l $_ ) {
715
716 # soft link
717 # important: check for links first
718 # to exclude them from further checks
719 push( @links, $fullname );
720 } elsif ( -d $_ ) {
721
722 # directories
723 push( @dirs, $fullname );
724 } elsif ( -f $_ ) {
725
726 # regular file
727 push( @files, $fullname );
728 } else {
729 push( @others, $fullname );
730 }
731 }
732 },
733 @_
734 );
735 }
736
737 # don't rely on others.
738 # If more specific file types are needed,
739 # they will be added
740 return {
741 files => \@files,
742 links => \@links,
743 dirs => \@dirs,
744 others => \@others
745 };
746}
747
748#####################################################################
749#
750# functions
751
752sub help(;@)
753{
754 if ( @_ == 0 ) {
755 usage();
756 } else {
757 print "help for @_: ...\n";
758 usage();
759 }
760}
761
762sub login(@)
763{
764 check_parameter( @_, 1 );
765 check_env();
766
767 my $input_username = $_[0];
768
769 if ( not $input_username ) {
770 my $output_username = "";
771 if ($DASSCM_USERNAME) {
772 $output_username = " ($DASSCM_USERNAME)";
773 }
774
775 print "Enter DASSCM user name", $output_username, ": ";
776 $input_username = <STDIN>;
777 chomp($input_username);
778
779 $input_username = $input_username || $DASSCM_USERNAME;
780 }
781
782 # hidden password input
783 print "Enter password for $input_username: ";
784 ReadMode('noecho');
785 my $input_password = <STDIN>;
786 ReadMode('normal');
787 chomp($input_password);
788 print "\n";
789
790 # checking checkout username/password
791 svn_check_credentials( $DASSCM_CHECKOUT_USERNAME,
792 $DASSCM_CHECKOUT_PASSWORD );
793 print "checkout access okay\n";
794
795 svn_check_credentials( $input_username, $input_password );
796
797 #
798 # set environment variables
799 #
800 $ENV{'DASSCM_USERNAME'} = "$input_username";
801 $ENV{'DASSCM_PASSWORD'} = "$input_password";
802
803 print "subversion access okay\n\n", "DASSCM_USERNAME: $input_username\n",
804 "DASSCM_PASSWORD: (hidden)\n", "DASSCM_PROD: $DASSCM_PROD\n",
805 "DASSCM_REPO: $DASSCM_REPO\n",
806 "Server Repository: $DASSCM_SVN_REPOSITORY\n", "\n";
807
808 status();
809
810 print "\n[dasscm shell]\n\n";
811 my $shell = $SHELL || "bash";
812 exec($shell) or die "failed to start new shell";
813}
814
815#
816# initialize local checkout directory (initial checkout)
817#
818sub init(@)
819{
820 check_parameter( @_, 1 );
821 check_env();
822
823 # don't do repository creation (svn mkdir) here,
824 # because then their must be a lot of prior checks
825
826 # update complete repository
827 # and create permission file
828 my $retcode = run_interactive(
829 "cd $DASSCM_LOCAL_REPOSITORY_BASE; $SVN checkout $svnCheckoutCredentials $svnOptions $DASSCM_SVN_REPOSITORY; mkdir -p `dirname $DASSCM_PERMISSION_FILE`; touch $DASSCM_PERMISSION_FILE"
830 );
831}
832
833sub ls(@)
834{
835 check_parameter( @_, 1 );
836 check_env();
837
838 my @files = svn_ls(@_);
839
840 if (@files) {
841 print join( "\n", @files );
842 print "\n";
843 }
844}
845
846sub update(@)
847{
848 check_parameter( @_, 1 );
849 check_env();
850
851 #
852 # update local repository
853 #
854 svn_update();
855}
856
857#
858# helper function for "add" command
859#
860sub add_helper(@)
861{
862 (
863 my $basename,
864 my $dirname_prod,
865 my $dirname_repo,
866 my $filename_prod,
867 my $filename_repo
868 ) = get_filenames( $_[0] );
869
870 mkpath($dirname_repo);
871
872 # TODO: are permissions also copied?
873 copy( $filename_prod, $filename_repo )
874 or error "failed to copy $filename_prod to repository: $!";
875
876 # already checked in?
877 chdir $DASSCM_REPO;
878
879 # also add the path to filename.
880 for my $dir ( split( '/', $dirname_prod ) ) {
881 if ($dir) {
882 my ( $rc, @out ) = run_command("$SVN add --non-recursive '$dir'");
883 if ( $rc > 0 ) {
884 print join( "\n", @out );
885 }
886 chdir $dir;
887 }
888 }
889 my ( $rc, @out ) = run_command("$SVN add '$basename'");
890 if ( $rc > 0 ) {
891 print join( "\n", @out );
892 }
893 chdir $StartDirectory;
894
895}
896
897#
898# adding new files (or directories)
899#
900sub add(@)
901{
902 check_parameter( @_, 1 );
903 check_env();
904
905 #
906 # update local repository
907 #
908 svn_update();
909
910 # get all regular files and links
911 my $href_files = get_files(@_);
912
913 #print Dumper( $href_files );
914
915 my @files = @{ $href_files->{files} };
916 my @links = @{ $href_files->{links} };
917
918 if (@files) {
919 my $number = $#files + 1;
920 print "files to check-in ($number): \n";
921 print join( "\n", @files );
922 print "\n";
923 }
924
925 # TODO: check in links and also link target? At least warn about link target
926 if (@links) {
927 my $number = $#links + 1;
928 print "\n";
929 print "ignoring links ($number):\n";
930 print join( "\n", @links );
931 print "\n";
932 }
933
934 # TODO: confirm
935
936 # copy files one by one to local repository
937 for my $file (@files) {
938
939 # add file
940 add_helper($file);
941 }
942
943 # create new permissions file
944 permissions();
945
946 # add permissions file
947 add_helper($DASSCM_PERMISSION_FILE);
948
949 if ( $options{'message'} ) {
950 $svnOptions .= " --message \"$options{'message'}\" ";
951 }
952
953 # commit calls $EDITOR.
954 # use "interactive" here, to display output
955 my $retcode = run_interactive(
956 "$SVN commit $svnOptions --username '$DASSCM_USERNAME' $svnPasswordCredentials $DASSCM_REPO"
957 );
958
959 # svn commit does not deliever an error return code, if commit is canceld,
960 # so a revert is performed in any case
961 svn_revert();
962}
963
964#
965# checks in all modified files
966#
967sub commit(@)
968{
969 check_parameter( @_, 1 );
970 check_env();
971
972 (
973 my $basename,
974 my $dirname_prod,
975 my $dirname_repo,
976 my $filename_prod,
977 my $filename_repo
978 ) = get_filenames( $_[0] );
979
980 #
981 # update local repository
982 #
983 svn_update();
984
985 ( my $refChangedFiles, my $refRemovedFiles ) =
986 getModifiedFiles($filename_prod);
987 my %changedfiles = %{$refChangedFiles};
988 my %removedfiles = %{$refRemovedFiles};
989
990 if (%removedfiles) {
991 my $removedFilesString =
992 '"' . join( '" "', values(%removedfiles) ) . '"';
993 my ( $rc, @out ) = run_command("$SVN rm $removedFilesString");
994 if ( $rc > 0 ) {
995 print join( "\n", @out );
996 }
997 }
998
999 # copy files one by one to local repository
1000 for my $file ( keys(%changedfiles) ) {
1001 copy_file_to_repository($file);
1002 }
1003
1004 # create new permissions file
1005 permissions();
1006
1007 # add permissions file
1008 add_helper($DASSCM_PERMISSION_FILE);
1009
1010 if ( $options{'message'} ) {
1011 $svnOptions .= " --message \"$options{'message'}\" ";
1012 }
1013
1014 # commit calls $EDITOR.
1015 # use "interactive" here, to display output
1016 my $retcode = run_interactive(
1017 "$SVN commit $svnOptions --username '$DASSCM_USERNAME' $svnPasswordCredentials $DASSCM_REPO"
1018 );
1019
1020 # svn commit does not deliever an error return code, if commit is canceld,
1021 # so a revert is performed in any case
1022 svn_revert();
1023}
1024
1025#
1026# revert: copies files back from repository to system
1027#
1028sub revert(@)
1029{
1030 check_parameter( @_, 1 );
1031 check_env();
1032
1033 (
1034 my $basename,
1035 my $dirname_prod,
1036 my $dirname_repo,
1037 my $filename_prod,
1038 my $filename_repo
1039 ) = get_filenames( $_[0] );
1040
1041 # return code for the shell
1042 # default: error
1043 my $return_code = $RETURN_OK;
1044
1045 # cleanup repository
1046 cleanup();
1047 #svn_update();
1048
1049 ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) =
1050 getModifiedFiles($filename_prod);
1051 my %changedfiles = %{$refChangedFiles};
1052 my %removedfiles = %{$refRemovedFiles};
1053 my %unknownfiles = %{$refUnknownFiles};
1054
1055 if ( %removedfiles or %changedfiles or %unknownfiles ) {
1056
1057 if (%removedfiles) {
1058 print "DELETED files and directories. Recreated from repository:\n";
1059 my @removedPaths =
1060 ( sort { length $a > length $b } keys %removedfiles );
1061 print join( "\n", @removedPaths ) . "\n\n";
1062
1063 # copy files one by one from local repository to system
1064 # and also create directories
1065 # paths are sorted, so that directories are created first
1066 for my $real_path (@removedPaths) {
1067 if ( -d $removedfiles{"$real_path"} ) {
1068 mkpath("$real_path");
1069 } else {
1070 copy( $removedfiles{"$real_path"}, $real_path )
1071 or error( "failed to copy "
1072 . $removedfiles{"$real_path"} . " to "
1073 . $real_path . ": "
1074 . $! );
1075 }
1076 }
1077 }
1078
1079 if (%changedfiles) {
1080 print "MODIFIED files. Copied from repository to the system:\n";
1081 print join( "\n", ( keys %changedfiles ) ) . "\n\n";
1082
1083 # copy files one by one from local repository to system
1084 for my $real_file ( keys(%changedfiles) ) {
1085 copy( $changedfiles{"$real_file"}, $real_file );
1086 }
1087
1088 }
1089
1090 if (%unknownfiles) {
1091 print "UNKNOWN: insufficient permission to check files:\n";
1092 print join( "\n", ( keys %unknownfiles ) ) . "\n\n";
1093
1094 $return_code = $RETURN_NOK;
1095 }
1096
1097 } else {
1098 print "no modified files found in $dirname_repo\n";
1099 }
1100
1101 return $return_code;
1102}
1103
1104sub blame(@)
1105{
1106 check_parameter( @_, 1 );
1107 check_env();
1108
1109 (
1110 my $basename,
1111 my $dirname_prod,
1112 my $dirname_repo,
1113 my $filename_prod,
1114 my $filename_repo
1115 ) = get_filenames( $_[0] );
1116
1117 my $retcode = run_interactive("$SVN blame $svnOptions $filename_repo");
1118}
1119
1120sub diff(@)
1121{
1122 check_parameter( @_, 1 );
1123 check_env();
1124
1125 (
1126 my $basename,
1127 my $dirname_prod,
1128 my $dirname_repo,
1129 my $filename_prod,
1130 my $filename_repo
1131 ) = get_filenames( $_[0] );
1132
1133 #print "$basename,$dirname_prod,$dirname_repo\n";
1134
1135 svn_update();
1136
1137 ( my $rc_diff, my @diff_result ) =
1138 run_command( $diff . " $filename_repo $filename_prod" );
1139
1140 print @diff_result;
1141}
1142
1143sub status(@)
1144{
1145 check_parameter( @_, 1 );
1146 check_env();
1147
1148 (
1149 my $basename,
1150 my $dirname_prod,
1151 my $dirname_repo,
1152 my $filename_prod,
1153 my $filename_repo
1154 ) = get_filenames( $_[0] || "/" );
1155
1156 # return code for the shell
1157 # default: error
1158 my $return_code = $RETURN_NOK;
1159
1160 #
1161 # update local repository
1162 #
1163 #svn_update( $filename_prod );
1164
1165 # check, if permissions have changed
1166 permissions();
1167
1168 # get modified files
1169 ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) =
1170 getModifiedFiles($dirname_prod);
1171 my %changedfiles = %{$refChangedFiles};
1172 my %removedfiles = %{$refRemovedFiles};
1173 my %unknownfiles = %{$refUnknownFiles};
1174
1175 if ( %removedfiles or %changedfiles or %unknownfiles ) {
1176
1177 if (%removedfiles) {
1178 print "DELETED: files found in repository, but not in system:\n";
1179 print join( "\n", sort ( keys %removedfiles ) ) . "\n\n";
1180 }
1181
1182 if (%changedfiles) {
1183 print "MODIFIED: files differs between repository and system:\n";
1184 print join( "\n", ( keys %changedfiles ) ) . "\n\n";
1185 }
1186
1187 if (%unknownfiles) {
1188 print "UNKNOWN: insufficient permission to check files:\n";
1189 print join( "\n", ( keys %unknownfiles ) ) . "\n\n";
1190 }
1191
1192 } else {
1193 print "no modified files found in $dirname_repo\n";
1194 $return_code = $RETURN_OK;
1195 }
1196
1197 return $return_code;
1198}
1199
1200#
1201# return short status in Nagios plugin conform way
1202#
1203sub check()
1204{
1205 check_env();
1206
1207 # return code for the shell
1208 my $return_code = $RETURN_OK;
1209 my $return_string = "OK: no modified files";
1210
1211 # check, if permissions have changed
1212 permissions();
1213
1214 # get modified files
1215 ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) =
1216 getModifiedFiles("/");
1217 my %changedfiles = %{$refChangedFiles};
1218 my %removedfiles = %{$refRemovedFiles};
1219 my %unknownfiles = %{$refUnknownFiles};
1220
1221 if ( %removedfiles or %changedfiles ) {
1222 $return_string = "Warning: ";
1223 if (%changedfiles) {
1224 $return_string .=
1225 "changed: " . join( ", ", ( keys %changedfiles ) ) . ". ";
1226 }
1227 if (%removedfiles) {
1228 $return_string .=
1229 "removed: " . join( ", ", ( keys %removedfiles ) ) . ". ";
1230 }
1231 if (%unknownfiles) {
1232 $return_string .=
1233 "unknown: " . join( ", ", ( keys %unknownfiles ) ) . ". ";
1234 }
1235 $return_code = $RETURN_WARN;
1236 }
1237
1238 # addition nagios Service Status
1239 #Critical
1240 #Unknown
1241
1242 print "$return_string\n";
1243 return $return_code;
1244}
1245
1246sub permissions()
1247{
1248 check_env();
1249
1250 my $return_code = $RETURN_OK;
1251
1252 #
1253 # update local repository
1254 #
1255 #svn_update();
1256
1257 my $dir = $DASSCM_REPO;
1258 my @files = svn_ls("/");
1259
1260 if (@files) {
1261
1262 # generieren der Permissions
1263 my @permissions = generatePermissionList(@files);
1264 my $OUTFILE;
1265 my $tofile = 0; # Status für schreiben in File
1266
1267 if ( -w dirname($DASSCM_PERMISSION_FILE) ) {
1268
1269 # Verzeichnis existiert => schreiben
1270 open( OUTFILE, ">$DASSCM_PERMISSION_FILE" )
1271 || die("failed to write to $DASSCM_PERMISSION_FILE: $!");
1272 $tofile = 1; # Merken, daß in File geschrieben wird
1273 print OUTFILE "#\n";
1274 print OUTFILE "# created by dasscm permissions\n";
1275 print OUTFILE
1276 "# It is intended to be used for restoring permissions\n";
1277 print OUTFILE "#\n";
1278 } else {
1279
1280 if ( $command eq "permission" ) {
1281
1282 # Pfad für Sicherungsdatei existiert nicht => schreiben auf stdout
1283 # Alias Filehandle für stdout erzeugen
1284 $return_code = $RETURN_WARN;
1285 *OUTFILE = *STDOUT;
1286 } else {
1287
1288 # TODO: improve this. Check for diff?
1289 $return_code = $RETURN_CRIT;
1290 return $return_code;
1291 }
1292 }
1293
1294 foreach my $line (@permissions) {
1295 print OUTFILE "$line\n";
1296 }
1297
1298 if ($tofile) {
1299 close(OUTFILE);
1300 }
1301 }
1302
1303 return $return_code;
1304}
1305
1306#
1307# remove all uncommited changes in the repository
1308#
1309sub cleanup()
1310{
1311 check_env();
1312
1313 svn_revert($DASSCM_REPO);
1314 svn_remove_unknown_files($DASSCM_REPO);
1315}
1316
1317#####################################################################
1318#
1319# main
1320#
1321
1322my $return_code = $RETURN_OK;
1323my $number_arguments = @ARGV;
1324
1325if ( $number_arguments > 0 ) {
1326
1327 # get subcommand and remove it from @ARGV
1328 $command = $ARGV[0];
1329 shift @ARGV;
1330
1331 $DASSCM_LOCAL_REPOSITORY_BASE = $config->{'DASSCM_LOCAL_REPOSITORY_BASE'};
1332 $DASSCM_REPOSITORY_NAME = $config->{'DASSCM_REPOSITORY_NAME'};
1333
1334 # TODO: check variables
1335 $DASSCM_SVN_REPOSITORY =
1336 $config->{'DASSCM_SVN_REPOSITORY_BASE'} . "/" . $DASSCM_REPOSITORY_NAME;
1337
1338 $DASSCM_CHECKOUT_USERNAME = $config->{'DASSCM_CHECKOUT_USERNAME'};
1339 $DASSCM_CHECKOUT_PASSWORD = $config->{'DASSCM_CHECKOUT_PASSWORD'};
1340
1341 #
1342 # if a user is given by dasscm configuration file, we use it.
1343 # Otherwise we expect that read-only account is configured
1344 # as local subversion configuration.
1345 # If this is also not the case,
1346 # user is required to type username and password.
1347 # This will be stored as local subversion configuration thereafter.
1348 #
1349 if ( $DASSCM_CHECKOUT_USERNAME && $DASSCM_CHECKOUT_PASSWORD ) {
1350 $svnCheckoutCredentials =
1351 " --username $DASSCM_CHECKOUT_USERNAME --password $DASSCM_CHECKOUT_PASSWORD ";
1352 }
1353
1354 $DASSCM_PERMISSION_FILE = $config->{'DASSCM_PERMISSION_FILE'}
1355 || "/etc/permissions.d/dasscm.permission_backup";
1356
1357 # get command line options and store them in options hash
1358 my $result = GetOptions( \%options, 'verbose', 'message=s' );
1359
1360 # print options
1361 foreach my $option ( keys %options ) {
1362 print "${option}: $options{$option}\n";
1363 }
1364
1365 # set verbose to command line option
1366 $verbose = $options{'verbose'};
1367
1368 #
1369 # action accordinly to command are taken
1370 # $command is rewritten in standard format,
1371 # so we can test for it later on more simply
1372 #
1373 $_ = $command;
1374 if (m/^help$/i) {
1375 help(@ARGV);
1376 } elsif (m/^login$/i) {
1377 $command = "login";
1378 login(@ARGV);
1379 } elsif (m/^init$/i) {
1380 $command = "init";
1381 init(@ARGV);
1382 } elsif (m/^ls$/i) {
1383 $command = "ls";
1384 ls(@ARGV);
1385 } elsif ( (m/^update$/i) || (m/^up$/i) ) {
1386 $command = "update";
1387 update(@ARGV);
1388 } elsif (m/^add$/i) {
1389 $command = "add";
1390 add(@ARGV);
1391 } elsif ( (m/^commit$/i) || (m/^checkin$/i) || (m/^ci$/i) ) {
1392 $command = "commit";
1393 commit(@ARGV);
1394 } elsif (m/^revert$/i) {
1395 $command = "revert";
1396 $return_code = revert(@ARGV);
1397 } elsif (m/^blame$/i) {
1398 $command = "blame";
1399 blame(@ARGV);
1400 } elsif (m/^diff$/i) {
1401 $command = "diff";
1402 diff(@ARGV);
1403 } elsif ( (m/^status$/i) || (m/^st$/i) ) {
1404 $command = "status";
1405 $return_code = status(@ARGV);
1406 } elsif (m/^check$/i) {
1407 $command = "check";
1408 $return_code = check();
1409 } elsif (m/^permissions$/i) {
1410 $command = "permissions";
1411 $return_code = permissions();
1412 } elsif (m/^cleanup$/i) {
1413 $command = "cleanup";
1414 cleanup();
1415 } else {
1416 print "unknown command: $command\n\n";
1417 usage();
1418 check_env();
1419 $return_code = $RETURN_NOK;
1420 }
1421}
1422
1423exit $return_code;
Note: See TracBrowser for help on using the repository browser.