source: dasscm/trunk/usr/bin/dasscm

Last change on this file was 1229, checked in by joergs, on Jan 17, 2017 at 4:34:56 PM

dasscm init can accept new certificates permanently again.

  • Property keyword set to id
  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 49.5 KB
RevLine 
[1069]1#!/usr/bin/perl
[186]2
3# $Id: dasscm 1229 2017-01-17 15:34:56Z joergs $
4
[1069]5use warnings;
[186]6use strict;
7
[208]8use Env
[235]9 qw($DASSCM_PROD $DASSCM_REPO $USER $DASSCM_USERNAME $DASSCM_USER $DASSCM_PASSWORD $SHELL);
[186]10use Cwd;
[907]11use Getopt::Long;
[186]12use File::Basename;
[209]13use File::Compare;
[891]14## used system("cp -a"), because File::Copy does not keep permissions
15##use File::Copy;
[237]16use File::Find;
[186]17use File::stat;
18use File::Path;
[930]19## Term::ReadKey (ReadMode('noecho')) replaced by "stty" to reduce dependencies
20##use Term::ReadKey;
[239]21
[894]22use Data::Dumper;
[186]23
[189]24#####################################################################
25#
[186]26# global
[189]27#
[205]28
[253]29# shell exit codes
[252]30my $RETURN_OK = 0;
31my $RETURN_NOK = 1;
32
[277]33# Nagios return codes
[290]34my $RETURN_WARN = 1;
35my $RETURN_CRIT = 2;
[277]36my $RETURN_UNKNOWN = 3;
37
[238]38# documentation file (for usage)
39my $doc_file = "/usr/share/doc/packages/dasscm/dasscm_howto.txt";
40
[884]41my $config_file = "/etc/dasscm.conf";
42my $config = get_config($config_file);
[252]43
[894]44my @OPTIONS_GLOBAL = ( 'help', 'verbose' );
45
[934]46# command called => command definition key
[884]47my %COMMANDS = (
[889]48 'help' => 'help',
49 'login' => 'login',
50 'init' => 'init',
51 'ls' => 'ls',
[1166]52 'log' => 'log',
[889]53 'update' => 'update',
54 'up' => 'update',
55 'add' => 'add',
56 'commit' => 'commit',
57 'checkin' => 'commit',
58 'ci' => 'commit',
59 'revert' => 'revert',
60 'blame' => 'blame',
61 'diff' => 'diff',
62 'status' => 'status',
63 'st' => 'status',
64 'check' => 'check',
65 'permissions' => 'permissions',
66 'cleanup' => 'cleanup',
67 'complete' => 'complete',
68 'complete_path' => 'complete_path',
[887]69 'complete_repopath' => 'complete_repopath',
[916]70 'plugins' => 'plugins',
[884]71);
72
73# desc: description (eg. for usage)
74# params: parameters
75# CMD
76# USER
[891]77# PATH_PROD
78# PATH_REPO
[889]79# require:
[884]80# WRITE commands that require write access (and therefore a login)
81my %COMMAND_DEFINITIONS = (
82 'help' => {
[914]83 'desc' => ["print help and usage information"],
[889]84 'params' => ["CMD"],
[884]85 'function' => \&help,
86 },
87 'login' => {
[914]88 'desc' => ["user login to Subversion repositoty"],
[889]89 'params' => ["USER"],
[884]90 'function' => \&login
91 },
92 'init' => {
[889]93 'desc' => [
94 "initialize local subversion checkout.",
95 "This is the first thing to do (after configuring $config_file)"
96 ],
97 'params' => [],
[884]98 'function' => \&init
99 },
100 'ls' => {
[914]101 'desc' => ["list file from repository"],
[891]102 'params' => ["PATH_REPO"],
[884]103 'function' => \&ls
104 },
[1166]105 'log' => {
106 'desc' => ["show the log messages of commits"],
107 'params' => ["PATH_REPO"],
108 'function' => \&log
109 },
[884]110 'update' => {
[914]111 'desc' => [
112 "update local repository checkout",
113 "Normally, this is done automatically"
114 ],
[891]115 'params' => ["PATH_REPO"],
[884]116 'function' => \&update
117 },
118 'add' => {
[914]119 'desc' => [
120 "add a file to the subversion repository",
121 "Unlike the native svn command,",
122 "dasscm adds and immediatly submits a file to the subversion repository"
123 ],
[891]124 'params' => ["PATH_PROD"],
[893]125 'options' => [ 'verbose', 'message=s' ],
[889]126 'require' => ["WRITE"],
[884]127 'function' => \&add
128 },
129 'commit' => {
[914]130 'desc' => ["commit a changed file to the subversion repository"],
[895]131 ## TODO: only modified files
[891]132 'params' => ["PATH_REPO"],
[893]133 'options' => [ 'verbose', 'message=s' ],
[889]134 'require' => ["WRITE"],
[884]135 'function' => \&commit
136 },
137 'revert' => {
[914]138 'desc' => [
139 "revert local changes back to version from the repository (see diff)"
140 ],
[891]141 'params' => ["PATH_REPO"],
[884]142 'function' => \&revert
143 },
144 'blame' => {
[914]145 'desc' => ['like "svn blame"'],
[891]146 ## TODO: only files from PATH_REPO
147 'params' => ["PATH_REPO"],
[884]148 'function' => \&blame
149 },
150 'diff' => {
[914]151 'desc' => [
152 'display the differences between files on the system and the repository'
153 ],
[891]154 'params' => ["PATH_REPO"],
[884]155 'function' => \&diff
156 },
157 'status' => {
[914]158 'desc' => [
159 'display status information about modified and deleted files.',
160 'If no path is given "/" is assumed',
161 '(in contract to "svn" with assumes ".")'
162 ],
[891]163 'params' => ["PATH_REPO"],
[884]164 'function' => \&status
165 },
166 'check' => {
[889]167 'desc' => ["perform Nagios NRPE conform check"],
168 'params' => [],
[884]169 'function' => \&check
170 },
171 'permissions' => {
[914]172 'desc' =>
[918]173 ["internal, print permissions for all files in the repository"],
[889]174 'params' => [],
[884]175 'function' => \&permissions
176 },
177 'cleanup' => {
[914]178 'desc' => ["internal, used to clean repository checkout"],
[889]179 'params' => [],
[884]180 'function' => \&cleanup
181 },
182 'complete' => {
[914]183 'desc' => ["internal, used for bash completion"],
[889]184 'params' => ["CMD"],
[884]185 'function' => \&complete
186 },
[887]187 'complete_path' => {
[914]188 'desc' => ["internal, used for bash completion"],
[889]189 'params' => [],
[887]190 'function' => \&complete_path
191 },
192 'complete_repopath' => {
[914]193 'desc' => ["internal, used for bash completion"],
[889]194 'params' => [],
[887]195 'function' => \&complete_repopath
196 },
[916]197 'plugins' => {
198 'desc' => ["internal, perform plugins"],
199 'params' => [],
200 'function' => \&perform_plugins
201 },
202
[884]203);
204
[215]205# configuration file
[205]206my $DASSCM_LOCAL_REPOSITORY_BASE;
207my $DASSCM_REPOSITORY_NAME;
[916]208my $DASSCM_PLUGIN_RESULTS_PATH;
[205]209my $DASSCM_SVN_REPOSITORY;
[247]210my $DASSCM_CHECKOUT_USERNAME;
211my $DASSCM_CHECKOUT_PASSWORD;
[922]212my $DASSCM_GID;
[918]213my @DASSCM_ADDITIONAL_FILES;
[205]214
[238]215# current directory at program start
216my $StartDirectory = cwd();
217
[268]218my $diff = "diff --exclude .svn ";
[205]219my $SVN = "svn ";
[1182]220my $svnOptions = "--no-auth-cache";
[205]221my $svnCheckoutCredentials = "";
222my $svnPasswordCredentials = "";
[275]223
[268]224# flag. Set to true by svn_update
225# This prevents, that svn_update is called multiple times
226my $svnRepositoryIsUptodate = 0;
[205]227
[196]228# command line options get stored in options hash
[205]229my %options = ();
230
[197]231# subcommand, that gets executed (add, commit, ...)
[196]232my $command;
[186]233
[205]234my $verbose = 0;
235
[189]236#####################################################################
237#
[186]238# util functions
[189]239#
[187]240sub usage()
241{
[283]242 print '$Id: dasscm 1229 2017-01-17 15:34:56Z joergs $';
243 print "\n\n";
[205]244 print "usage: dasscm <subcommand> [options] [args]\n";
245 print "\n";
246 print "dasscm is intended to help versioning configuration files\n";
247 print "\n";
248 print "Available subcommands:\n";
[889]249 foreach my $i ( sort keys(%COMMAND_DEFINITIONS) ) {
250 print " ", $i, " ", join( " ", get_command_possible_params($i) ),
251 "\n";
[884]252 foreach my $line ( get_command_desc($i) ) {
[889]253 print " " x 20, $line, "\n";
[884]254 }
255 }
[205]256 print "\n";
[800]257 print "If dasscm is not yet configured, read $doc_file\n";
[187]258}
259
[233]260sub warning(@)
261{
262 print "Warning: " . join( "\n ", @_ ) . "\n";
263}
264
265sub error(@)
266{
267 print "Error: " . join( "\n ", @_ ) . "\n";
268}
269
270sub fatalerror(@)
271{
[239]272 error(@_);
273
[233]274 #print "Exiting\n";
[239]275 exit 1;
[233]276}
277
[238]278#
279# reading config file and return key/value pairs as hash
280#
[234]281sub get_config
282{
[239]283 my $file = $_[0];
[234]284
[239]285 if ( !$file ) {
[1166]286 fatalerror( "failed to open config file " . $file );
[234]287 }
288
289 my $data = {};
290
291 # try to open config file
292 if ( !open( FH, $file ) ) {
[1166]293 fatalerror( "failed to open config file " . $file );
[234]294 } else {
295 while (<FH>) {
296 chomp;
297 if (/^#/) {
298 next;
299 }
300 if ( $_ =~ /=/g ) {
[239]301
[238]302 # splitting in 2 fields at maximum
[234]303 my ( $option, $value ) = split( /=/, $_, 2 );
304 $option =~ s/^\s+//g;
305 $option =~ s/\s+$//g;
306 $option =~ s/\"+//g;
307 $value =~ s/^\s+//g;
308 $value =~ s/\s+$//g;
309 $value =~ s/\"+//g;
310
311 if ( length($option) ) {
312 $data->{$option} = $value;
313 }
314 }
315 }
316 }
317 close(FH);
318
319 return $data;
320}
321
[270]322#
323# check and evaluate environment variables
324#
[186]325sub check_env()
326{
[205]327
328 # DASSCM_PROD
329 if ( !$DASSCM_PROD ) {
330 $DASSCM_PROD = "/";
331 }
332
333 if ( !-d $DASSCM_PROD ) {
334 die "DASSCM_PROD ($DASSCM_PROD) is not set to a directory.\n";
335 }
336 if ($verbose) { print "DASSCM_PROD: " . $DASSCM_PROD . "\n"; }
337
338 # DASSCM_REPOSITORY_NAME
[208]339 if ( !$DASSCM_REPOSITORY_NAME ) {
340 die
341 "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";
342 }
[205]343
344 # DASSCM_REPO
345 if ( !$DASSCM_REPO ) {
346 if ( $DASSCM_LOCAL_REPOSITORY_BASE && $DASSCM_REPOSITORY_NAME ) {
347 $DASSCM_REPO =
348 $DASSCM_LOCAL_REPOSITORY_BASE . "/" . $DASSCM_REPOSITORY_NAME;
349 } else {
350 die
351 "Envirnonment variable DASSCM_REPO not set.\nSet DASSCM_REPO to the directory of the versioning system checkout for this machine.\n";
352 }
353 }
[887]354 $DASSCM_REPO = normalize_path($DASSCM_REPO);
[215]355 if ($verbose) { print "DASSCM_REPO: " . $DASSCM_REPO . "\n"; }
[205]356
357 #
[248]358 # subversion checkout user
359 #
[252]360 if ( !$DASSCM_CHECKOUT_USERNAME ) {
361 fatalerror(
[248]362 "variable DASSCM_CHECKOUT_USERNAME is not defined.",
[252]363 "Use file $config_file to configure it."
364 );
[248]365 }
366
[252]367 if ( !$DASSCM_CHECKOUT_PASSWORD ) {
368 fatalerror(
[248]369 "variable DASSCM_CHECKOUT_PASSWORD is not defined.",
[252]370 "Use file $config_file to configure it."
371 );
[248]372 }
373
374 #
[239]375 # check if local repository directory exist
[235]376 # (if not creating by init)
[205]377 #
378 if ( $command ne "init" ) {
379 if ( not -d $DASSCM_REPO ) {
[916]380 fatalerror(
[918]381 "Can't access local repository DASSCM_REPO",
382 "($DASSCM_REPO)",
383 "Check configuration and execute",
384 "dasscm init"
[916]385 );
[205]386 }
[208]387
[205]388 #
389 # user settings
390 #
[208]391
[205]392 # DASSCM_USER is legacy. Use DASSCM_USERNAME instead
[208]393 if ( !$DASSCM_USERNAME ) {
394 $DASSCM_USERNAME = $DASSCM_USER;
[205]395 }
396
397 # user root is not allowed for checkins.
398 # if user is root, DASSCM_USER has to be set,
399 # otherwise USER can be used
400 if ( "$USER" eq "root" ) {
[252]401 if ( ( not $DASSCM_USERNAME )
[889]402 and ( get_command_requires_write($command) ) )
[252]403 {
404
405 #( $command ne "login" ) and ( $command ne "status" ) ) {
406 fatalerror(
407 "Envirnonment variable DASSCM_USERNAME not set.",
408 "Set DASSCM_USERNAME to your subversion user account or",
409 "use 'dasscm login'"
410 );
[205]411 }
[1182]412 #$svnOptions .= " --no-auth-cache ";
[205]413 } elsif ( !$DASSCM_USERNAME ) {
414 $DASSCM_USERNAME = $USER;
415 }
416
417 #
418 # password
419 #
[208]420 if ($DASSCM_PASSWORD) {
[267]421 $svnPasswordCredentials = " --password '$DASSCM_PASSWORD' ";
[205]422 }
423 }
424
425 #$svnOptions .= " --username $DASSCM_USERNAME "
[922]426
427 #
428 # prepare file permissions
429 # (read-write access for group "dasscm",
430 # if this group exists)
431 #
432 (my $gname, my $gpw, $DASSCM_GID, my $members) = getgrnam( "dasscm" );
433 if( $DASSCM_GID ) {
434 umask 0007
435 }
[186]436}
437
[270]438#
439# has been intendend,
440# to check addtitional parameters.
441# Currently not used.
442#
[186]443sub check_parameter(@)
444{
445}
446
[884]447sub get_command_uniform_name( $ )
448{
449 my $command_abbrivation = $_[0];
[889]450 if ( defined( $COMMANDS{$command_abbrivation} ) ) {
[884]451 return $COMMANDS{$command_abbrivation};
452 }
453 return;
454}
455
456sub get_command_desc( $ )
457{
[889]458 my $command = get_command_uniform_name( $_[0] );
459 my @desc = ();
460 if ( $command && defined( $COMMAND_DEFINITIONS{$command}{'desc'} ) ) {
461 @desc = @{ $COMMAND_DEFINITIONS{$command}{'desc'} };
[884]462 }
463 return @desc;
464}
465
466sub get_command_function( $ )
467{
[889]468 my $command = get_command_uniform_name( $_[0] );
[884]469 my $func;
[889]470 if ( $command && defined( $COMMAND_DEFINITIONS{$command}{'function'} ) ) {
[884]471 $func = $COMMAND_DEFINITIONS{$command}{'function'};
472 }
473 return $func;
474}
475
476sub get_command_possible_params( $ )
477{
[889]478 my $command = get_command_uniform_name( $_[0] );
479 my @params = ();
480 if ( $command && defined( $COMMAND_DEFINITIONS{$command}{'params'} ) ) {
481 @params = @{ $COMMAND_DEFINITIONS{$command}{'params'} };
[884]482 }
483 return @params;
484}
485
[893]486sub get_command_possible_options( $ )
487{
488 my $command = get_command_uniform_name( $_[0] );
489 my @params = ();
490 if ( $command && defined( $COMMAND_DEFINITIONS{$command}{'options'} ) ) {
491 @params = @{ $COMMAND_DEFINITIONS{$command}{'options'} };
492 }
493 return @params;
494}
495
[884]496sub get_command_requirements( $ )
497{
[889]498 my $command = get_command_uniform_name( $_[0] );
[884]499 my @requirements = ();
[889]500 if ( $command && defined( $COMMAND_DEFINITIONS{$command}{'require'} ) ) {
501 @requirements = @{ $COMMAND_DEFINITIONS{$command}{'require'} };
[884]502 }
503 return @requirements;
504}
505
506sub get_command_requires_write( $ )
507{
[889]508 return grep( /^WRITE$/, get_command_requirements( $_[0] ) );
[884]509}
510
[238]511#
[287]512# normalize path namens:
513# - directories should end with "/"
514# - use only single "/"
515#
516sub normalize_path($)
517{
518 my $path = shift || "";
519
[290]520 if ( $path =~ m|^/| ) {
521
[287]522 # full path
[290]523 if ( -d $path ) {
524
[287]525 # ensure, a directory ends with '/'
526 $path .= '/';
527 }
[290]528 } elsif ( -d cwd() . '/' . $path ) {
529
530 # ensure, a directory ends with '/'
531 $path .= '/';
[287]532 }
533
534 # remove double (triple) slashes (/)
535 $path =~ s|/[/]*|/|g;
536
[887]537 # remove self reference path
538 $path =~ s|/./|/|g;
539
[287]540 return $path;
541}
542
543#
[239]544# generate from (relative) filename
[238]545# all required file and directory names:
546# $basename, $dirname_prod, $dirname_repo,
547# $filename_prod, $filename_repo
548#
[187]549sub get_filenames(@)
550{
[250]551 my $filename_prod = $_[0] || ".";
[237]552
[238]553 # make filename absolut
[205]554 if ( !( $filename_prod =~ m/^\// ) ) {
[270]555 $filename_prod = cwd() . '/' . $filename_prod;
[205]556 }
[187]557
[889]558 # file must be readable.
559 # The only exceptions are,
[887]560 # - if the file parameter is to be completed or
561 # - if a file should be reverted
562 if ( $command ne "revert" && $command !~ m/^complete/ ) {
[800]563 if ( not -r $filename_prod ) {
564 fatalerror( $filename_prod . " is not accessable" );
565 }
[233]566 }
[205]567
[274]568 # dirname buggy: eg. "/etc/" is reduced to "/",
569 # "/etc" is used as filename
570 # herefore make sure, that if filename is a directory,
571 # it will end by "/"
[290]572 $filename_prod = normalize_path($filename_prod);
[238]573
[270]574 ( my $basename, my $dirname_prod ) = fileparse($filename_prod);
575
[801]576 # normalize path.
[800]577 # not done for reverting, because in this case, the directory may not exist
578 # and the correct path should already be stored in the repository
579 if ( $command ne "revert" ) {
[801]580
[800]581 # uses chdir to determine real directory in a unique way
[801]582 chdir $dirname_prod
583 or fatalerror( "failed to access directory $dirname_prod: " . $! );
[800]584 $dirname_prod = normalize_path( cwd() );
585 chdir $StartDirectory;
586 }
[238]587
[287]588 my $dirname_repo = normalize_path( $DASSCM_REPO . "/" . $dirname_prod );
[290]589 my $filename_repo = normalize_path("$dirname_repo/$basename");
[287]590
[214]591 if ($verbose) {
[287]592 print "filename_repo: " . $filename_repo . "\n";
[290]593 print "dirname_repo: " . $dirname_repo . "\n";
[287]594 print "filename_prod: " . $filename_prod . "\n";
[290]595 print "dirname_prod: " . $dirname_prod . "\n";
[287]596 print "basename: " . $basename . "\n";
[214]597 }
[205]598
599 return (
600 $basename, $dirname_prod, $dirname_repo,
601 $filename_prod, $filename_repo
602 );
[187]603}
604
[271]605sub copy_file_to_repository( $ )
606{
607 my $filename = shift;
[256]608
[271]609 (
610 my $basename,
611 my $dirname_prod,
612 my $dirname_repo,
613 my $filename_prod,
614 my $filename_repo
[801]615 ) = get_filenames($filename);
[271]616
[802]617 #copy( $filename_prod, $filename_repo )
[889]618 ( my $rc, my @result ) =
619 run_command("cp -a \"$filename_prod\" \"$filename_repo\"");
620 if ( $rc != 0 ) {
[802]621 error( "failed to copy $filename_prod to repository: ", @result );
622 }
623
624 # return success
625 return $rc == 0;
[271]626}
627
[802]628sub copy_file_from_repository_to_system( $ )
629{
630 my $filename = shift;
631
632 (
633 my $basename,
634 my $dirname_prod,
635 my $dirname_repo,
636 my $filename_prod,
637 my $filename_repo
638 ) = get_filenames($filename);
639
[889]640 ( my $rc, my @result ) =
641 run_command("cp -a \"$filename_repo\" \"$filename_prod\"");
642 if ( $rc != 0 ) {
[802]643 error( "failed to copy $filename_repo to $filename_prod: ", @result );
644 }
645
646 # return success
647 return $rc == 0;
648}
649
[256]650#
651# creates a file with permissions
652#
[215]653sub generatePermissionList
[209]654{
655
[215]656 # generieren der Zeilen für Permission-Savefile
657 my @files = @_;
658 my @permlist = ();
659 foreach my $file (@files) {
[227]660 $file = "/" . $file;
[239]661 if ( -e $file ) {
662 my $info = stat($file) || die "failed to stat $file: aborting";
663 my $mode = get_type( $info->mode ) & 07777;
[227]664 my $modestring = sprintf( "%04o", $mode );
[278]665 my $uidnumber = $info->uid;
666 my $uid = getpwuid($uidnumber) || $uidnumber;
667 my $gidnumber = $info->gid;
668 my $gid = getgrgid($gidnumber) || $gidnumber;
[227]669 push(
670 @permlist,
671 sprintf( "%-55s %-17s %4d",
[278]672 $file, "${uid}:${gid}", $modestring )
[227]673 );
674 }
[215]675 }
676 return @permlist;
677}
[209]678
[215]679sub get_type
680{
[209]681
[215]682 # Funktion übernommen aus /usr/bin/chkstat
683 my $S_IFLNK = 0120000; # symbolic link
684 my $S_IFREG = 0100000; # regular file
685 my $S_IFDIR = 0040000; # directory
686 my $S_IFCHAR = 0020000; # character device
687 my $S_IFBLK = 0060000; # block device
688 my $S_IFFIFO = 0010000; # fifo
689 my $S_IFSOCK = 0140000; # socket
690 my $S_IFMT = 0170000; # type of file
[209]691
[215]692 my $S_m;
693 if ( ( $_[0] & $S_IFMT ) == $S_IFLNK ) { $S_m = $_[0] - $S_IFLNK; }
694 elsif ( ( $_[0] & $S_IFMT ) == $S_IFREG ) { $S_m = $_[0] - $S_IFREG; }
695 elsif ( ( $_[0] & $S_IFMT ) == $S_IFDIR ) { $S_m = $_[0] - $S_IFDIR; }
696 elsif ( ( $_[0] & $S_IFMT ) == $S_IFCHAR ) { $S_m = $_[0] - $S_IFCHAR; }
697 elsif ( ( $_[0] & $S_IFMT ) == $S_IFBLK ) { $S_m = $_[0] - $S_IFBLK; }
698 elsif ( ( $_[0] & $S_IFMT ) == $S_IFFIFO ) { $S_m = $_[0] - $S_IFFIFO; }
699 elsif ( ( $_[0] & $S_IFMT ) == $S_IFSOCK ) { $S_m = $_[0] - $S_IFSOCK; }
700 $S_m;
[209]701}
702
[186]703sub run_command
704{
[205]705 my $command = shift;
[186]706
[926]707 if ($verbose) {
708 print "executing command: " . $command . "\n";
709 }
[186]710
[926]711 my @result;
712 if( open( RESULT, $command . ' 2>&1 |' ) ) {
713 @result = <RESULT>;
714 close(RESULT);
715 }
[205]716 my $retcode = $? >> 8;
[186]717
[926]718 if ($verbose) {
719 print @result;
720 if( $retcode ) { print "return code: " . $retcode . "\n"; }
721 }
[186]722
[205]723 return ( $retcode, @result );
[186]724}
725
[205]726sub run_interactive
727{
[186]728
[208]729 if ($verbose) {
[205]730 print "run_interactive:" . join( " ", @_ ) . "\n";
731 }
[196]732
[205]733 system(@_);
734 if ( $? == -1 ) {
735 printf "failed to execute: $!\n";
736 } elsif ( $? & 127 ) {
737 printf "child died with signal %d, %s coredump\n", ( $? & 127 ),
738 ( $? & 128 ) ? 'with' : 'without';
739 } elsif ( $? >> 8 != 0 ) {
740 printf "child exited with value %d\n", $? >> 8;
741 }
742 return ( $? >> 8 );
743}
744
[916]745#
746# en- or disable echo mode.
747# used for reading passwords from STDIN
748#
749sub setEchoMode( $ )
750{
751 my $mode = shift;
[918]752 if ($mode) {
753 run_command("stty echo");
[916]754 } else {
[918]755 run_command("stty -echo");
[916]756 }
757}
758
759sub write_array_to_file( $@ )
760{
761 my $filename = shift;
[918]762 my @array = @_;
[916]763
[922]764 if ( -e $filename && !-w $filename ) {
765 warning( "failed to write to $filename:", "permission denied" );
766 return;
767 }
768
[918]769 if ( !-w dirname($filename) ) {
[922]770 warning( "failed to write to $filename:", "directory does not exist" );
[916]771 return;
772 }
773
774 # directory exists => write
[918]775 if ( !open( OUTFILE, ">$filename" ) ) {
776 warning("failed to open $filename: $!");
[916]777 return;
778 }
779
780 foreach my $line (@array) {
781 print OUTFILE "$line";
782 }
783 close(OUTFILE);
784
[922]785 # if group dasscm exists,
786 # create plugin results with group membership dasscm
787 if( $DASSCM_GID ) {
788 chown( -1, $DASSCM_GID, $filename );
789 }
790
[916]791 return 1;
792}
793
794sub perform_plugins()
795{
796 check_env();
797
798 my @plugin_results = ();
799
800 # get all defined plugins.
801 # Plugin definitions starting with DASSCM_PLUGIN_
[918]802 my @plugins = grep( /^DASSCM_PLUGIN_CMD_/, keys( %{$config} ) );
[916]803
804 for my $plugin (@plugins) {
[918]805 my $plugin_name = substr( $plugin, length("DASSCM_PLUGIN_CMD_") );
806 my $plugin_test = $config->{ 'DASSCM_PLUGIN_TEST_' . $plugin_name };
[916]807 if ($verbose) { print "Plugin $plugin_name: "; }
[926]808 # all plugins are executed with LANG settings C
809 # bash -c is used, to supress all output
810 # (otherwise there are problem with && commands)
[1158]811 ( my $rc_test, my @result_test ) = run_command( 'LANG=C LC_ALL=C bash -c "' . $plugin_test . '"' );
[918]812 if ( $rc_test != 0 ) {
[916]813 if ($verbose) { print "skipped\n"; }
814 } else {
815 if ($verbose) { print "$config->{$plugin}\n"; }
[1158]816 ( my $rc, my @result ) = run_command( 'LANG=C LC_ALL=C bash -c "' . $config->{$plugin} . '"' );
[918]817 if ( $rc != 0 ) {
818 warning("failed to run plugin $plugin");
[916]819 } else {
[918]820 my $plugin_result_file =
821 $DASSCM_PLUGIN_RESULTS_PATH . "/" . $plugin_name;
[916]822 write_array_to_file( $plugin_result_file, @result );
823 push @plugin_results, $plugin_result_file;
824 }
825 }
826 }
827 return @plugin_results;
828}
829
[247]830sub svn_check_credentials( $$;$$ )
[196]831{
[205]832 my $username = shift;
833 my $password = shift;
834
[252]835 # check silently are allow user interaction?
[247]836 my $interactive = shift || 0;
[220]837
[247]838 # default: exit program, if repository is not accessable
839 # (do not exit for 'init')
840 my $fatalerror = shift || 1;
841
842 print "checking credentials ";
843
[239]844 if ( !$username ) {
845 fatalerror("no username given");
[238]846 }
847
[239]848 if ( !$password ) {
849 fatalerror("no password given");
[238]850 }
851
[247]852 print "for " . $username . "@" . $DASSCM_SVN_REPOSITORY . ": ";
853
[220]854 # Options for "svn info" are not supported by subversion 1.0.0 (SLES9),
855 # therefore switching to "svn status"
856 # ( my $rc_update, my @result ) =
857 # run_command(
858 # "$SVN info --non-interactive --no-auth-cache --username $username --password $password $DASSCM_SVN_REPOSITORY"
859 # );
860 #print @result;
861
[247]862 my $rc_update;
[252]863 if ($interactive) {
[801]864 $rc_update = run_interactive(
[267]865 "$SVN ls --no-auth-cache --username '$username' --password '$password' $DASSCM_SVN_REPOSITORY"
[801]866 );
[247]867 } else {
[801]868 ( $rc_update, my @result ) = run_command(
[267]869 "$SVN ls --non-interactive --no-auth-cache --username '$username' --password '$password' $DASSCM_SVN_REPOSITORY"
[801]870 );
[252]871
[247]872 if ( $rc_update != 0 ) {
873 print "\n", @result;
[252]874 if ($fatalerror) {
[247]875 fatalerror();
876 }
877 return;
878 }
[205]879 }
880
[247]881 # return success
882 return $rc_update == 0;
[196]883}
884
[205]885sub svn_update( ;$ )
886{
[270]887 my $update_path = shift || "";
888
[797]889 # return value
890 my $update_ok = 1;
891
[270]892 # use this flag to do only one update per run
[275]893 if ( !$svnRepositoryIsUptodate ) {
[801]894 ( my $rc_update, my @result ) = run_command(
[275]895 "$SVN update --non-interactive $svnCheckoutCredentials '$DASSCM_REPO/$update_path'"
[801]896 );
[268]897 print @result;
898 if ( $rc_update != 0 ) {
[290]899 error("failed to update local repository ($update_path)");
[797]900 $update_ok = 0;
[270]901 } elsif ( not $update_path ) {
[275]902
[270]903 # set this flag if a full update is done
[268]904 $svnRepositoryIsUptodate = 1;
905 }
[205]906 }
[797]907 return $update_ok;
[215]908}
[196]909
[271]910sub svn_ls( ;@ )
[215]911{
[270]912 (
913 my $basename,
914 my $dirname_prod,
915 my $dirname_repo,
916 my $filename_prod,
917 my $filename_repo
[801]918 ) = get_filenames( $_[0] );
[220]919
[218]920 # svn ls -R is better, but much, much slower
921 # ( my $rc, my @result ) = run_command("$SVN ls --recursive $svnCheckoutCredentials $path");
[270]922
[271]923 my @files = ();
924 my @links = ();
925 my @dirs = ();
926 my @others = ();
927
[289]928 find(
929 {
930 wanted => sub {
931 my $name = normalize_path($File::Find::name);
932 $name =~ s|^$dirname_repo||;
[290]933
[289]934 #print "($name)\n";# . $File::Find::dir . "\n";
935 if ( not $name ) {
[290]936
[289]937 # name string is empty (top directory).
938 # do nothing
939 } elsif ( $name =~ m/\.svn/ ) {
[275]940
[289]941 # skip svn meta data
942 } elsif ( -l $_ ) {
[275]943
[289]944 # soft link
945 # important: check for links first
946 # to exclude them from further checks
947 push( @links, $name );
948 } elsif ( -d $_ ) {
[290]949
950 # directories
951 push( @dirs, $name );
[289]952 } elsif ( -f $_ ) {
[275]953
[289]954 # regular file
955 push( @files, $name );
956 } else {
957 push( @others, $name );
958 }
[290]959 }
[289]960 },
961 ($filename_repo)
962 );
[271]963
[287]964 return ( sort( @dirs, @files ) );
[205]965}
966
[274]967sub svn_revert( ;$ )
968{
969 my $path = shift || $DASSCM_REPO;
970
[275]971 ( my $rc_update, my @result ) = run_command("$SVN revert -R '$path'");
[274]972
973 if ( $rc_update != 0 ) {
974 print "\n", @result;
[275]975 error("failed to revert subversion repository changes");
[274]976 }
977}
978
[285]979sub svn_remove_unknown_files( ;$ )
980{
981 my $path = shift || $DASSCM_REPO;
982
[290]983 ( my $rc_update, my @result ) = run_command("$SVN status '$path'");
[285]984
985 if ( $rc_update != 0 ) {
986 print "\n", @result;
987 error("failed to receive subversion repository information");
988 } else {
[290]989 foreach (@result) {
990 if (s/^\? +//) {
[285]991 chomp;
[290]992
[285]993 # if file is unknown to subversion (line starts with "?")
994 # remove it
995 print "removing $_\n";
[801]996
[800]997 # unlink doesn't work recursive, there "rm -rf" is used
998 #unlink($_);
[801]999 system("rm -rf $_");
[285]1000 }
1001 }
1002 }
1003}
1004
[268]1005sub getModifiedFiles( ;$ )
1006{
[270]1007 (
1008 my $basename,
1009 my $dirname_prod,
1010 my $dirname_repo,
1011 my $filename_prod,
1012 my $filename_repo
[801]1013 ) = get_filenames( $_[0] );
[268]1014
[275]1015 my @files = svn_ls($filename_prod);
[270]1016
[268]1017 # stores result from status (cvscheck)
1018 my %removedfiles = ();
1019 my %changedfiles = ();
[278]1020 my %unknownfiles = ();
[268]1021
1022 # create list of modified files
1023 if (@files) {
1024
1025 foreach my $file (@files) {
1026
[271]1027 my $realfile = $dirname_prod . $file;
1028 my $cvsworkfile = $dirname_repo . $file;
[268]1029
1030 if ( -d $realfile ) {
[290]1031
[278]1032 # directory
[290]1033 if ( !-d "$cvsworkfile" ) {
1034
[278]1035 # real is directory, repository is not. This is a problem
1036 $changedfiles{"$realfile"} = $cvsworkfile;
1037 }
1038 } elsif ( !-e $realfile ) {
1039 $removedfiles{"$realfile"} = $cvsworkfile;
[268]1040 } elsif ( !-r $realfile ) {
[290]1041
[278]1042 # don't have permission to read the file,
1043 # can't check it
1044 $unknownfiles{"$realfile"} = $cvsworkfile;
[268]1045 } else {
1046 ( -r "$cvsworkfile" )
[278]1047 || fatalerror("failed to read $cvsworkfile");
[268]1048 if ( compare( $cvsworkfile, $realfile ) != 0 ) {
1049 $changedfiles{"$realfile"} = $cvsworkfile;
1050 }
1051 }
1052 }
1053 }
1054
[278]1055 return ( \%changedfiles, \%removedfiles, \%unknownfiles );
[268]1056}
1057
[238]1058#
1059# from an array of files/dirs,
1060# generates list of files
1061# sorted by type
1062#
[237]1063sub get_files( @ )
1064{
1065 my @files = ();
1066 my @links = ();
1067 my @dirs = ();
1068 my @others = ();
1069
[239]1070 if (@_) {
1071 find(
1072 {
1073 wanted => sub {
1074 my $fullname = cwd() . "/" . $_;
1075 if ( -l $_ ) {
1076
1077 # soft link
1078 # important: check for links first
1079 # to exclude them from further checks
1080 push( @links, $fullname );
1081 } elsif ( -d $_ ) {
[271]1082
1083 # directories
[239]1084 push( @dirs, $fullname );
1085 } elsif ( -f $_ ) {
1086
1087 # regular file
1088 push( @files, $fullname );
1089 } else {
1090 push( @others, $fullname );
1091 }
1092 }
1093 },
1094 @_
1095 );
[237]1096 }
1097
[239]1098 # don't rely on others.
[237]1099 # If more specific file types are needed,
1100 # they will be added
1101 return {
[239]1102 files => \@files,
1103 links => \@links,
1104 dirs => \@dirs,
1105 others => \@others
1106 };
[237]1107}
1108
[918]1109sub print_files_hash( $ )
1110{
1111 my $href_files = shift;
1112
1113 my @files = @{ $href_files->{files} };
1114 my @links = @{ $href_files->{links} };
1115
1116 if (@files) {
1117 my $number = $#files + 1;
1118 print "files to check-in ($number): \n";
1119 print join( "\n", @files );
1120 print "\n";
1121 }
1122
1123 # TODO: check in links and also link target? At least warn about link target
1124 if (@links) {
1125 my $number = $#links + 1;
1126 print "\n";
1127 print "ignoring links ($number):\n";
1128 print join( "\n", @links );
1129 print "\n";
1130 }
1131
1132}
1133
[888]1134#
1135# use globbing to get lsit of files
1136# that matches the given prefix
1137# used for bash completion
1138#
1139sub get_complete_path_globbing( $ )
1140{
1141 my $path = shift;
1142
1143 # add globbing
1144 $path .= "*";
1145
1146 # get files
[889]1147 my @files = glob($path);
[888]1148
[889]1149 if ( $#files == 0 ) {
[891]1150
1151 # if only one result is available
1152 # and this result is a directory,
1153 # add another result entry
1154 # (directory with and withour trainling /),
1155 # otherwise complete will stop here and continue with the next parameter
1156 my $path = normalize_path( $files[0] );
[889]1157 if ( -d $path ) {
[891]1158 @files = ( substr( $path, 0, -1 ), $path );
[888]1159 }
[890]1160 } else {
[891]1161
[890]1162 # add "/" to all directories
[891]1163 @files = map( { normalize_path($_) } @files );
[888]1164 }
1165
1166 return @files;
1167}
1168
[189]1169#####################################################################
1170#
[186]1171# functions
1172sub help(;@)
1173{
[893]1174 if ( not @_ ) {
[205]1175 usage();
1176 } else {
[893]1177 print "help for ", join( " ", @_ ), ": ...\n";
[214]1178 usage();
[205]1179 }
[929]1180
1181 return $RETURN_OK;
[186]1182}
1183
[203]1184sub login(@)
1185{
[205]1186 check_parameter( @_, 1 );
1187 check_env();
[203]1188
[235]1189 my $input_username = $_[0];
[214]1190
1191 if ( not $input_username ) {
1192 my $output_username = "";
1193 if ($DASSCM_USERNAME) {
1194 $output_username = " ($DASSCM_USERNAME)";
1195 }
1196
1197 print "Enter DASSCM user name", $output_username, ": ";
1198 $input_username = <STDIN>;
1199 chomp($input_username);
[247]1200
1201 $input_username = $input_username || $DASSCM_USERNAME;
[205]1202 }
[203]1203
[205]1204 # hidden password input
[916]1205 print "Enter password for $input_username: ";
[918]1206 setEchoMode(0);
[205]1207 my $input_password = <STDIN>;
[918]1208 setEchoMode(1);
[205]1209 chomp($input_password);
[220]1210 print "\n";
[203]1211
[247]1212 # checking checkout username/password
[252]1213 svn_check_credentials( $DASSCM_CHECKOUT_USERNAME,
1214 $DASSCM_CHECKOUT_PASSWORD );
[247]1215 print "checkout access okay\n";
[205]1216
[247]1217 svn_check_credentials( $input_username, $input_password );
1218
[205]1219 #
1220 # set environment variables
1221 #
[267]1222 $ENV{'DASSCM_USERNAME'} = "$input_username";
1223 $ENV{'DASSCM_PASSWORD'} = "$input_password";
[205]1224
[209]1225 print "subversion access okay\n\n", "DASSCM_USERNAME: $input_username\n",
1226 "DASSCM_PASSWORD: (hidden)\n", "DASSCM_PROD: $DASSCM_PROD\n",
1227 "DASSCM_REPO: $DASSCM_REPO\n",
[278]1228 "Server Repository: $DASSCM_SVN_REPOSITORY\n", "\n";
[205]1229
[278]1230 status();
1231
1232 print "\n[dasscm shell]\n\n";
[235]1233 my $shell = $SHELL || "bash";
1234 exec($shell) or die "failed to start new shell";
[203]1235}
1236
[260]1237#
1238# initialize local checkout directory (initial checkout)
1239#
[205]1240sub init(@)
1241{
1242 check_parameter( @_, 1 );
1243 check_env();
1244
[235]1245 # don't do repository creation (svn mkdir) here,
1246 # because then their must be a lot of prior checks
1247
[1229]1248 # accept a new certificate
1249 #run_command( "echo "p" | $SVN svn ls $DASSCM_SVN_REPOSITORY" );
1250
1251 #
[205]1252 # update complete repository
[1229]1253 #
1254 # Don't use $svnOptions, as this contains "--no-auth-cache".
1255 # With "--no-auth-cache" set, a certificate can not be accepted permanently,
1256 # only temporary. However, this is required, because otherwise unsequent commands will fail.
1257 # Disadvantage: checkout credentials are stored in local home.
1258 #
[801]1259 my $retcode = run_interactive(
[1229]1260 "cd $DASSCM_LOCAL_REPOSITORY_BASE; $SVN checkout $svnCheckoutCredentials $DASSCM_SVN_REPOSITORY"
[801]1261 );
[929]1262
1263 return $retcode;
[205]1264}
1265
[215]1266sub ls(@)
[186]1267{
[929]1268 my $return_code = $RETURN_OK;
[205]1269 check_parameter( @_, 1 );
1270 check_env();
[186]1271
[271]1272 my @files = svn_ls(@_);
[215]1273
[275]1274 if (@files) {
[274]1275 print join( "\n", @files );
1276 print "\n";
1277 }
[929]1278 return $return_code;
[215]1279}
1280
[1166]1281sub log(@)
1282{
1283 my $return_code = $RETURN_OK;
1284 check_parameter( @_, 1 );
1285 check_env();
1286
1287 check_parameter( @_, 1 );
1288 check_env();
1289
1290 (
1291 my $basename,
1292 my $dirname_prod,
1293 my $dirname_repo,
1294 my $filename_prod,
1295 my $filename_repo
1296 ) = get_filenames( $_[0] );
1297
1298 my $retcode = run_interactive("$SVN log --non-interactive --verbose $svnCheckoutCredentials $svnOptions $filename_repo");
1299 return $retcode;
1300}
1301
[215]1302sub update(@)
1303{
[929]1304 my $return_code = $RETURN_OK;
[215]1305 check_parameter( @_, 1 );
1306 check_env();
1307
1308 #
1309 # update local repository
1310 #
[929]1311 if( ! svn_update() ) {
1312 $return_code = $RETURN_NOK;
1313 }
1314 return $return_code;
[215]1315}
1316
[237]1317#
1318# helper function for "add" command
1319#
[215]1320sub add_helper(@)
1321{
[205]1322 (
1323 my $basename,
1324 my $dirname_prod,
1325 my $dirname_repo,
1326 my $filename_prod,
1327 my $filename_repo
[801]1328 ) = get_filenames( $_[0] );
[186]1329
[274]1330 mkpath($dirname_repo);
[889]1331 copy_file_to_repository($filename_prod);
[186]1332
[274]1333 # already checked in?
1334 chdir $DASSCM_REPO;
[205]1335
[274]1336 # also add the path to filename.
1337 for my $dir ( split( '/', $dirname_prod ) ) {
1338 if ($dir) {
[275]1339 my ( $rc, @out ) = run_command("$SVN add --non-recursive '$dir'");
[274]1340 if ( $rc > 0 ) {
1341 print join( "\n", @out );
[205]1342 }
[274]1343 chdir $dir;
[205]1344 }
1345 }
[275]1346 my ( $rc, @out ) = run_command("$SVN add '$basename'");
[274]1347 if ( $rc > 0 ) {
1348 print join( "\n", @out );
1349 }
1350 chdir $StartDirectory;
1351
[215]1352}
[205]1353
[918]1354sub add_helper_multi(@)
[215]1355{
1356
[237]1357 # get all regular files and links
[239]1358 my $href_files = get_files(@_);
[220]1359
[237]1360 #print Dumper( $href_files );
1361
[239]1362 my @files = @{ $href_files->{files} };
1363 my @links = @{ $href_files->{links} };
[237]1364
1365 # copy files one by one to local repository
1366 for my $file (@files) {
[239]1367
[233]1368 # add file
[239]1369 add_helper($file);
[233]1370 }
1371
[918]1372 return $href_files;
1373}
[220]1374
[918]1375#
1376# adding new files (or directories)
1377#
1378sub add(@)
1379{
1380 check_parameter( @_, 1 );
1381 check_env();
[215]1382
[918]1383 #
1384 # update local repository
1385 #
1386 svn_update();
[916]1387
[918]1388 # add files to repository, print information about added files
1389 print_files_hash( add_helper_multi(@_) );
[916]1390
[918]1391 # perform plugins and add additional files, like plugin results
1392 perform_plugins();
1393 add_helper_multi(@DASSCM_ADDITIONAL_FILES);
1394
[205]1395 if ( $options{'message'} ) {
1396 $svnOptions .= " --message \"$options{'message'}\" ";
1397 }
1398
[239]1399 # commit calls $EDITOR.
[237]1400 # use "interactive" here, to display output
[801]1401 my $retcode = run_interactive(
[267]1402 "$SVN commit $svnOptions --username '$DASSCM_USERNAME' $svnPasswordCredentials $DASSCM_REPO"
[801]1403 );
[205]1404
[274]1405 # svn commit does not deliever an error return code, if commit is canceld,
1406 # so a revert is performed in any case
1407 svn_revert();
[929]1408 return $retcode;
[186]1409}
1410
[271]1411#
1412# checks in all modified files
1413#
1414sub commit(@)
1415{
1416 check_parameter( @_, 1 );
1417 check_env();
1418
1419 (
1420 my $basename,
1421 my $dirname_prod,
1422 my $dirname_repo,
1423 my $filename_prod,
1424 my $filename_repo
[801]1425 ) = get_filenames( $_[0] );
[271]1426
1427 #
1428 # update local repository
1429 #
1430 svn_update();
1431
[275]1432 ( my $refChangedFiles, my $refRemovedFiles ) =
1433 getModifiedFiles($filename_prod);
[271]1434 my %changedfiles = %{$refChangedFiles};
1435 my %removedfiles = %{$refRemovedFiles};
1436
[275]1437 if (%removedfiles) {
1438 my $removedFilesString =
1439 '"' . join( '" "', values(%removedfiles) ) . '"';
1440 my ( $rc, @out ) = run_command("$SVN rm $removedFilesString");
[271]1441 if ( $rc > 0 ) {
1442 print join( "\n", @out );
1443 }
1444 }
1445
1446 # copy files one by one to local repository
1447 for my $file ( keys(%changedfiles) ) {
[275]1448 copy_file_to_repository($file);
[271]1449 }
1450
[918]1451 perform_plugins();
1452 add_helper_multi(@DASSCM_ADDITIONAL_FILES);
[271]1453
1454 if ( $options{'message'} ) {
1455 $svnOptions .= " --message \"$options{'message'}\" ";
1456 }
1457
1458 # commit calls $EDITOR.
1459 # use "interactive" here, to display output
[801]1460 my $retcode = run_interactive(
[271]1461 "$SVN commit $svnOptions --username '$DASSCM_USERNAME' $svnPasswordCredentials $DASSCM_REPO"
[801]1462 );
[274]1463
1464 # svn commit does not deliever an error return code, if commit is canceld,
1465 # so a revert is performed in any case
1466 svn_revert();
[929]1467 return $retcode;
[271]1468}
1469
[800]1470#
1471# revert: copies files back from repository to system
1472#
1473sub revert(@)
1474{
1475 check_parameter( @_, 1 );
1476 check_env();
1477
1478 (
1479 my $basename,
1480 my $dirname_prod,
1481 my $dirname_repo,
1482 my $filename_prod,
1483 my $filename_repo
[801]1484 ) = get_filenames( $_[0] );
[800]1485
1486 # return code for the shell
1487 # default: error
1488 my $return_code = $RETURN_OK;
1489
1490 # cleanup repository
[801]1491 cleanup();
[889]1492
[800]1493 #svn_update();
1494
1495 ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) =
1496 getModifiedFiles($filename_prod);
1497 my %changedfiles = %{$refChangedFiles};
1498 my %removedfiles = %{$refRemovedFiles};
1499 my %unknownfiles = %{$refUnknownFiles};
1500
1501 if ( %removedfiles or %changedfiles or %unknownfiles ) {
1502
1503 if (%removedfiles) {
1504 print "DELETED files and directories. Recreated from repository:\n";
[801]1505 my @removedPaths =
1506 ( sort { length $a > length $b } keys %removedfiles );
[800]1507 print join( "\n", @removedPaths ) . "\n\n";
1508
1509 # copy files one by one from local repository to system
1510 # and also create directories
1511 # paths are sorted, so that directories are created first
[801]1512 for my $real_path (@removedPaths) {
1513 if ( -d $removedfiles{"$real_path"} ) {
[800]1514 mkpath("$real_path");
1515 } else {
[889]1516 copy_file_from_repository_to_system($real_path);
[800]1517 }
1518 }
1519 }
1520
1521 if (%changedfiles) {
1522 print "MODIFIED files. Copied from repository to the system:\n";
1523 print join( "\n", ( keys %changedfiles ) ) . "\n\n";
1524
1525 # copy files one by one from local repository to system
1526 for my $real_file ( keys(%changedfiles) ) {
[889]1527 copy_file_from_repository_to_system($real_file);
[800]1528 }
1529
1530 }
1531
1532 if (%unknownfiles) {
1533 print "UNKNOWN: insufficient permission to check files:\n";
1534 print join( "\n", ( keys %unknownfiles ) ) . "\n\n";
1535
1536 $return_code = $RETURN_NOK;
1537 }
1538
1539 } else {
1540 print "no modified files found in $dirname_repo\n";
1541 }
1542
1543 return $return_code;
1544}
1545
[193]1546sub blame(@)
1547{
[205]1548 check_parameter( @_, 1 );
1549 check_env();
[193]1550
[205]1551 (
1552 my $basename,
1553 my $dirname_prod,
1554 my $dirname_repo,
1555 my $filename_prod,
1556 my $filename_repo
[801]1557 ) = get_filenames( $_[0] );
[205]1558
[929]1559 my $retcode = run_interactive("$SVN blame --non-interactive $svnCheckoutCredentials $svnOptions $filename_repo");
1560 return $retcode;
[193]1561}
1562
[187]1563sub diff(@)
1564{
[205]1565 check_parameter( @_, 1 );
1566 check_env();
[187]1567
[205]1568 (
1569 my $basename,
1570 my $dirname_prod,
1571 my $dirname_repo,
1572 my $filename_prod,
1573 my $filename_repo
[801]1574 ) = get_filenames( $_[0] );
[205]1575
1576 #print "$basename,$dirname_prod,$dirname_repo\n";
1577
[797]1578 svn_update();
[205]1579
[238]1580 ( my $rc_diff, my @diff_result ) =
[239]1581 run_command( $diff . " $filename_repo $filename_prod" );
[238]1582
1583 print @diff_result;
[929]1584 return $rc_diff;
[187]1585}
1586
[209]1587sub status(@)
1588{
1589 check_parameter( @_, 1 );
1590 check_env();
1591
[270]1592 (
1593 my $basename,
1594 my $dirname_prod,
1595 my $dirname_repo,
1596 my $filename_prod,
1597 my $filename_repo
[801]1598 ) = get_filenames( $_[0] || "/" );
[270]1599
[252]1600 # return code for the shell
1601 # default: error
1602 my $return_code = $RETURN_NOK;
1603
[209]1604 #
1605 # update local repository
1606 #
[278]1607 #svn_update( $filename_prod );
[209]1608
[918]1609 # perform plugins (required to see changes in plugin results)
[916]1610 perform_plugins();
[278]1611
1612 # get modified files
1613 ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) =
[275]1614 getModifiedFiles($dirname_prod);
[268]1615 my %changedfiles = %{$refChangedFiles};
1616 my %removedfiles = %{$refRemovedFiles};
[278]1617 my %unknownfiles = %{$refUnknownFiles};
[209]1618
[278]1619 if ( %removedfiles or %changedfiles or %unknownfiles ) {
1620
[215]1621 if (%removedfiles) {
[278]1622 print "DELETED: files found in repository, but not in system:\n";
[800]1623 print join( "\n", sort ( keys %removedfiles ) ) . "\n\n";
[209]1624 }
1625
[215]1626 if (%changedfiles) {
[278]1627 print "MODIFIED: files differs between repository and system:\n";
1628 print join( "\n", ( keys %changedfiles ) ) . "\n\n";
[209]1629 }
[278]1630
1631 if (%unknownfiles) {
1632 print "UNKNOWN: insufficient permission to check files:\n";
1633 print join( "\n", ( keys %unknownfiles ) ) . "\n\n";
1634 }
1635
[209]1636 } else {
[270]1637 print "no modified files found in $dirname_repo\n";
[252]1638 $return_code = $RETURN_OK;
[209]1639 }
[215]1640
[252]1641 return $return_code;
[215]1642}
[209]1643
[277]1644#
1645# return short status in Nagios plugin conform way
1646#
1647sub check()
1648{
1649 check_env();
1650
1651 # return code for the shell
[290]1652 my $return_code = $RETURN_OK;
[277]1653 my $return_string = "OK: no modified files";
1654
[918]1655 # perform plugins (required to see changes in plugin results)
[916]1656 perform_plugins();
[278]1657
1658 # get modified files
1659 ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) =
[290]1660 getModifiedFiles("/");
[277]1661 my %changedfiles = %{$refChangedFiles};
1662 my %removedfiles = %{$refRemovedFiles};
[278]1663 my %unknownfiles = %{$refUnknownFiles};
[277]1664
1665 if ( %removedfiles or %changedfiles ) {
1666 $return_string = "Warning: ";
[290]1667 if (%changedfiles) {
1668 $return_string .=
1669 "changed: " . join( ", ", ( keys %changedfiles ) ) . ". ";
[277]1670 }
[290]1671 if (%removedfiles) {
1672 $return_string .=
1673 "removed: " . join( ", ", ( keys %removedfiles ) ) . ". ";
[277]1674 }
[278]1675 if (%unknownfiles) {
[290]1676 $return_string .=
1677 "unknown: " . join( ", ", ( keys %unknownfiles ) ) . ". ";
[278]1678 }
[277]1679 $return_code = $RETURN_WARN;
1680 }
1681
1682 # addition nagios Service Status
1683 #Critical
1684 #Unknown
1685
[290]1686 print "$return_string\n";
[277]1687 return $return_code;
1688}
1689
[270]1690sub permissions()
[215]1691{
1692 check_env();
1693
[278]1694 my $return_code = $RETURN_OK;
1695
[215]1696 #
1697 # update local repository
1698 #
1699 #svn_update();
1700
[220]1701 my $dir = $DASSCM_REPO;
[275]1702 my @files = svn_ls("/");
[215]1703
1704 if (@files) {
1705
[918]1706 print "#\n";
1707 print "# created by dasscm permissions\n";
1708 print "# It is intended to be used for restoring permissions\n";
1709 print "#\n";
[220]1710
[918]1711 # generate and print permissions
1712 foreach my $line ( generatePermissionList(@files) ) {
1713 print "$line\n";
[215]1714 }
[278]1715
[215]1716 }
[278]1717
1718 return $return_code;
[209]1719}
1720
[274]1721#
1722# remove all uncommited changes in the repository
1723#
1724sub cleanup()
1725{
[929]1726 my $return_code = $RETURN_OK;
1727
[274]1728 check_env();
[268]1729
[275]1730 svn_revert($DASSCM_REPO);
[285]1731 svn_remove_unknown_files($DASSCM_REPO);
[929]1732
1733 return $return_code;
[274]1734}
1735
[884]1736#
1737# used for bash completion
1738# prints the next possible command line parameters
1739#
1740sub complete(@)
1741{
[889]1742 my @input = @_;
[894]1743 my %options_complete = ();
[884]1744
[929]1745 my $return_code = $RETURN_OK;
1746
[894]1747 # check and remove global options. if options are wrong, nothing to do
[907]1748 @ARGV = @input;
[914]1749 if ( GetOptions( \%options_complete, @OPTIONS_GLOBAL ) ) {
[894]1750 my $number_arguments = @input;
1751 if ( $number_arguments <= 1 ) {
[893]1752
[894]1753 # complete dasscm commands
1754 my $input = $input[0] || "";
1755 map { m/^$input/ && print $_, "\n" } ( keys %COMMANDS );
1756 } else {
[914]1757
[894]1758 # complete dasscm parameter
1759 my $command = get_command_uniform_name( $input[0] );
[914]1760 if ($command) {
1761
[895]1762 # remove command
1763 shift @input;
[887]1764
[895]1765 # check and remove options
[914]1766 my @options = get_command_possible_options($command);
[907]1767 @ARGV = @input;
[914]1768 if ( ( not @options )
1769 || ( GetOptions( \%options_complete, @options ) ) )
1770 {
[895]1771
[914]1772 my @params = get_command_possible_params($command);
[895]1773 if ($verbose) { print "params: ", Dumper(@params); }
1774
1775 my $number_arguments = @input;
1776
1777 #print "input: ", join( ",", @input ), " (", $number_arguments, ")\n";
1778
[914]1779 if ( $number_arguments > 0 ) {
[895]1780 my $parameter_number = $number_arguments - 1;
1781 if ( defined( $params[$parameter_number] )
1782 && $params[$parameter_number] )
1783 {
1784 my $param = $params[$parameter_number];
[914]1785 if ($verbose) {
1786 print "param used: ", $param, "\n";
1787 }
[895]1788 if ( $param eq "PATH_PROD" ) {
[914]1789 complete_path(
1790 $input[ $number_arguments - 1 ] );
[895]1791 } elsif ( $param eq "PATH_REPO" ) {
[914]1792 complete_repopath(
1793 $input[ $number_arguments - 1 ] );
[895]1794 }
1795 }
1796 }
[894]1797 }
[887]1798 }
[884]1799 }
1800 }
[929]1801 return $return_code;
[884]1802}
1803
[887]1804sub complete_path(@)
1805{
[929]1806 my $return_code = $RETURN_OK;
[887]1807 check_parameter( @_, 1 );
1808 check_env();
1809
1810 (
1811 my $basename,
1812 my $dirname_prod,
1813 my $dirname_repo,
1814 my $filename_prod,
1815 my $filename_repo
1816 ) = get_filenames( $_[0] );
1817
[889]1818 my @files = get_complete_path_globbing($filename_prod);
[887]1819
1820 if (@files) {
1821 print join( "\n", @files );
1822 print "\n";
1823 }
[929]1824 return $return_code;
[887]1825}
1826
1827sub complete_repopath(@)
1828{
[929]1829 my $return_code = $RETURN_OK;
[887]1830 check_parameter( @_, 1 );
1831 check_env();
1832
1833 (
1834 my $basename,
1835 my $dirname_prod,
1836 my $dirname_repo,
1837 my $filename_prod,
1838 my $filename_repo
1839 ) = get_filenames( $_[0] );
1840
[889]1841 my @files = get_complete_path_globbing($filename_repo);
1842
[887]1843 if (@files) {
[889]1844
[887]1845 # remove DASSCM_REPO path again
[889]1846 print join(
1847 "\n",
1848 map( {
1849 s|^${DASSCM_REPO}|/|;
1850 $_
1851 } @files )
1852 );
[887]1853 print "\n";
1854 }
[929]1855 return $return_code;
[887]1856}
1857
[189]1858#####################################################################
1859#
[186]1860# main
[189]1861#
[186]1862
[252]1863my $return_code = $RETURN_OK;
[186]1864my $number_arguments = @ARGV;
1865
[893]1866# global options
1867# stops at first non-option
[914]1868Getopt::Long::Configure('require_order');
1869if ( not GetOptions( \%options, @OPTIONS_GLOBAL ) ) {
[893]1870 usage();
1871 exit $RETURN_NOK;
1872}
1873
1874# set verbose to command line option
1875$verbose = $options{'verbose'};
1876
[914]1877if ( $options{'help'} ) {
[893]1878 help(@ARGV);
1879 exit;
1880}
1881
[892]1882# get subcommand and remove it from @ARGV
[914]1883if ( defined( $ARGV[0] ) ) {
[889]1884 $command = get_command_uniform_name( $ARGV[0] );
[205]1885 shift @ARGV;
[892]1886}
[893]1887
[914]1888if ( not defined($command) ) {
1889 usage();
1890 exit $RETURN_NOK;
[893]1891}
[196]1892
[893]1893$DASSCM_LOCAL_REPOSITORY_BASE = $config->{'DASSCM_LOCAL_REPOSITORY_BASE'};
1894$DASSCM_REPOSITORY_NAME = $config->{'DASSCM_REPOSITORY_NAME'};
[205]1895
[918]1896$DASSCM_PLUGIN_RESULTS_PATH =
1897 $config->{'DASSCM_LOCAL_REPOSITORY_BASE'} . "/" . "plugin-results/";
[916]1898
[918]1899# get list of additional directories and files, seperated by blank (" ")
1900# these files are always stored in subversion
1901if ( $config->{'DASSCM_ADDITIONAL_FILES'} ) {
1902 @DASSCM_ADDITIONAL_FILES = split / /, $config->{'DASSCM_ADDITIONAL_FILES'};
1903} else {
1904 @DASSCM_ADDITIONAL_FILES = ( $DASSCM_PLUGIN_RESULTS_PATH );
1905}
1906
[1133]1907if ( $config->{'DASSCM_SVN_REPOSITORY_BASE'} && $DASSCM_REPOSITORY_NAME ) {
[1132]1908 $DASSCM_SVN_REPOSITORY =
1909 $config->{'DASSCM_SVN_REPOSITORY_BASE'} . "/" . $DASSCM_REPOSITORY_NAME;
1910}
[205]1911
[893]1912$DASSCM_CHECKOUT_USERNAME = $config->{'DASSCM_CHECKOUT_USERNAME'};
1913$DASSCM_CHECKOUT_PASSWORD = $config->{'DASSCM_CHECKOUT_PASSWORD'};
[205]1914
[893]1915#
1916# if a user is given by dasscm configuration file, we use it.
1917# Otherwise we expect that read-only account is configured
1918# as local subversion configuration.
1919# If this is also not the case,
1920# user is required to type username and password.
1921# This will be stored as local subversion configuration thereafter.
1922#
1923if ( $DASSCM_CHECKOUT_USERNAME && $DASSCM_CHECKOUT_PASSWORD ) {
1924 $svnCheckoutCredentials =
[914]1925 " --username $DASSCM_CHECKOUT_USERNAME --password $DASSCM_CHECKOUT_PASSWORD ";
[893]1926}
[286]1927
[893]1928
1929# check for command options
[914]1930my @cmd_options = get_command_possible_options($command);
1931if (@cmd_options) {
1932
[205]1933 # get command line options and store them in options hash
[893]1934 my $result = GetOptions( \%options, @cmd_options );
[205]1935
1936 # print options
1937 foreach my $option ( keys %options ) {
[215]1938 print "${option}: $options{$option}\n";
[205]1939 }
[893]1940}
[205]1941
[893]1942#
1943# action accordinly to command are taken
1944#
[929]1945$return_code = &{ get_command_function($command) }(@ARGV);
[214]1946
[252]1947exit $return_code;
Note: See TracBrowser for help on using the repository browser.