#!/usr/bin/perl -w # $Id: dasscm 803 2009-11-18 15:28:12Z joergs $ use strict; use Env qw($DASSCM_PROD $DASSCM_REPO $USER $DASSCM_USERNAME $DASSCM_USER $DASSCM_PASSWORD $SHELL); use Cwd; use Getopt::Long; use File::Basename; use File::Compare; # used system("cp -a"), because File::Copy does not keep permissions #use File::Copy; use File::Find; use File::stat; use File::Path; use Term::ReadKey; #use Data::Dumper; ##################################################################### # # global # # shell exit codes my $RETURN_OK = 0; my $RETURN_NOK = 1; # Nagios return codes my $RETURN_WARN = 1; my $RETURN_CRIT = 2; my $RETURN_UNKNOWN = 3; # documentation file (for usage) my $doc_file = "/usr/share/doc/packages/dasscm/dasscm_howto.txt"; # commands that require write access (and therefore a login) # to the repository server my @COMMANDS_REQUIRE_WRITE = ( "add", "commit" ); # configuration file my $config_file = "/etc/dasscm.conf"; my $config = get_config($config_file); my $DASSCM_LOCAL_REPOSITORY_BASE; my $DASSCM_REPOSITORY_NAME; my $DASSCM_SVN_REPOSITORY; my $DASSCM_CHECKOUT_USERNAME; my $DASSCM_CHECKOUT_PASSWORD; my $DASSCM_PERMISSION_FILE; # current directory at program start my $StartDirectory = cwd(); my $diff = "diff --exclude .svn "; my $SVN = "svn "; my $svnOptions = ""; my $svnCheckoutCredentials = ""; my $svnPasswordCredentials = ""; # flag. Set to true by svn_update # This prevents, that svn_update is called multiple times my $svnRepositoryIsUptodate = 0; # command line options get stored in options hash my %options = (); # subcommand, that gets executed (add, commit, ...) my $command; my $verbose = 0; ##################################################################### # # util functions # sub usage() { print '$Id: dasscm 803 2009-11-18 15:28:12Z joergs $'; print "\n\n"; print "usage: dasscm [options] [args]\n"; print "\n"; print "dasscm is intended to help versioning configuration files\n"; print "\n"; print "Available subcommands:\n"; print " help \n"; print " init\n"; print " login \n"; print " up \n"; print " ls \n"; print " add \n"; print " commit \n"; print " revert \n"; print " diff \n"; print " status \n"; print " check\n"; print " cleanup\n"; print " permissions\n"; print "\n"; print "If dasscm is not yet configured, read $doc_file\n"; } sub warning(@) { print "Warning: " . join( "\n ", @_ ) . "\n"; } sub error(@) { print "Error: " . join( "\n ", @_ ) . "\n"; } sub fatalerror(@) { error(@_); #print "Exiting\n"; exit 1; } # # reading config file and return key/value pairs as hash # sub get_config { my $file = $_[0]; if ( !$file ) { fatalerror( "failed to open config file" . $file ); } my $data = {}; # try to open config file if ( !open( FH, $file ) ) { fatalerror( "failed to open config file" . $file ); } else { while () { chomp; if (/^#/) { next; } if ( $_ =~ /=/g ) { # splitting in 2 fields at maximum my ( $option, $value ) = split( /=/, $_, 2 ); $option =~ s/^\s+//g; $option =~ s/\s+$//g; $option =~ s/\"+//g; $value =~ s/^\s+//g; $value =~ s/\s+$//g; $value =~ s/\"+//g; if ( length($option) ) { $data->{$option} = $value; } } } } close(FH); return $data; } # # check and evaluate environment variables # sub check_env() { # DASSCM_PROD if ( !$DASSCM_PROD ) { $DASSCM_PROD = "/"; } if ( !-d $DASSCM_PROD ) { die "DASSCM_PROD ($DASSCM_PROD) is not set to a directory.\n"; } if ($verbose) { print "DASSCM_PROD: " . $DASSCM_PROD . "\n"; } # DASSCM_REPOSITORY_NAME if ( !$DASSCM_REPOSITORY_NAME ) { die "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"; } # DASSCM_REPO if ( !$DASSCM_REPO ) { if ( $DASSCM_LOCAL_REPOSITORY_BASE && $DASSCM_REPOSITORY_NAME ) { $DASSCM_REPO = $DASSCM_LOCAL_REPOSITORY_BASE . "/" . $DASSCM_REPOSITORY_NAME; } else { die "Envirnonment variable DASSCM_REPO not set.\nSet DASSCM_REPO to the directory of the versioning system checkout for this machine.\n"; } } if ($verbose) { print "DASSCM_REPO: " . $DASSCM_REPO . "\n"; } # # subversion checkout user # if ( !$DASSCM_CHECKOUT_USERNAME ) { fatalerror( "variable DASSCM_CHECKOUT_USERNAME is not defined.", "Use file $config_file to configure it." ); } if ( !$DASSCM_CHECKOUT_PASSWORD ) { fatalerror( "variable DASSCM_CHECKOUT_PASSWORD is not defined.", "Use file $config_file to configure it." ); } # # check if local repository directory exist # (if not creating by init) # if ( $command ne "init" ) { if ( not -d $DASSCM_REPO ) { die "Can't access local repository DASSCM_REPO\n($DASSCM_REPO)\nCheck configuration and execute\n dasscm init\n"; } # # user settings # # DASSCM_USER is legacy. Use DASSCM_USERNAME instead if ( !$DASSCM_USERNAME ) { $DASSCM_USERNAME = $DASSCM_USER; } # user root is not allowed for checkins. # if user is root, DASSCM_USER has to be set, # otherwise USER can be used if ( "$USER" eq "root" ) { if ( ( not $DASSCM_USERNAME ) and ( grep { m|^$command$| } @COMMANDS_REQUIRE_WRITE ) ) { #( $command ne "login" ) and ( $command ne "status" ) ) { fatalerror( "Envirnonment variable DASSCM_USERNAME not set.", "Set DASSCM_USERNAME to your subversion user account or", "use 'dasscm login'" ); } $svnOptions .= " --no-auth-cache "; } elsif ( !$DASSCM_USERNAME ) { $DASSCM_USERNAME = $USER; } # # password # if ($DASSCM_PASSWORD) { $svnPasswordCredentials = " --password '$DASSCM_PASSWORD' "; } } #$svnOptions .= " --username $DASSCM_USERNAME " } # # has been intendend, # to check addtitional parameters. # Currently not used. # sub check_parameter(@) { } # # normalize path namens: # - directories should end with "/" # - use only single "/" # sub normalize_path($) { my $path = shift || ""; if ( $path =~ m|^/| ) { # full path if ( -d $path ) { # ensure, a directory ends with '/' $path .= '/'; } } elsif ( -d cwd() . '/' . $path ) { # ensure, a directory ends with '/' $path .= '/'; } # remove double (triple) slashes (/) $path =~ s|/[/]*|/|g; return $path; } # # generate from (relative) filename # all required file and directory names: # $basename, $dirname_prod, $dirname_repo, # $filename_prod, $filename_repo # sub get_filenames(@) { my $filename_prod = $_[0] || "."; # make filename absolut if ( !( $filename_prod =~ m/^\// ) ) { $filename_prod = cwd() . '/' . $filename_prod; } # file must be readable. Only exception is, when file should be reverted if ( $command ne "revert" ) { if ( not -r $filename_prod ) { fatalerror( $filename_prod . " is not accessable" ); } } # dirname buggy: eg. "/etc/" is reduced to "/", # "/etc" is used as filename # herefore make sure, that if filename is a directory, # it will end by "/" $filename_prod = normalize_path($filename_prod); ( my $basename, my $dirname_prod ) = fileparse($filename_prod); # normalize path. # not done for reverting, because in this case, the directory may not exist # and the correct path should already be stored in the repository if ( $command ne "revert" ) { # uses chdir to determine real directory in a unique way chdir $dirname_prod or fatalerror( "failed to access directory $dirname_prod: " . $! ); $dirname_prod = normalize_path( cwd() ); chdir $StartDirectory; } my $dirname_repo = normalize_path( $DASSCM_REPO . "/" . $dirname_prod ); my $filename_repo = normalize_path("$dirname_repo/$basename"); if ($verbose) { print "filename_repo: " . $filename_repo . "\n"; print "dirname_repo: " . $dirname_repo . "\n"; print "filename_prod: " . $filename_prod . "\n"; print "dirname_prod: " . $dirname_prod . "\n"; print "basename: " . $basename . "\n"; } return ( $basename, $dirname_prod, $dirname_repo, $filename_prod, $filename_repo ); } sub copy_file_to_repository( $ ) { my $filename = shift; ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames($filename); #copy( $filename_prod, $filename_repo ) ( my $rc, my @result ) = run_command( "cp -a \"$filename_prod\" \"$filename_repo\"" ); if( $rc != 0 ) { error( "failed to copy $filename_prod to repository: ", @result ); } # return success return $rc == 0; } sub copy_file_from_repository_to_system( $ ) { my $filename = shift; ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames($filename); ( my $rc, my @result ) = run_command( "cp -a \"$filename_repo\" \"$filename_prod\"" ); if( $rc != 0 ) { error( "failed to copy $filename_repo to $filename_prod: ", @result ); } # return success return $rc == 0; } # # creates a file with permissions # sub generatePermissionList { # generieren der Zeilen für Permission-Savefile my @files = @_; my @permlist = (); foreach my $file (@files) { $file = "/" . $file; if ( -e $file ) { my $info = stat($file) || die "failed to stat $file: aborting"; my $mode = get_type( $info->mode ) & 07777; my $modestring = sprintf( "%04o", $mode ); my $uidnumber = $info->uid; my $uid = getpwuid($uidnumber) || $uidnumber; my $gidnumber = $info->gid; my $gid = getgrgid($gidnumber) || $gidnumber; push( @permlist, sprintf( "%-55s %-17s %4d", $file, "${uid}:${gid}", $modestring ) ); } } return @permlist; } sub get_type { # Funktion übernommen aus /usr/bin/chkstat my $S_IFLNK = 0120000; # symbolic link my $S_IFREG = 0100000; # regular file my $S_IFDIR = 0040000; # directory my $S_IFCHAR = 0020000; # character device my $S_IFBLK = 0060000; # block device my $S_IFFIFO = 0010000; # fifo my $S_IFSOCK = 0140000; # socket my $S_IFMT = 0170000; # type of file my $S_m; if ( ( $_[0] & $S_IFMT ) == $S_IFLNK ) { $S_m = $_[0] - $S_IFLNK; } elsif ( ( $_[0] & $S_IFMT ) == $S_IFREG ) { $S_m = $_[0] - $S_IFREG; } elsif ( ( $_[0] & $S_IFMT ) == $S_IFDIR ) { $S_m = $_[0] - $S_IFDIR; } elsif ( ( $_[0] & $S_IFMT ) == $S_IFCHAR ) { $S_m = $_[0] - $S_IFCHAR; } elsif ( ( $_[0] & $S_IFMT ) == $S_IFBLK ) { $S_m = $_[0] - $S_IFBLK; } elsif ( ( $_[0] & $S_IFMT ) == $S_IFFIFO ) { $S_m = $_[0] - $S_IFFIFO; } elsif ( ( $_[0] & $S_IFMT ) == $S_IFSOCK ) { $S_m = $_[0] - $S_IFSOCK; } $S_m; } sub run_command { my $command = shift; #print "executing command: " . $command . "\n"; open( RESULT, $command . ' 2>&1 |' ); my @result = ; close(RESULT); my $retcode = $? >> 8; #print @result; #if( $retcode ) { print "return code: " . $retcode . "\n"; } return ( $retcode, @result ); } sub run_interactive { if ($verbose) { print "run_interactive:" . join( " ", @_ ) . "\n"; } system(@_); if ( $? == -1 ) { printf "failed to execute: $!\n"; } elsif ( $? & 127 ) { printf "child died with signal %d, %s coredump\n", ( $? & 127 ), ( $? & 128 ) ? 'with' : 'without'; } elsif ( $? >> 8 != 0 ) { printf "child exited with value %d\n", $? >> 8; } return ( $? >> 8 ); } sub svn_check_credentials( $$;$$ ) { my $username = shift; my $password = shift; # check silently are allow user interaction? my $interactive = shift || 0; # default: exit program, if repository is not accessable # (do not exit for 'init') my $fatalerror = shift || 1; print "checking credentials "; if ( !$username ) { fatalerror("no username given"); } if ( !$password ) { fatalerror("no password given"); } print "for " . $username . "@" . $DASSCM_SVN_REPOSITORY . ": "; # Options for "svn info" are not supported by subversion 1.0.0 (SLES9), # therefore switching to "svn status" # ( my $rc_update, my @result ) = # run_command( # "$SVN info --non-interactive --no-auth-cache --username $username --password $password $DASSCM_SVN_REPOSITORY" # ); #print @result; my $rc_update; if ($interactive) { $rc_update = run_interactive( "$SVN ls --no-auth-cache --username '$username' --password '$password' $DASSCM_SVN_REPOSITORY" ); } else { ( $rc_update, my @result ) = run_command( "$SVN ls --non-interactive --no-auth-cache --username '$username' --password '$password' $DASSCM_SVN_REPOSITORY" ); if ( $rc_update != 0 ) { print "\n", @result; if ($fatalerror) { fatalerror(); } return; } } # return success return $rc_update == 0; } sub svn_update( ;$ ) { my $update_path = shift || ""; # return value my $update_ok = 1; # use this flag to do only one update per run if ( !$svnRepositoryIsUptodate ) { ( my $rc_update, my @result ) = run_command( "$SVN update --non-interactive $svnCheckoutCredentials '$DASSCM_REPO/$update_path'" ); print @result; if ( $rc_update != 0 ) { error("failed to update local repository ($update_path)"); $update_ok = 0; } elsif ( not $update_path ) { # set this flag if a full update is done $svnRepositoryIsUptodate = 1; } } return $update_ok; } sub svn_ls( ;@ ) { ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames( $_[0] ); # svn ls -R is better, but much, much slower # ( my $rc, my @result ) = run_command("$SVN ls --recursive $svnCheckoutCredentials $path"); my @files = (); my @links = (); my @dirs = (); my @others = (); find( { wanted => sub { my $name = normalize_path($File::Find::name); $name =~ s|^$dirname_repo||; #print "($name)\n";# . $File::Find::dir . "\n"; if ( not $name ) { # name string is empty (top directory). # do nothing } elsif ( $name =~ m/\.svn/ ) { # skip svn meta data } elsif ( -l $_ ) { # soft link # important: check for links first # to exclude them from further checks push( @links, $name ); } elsif ( -d $_ ) { # directories push( @dirs, $name ); } elsif ( -f $_ ) { # regular file push( @files, $name ); } else { push( @others, $name ); } } }, ($filename_repo) ); return ( sort( @dirs, @files ) ); } sub svn_revert( ;$ ) { my $path = shift || $DASSCM_REPO; ( my $rc_update, my @result ) = run_command("$SVN revert -R '$path'"); if ( $rc_update != 0 ) { print "\n", @result; error("failed to revert subversion repository changes"); } } sub svn_remove_unknown_files( ;$ ) { my $path = shift || $DASSCM_REPO; ( my $rc_update, my @result ) = run_command("$SVN status '$path'"); if ( $rc_update != 0 ) { print "\n", @result; error("failed to receive subversion repository information"); } else { foreach (@result) { if (s/^\? +//) { chomp; # if file is unknown to subversion (line starts with "?") # remove it print "removing $_\n"; # unlink doesn't work recursive, there "rm -rf" is used #unlink($_); system("rm -rf $_"); } } } } sub getModifiedFiles( ;$ ) { ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames( $_[0] ); my @files = svn_ls($filename_prod); # stores result from status (cvscheck) my %removedfiles = (); my %changedfiles = (); my %unknownfiles = (); # create list of modified files if (@files) { foreach my $file (@files) { my $realfile = $dirname_prod . $file; my $cvsworkfile = $dirname_repo . $file; if ( -d $realfile ) { # directory if ( !-d "$cvsworkfile" ) { # real is directory, repository is not. This is a problem $changedfiles{"$realfile"} = $cvsworkfile; } } elsif ( !-e $realfile ) { $removedfiles{"$realfile"} = $cvsworkfile; } elsif ( !-r $realfile ) { # don't have permission to read the file, # can't check it $unknownfiles{"$realfile"} = $cvsworkfile; } else { ( -r "$cvsworkfile" ) || fatalerror("failed to read $cvsworkfile"); if ( compare( $cvsworkfile, $realfile ) != 0 ) { $changedfiles{"$realfile"} = $cvsworkfile; } } } } return ( \%changedfiles, \%removedfiles, \%unknownfiles ); } # # from an array of files/dirs, # generates list of files # sorted by type # sub get_files( @ ) { my @files = (); my @links = (); my @dirs = (); my @others = (); if (@_) { find( { wanted => sub { my $fullname = cwd() . "/" . $_; if ( -l $_ ) { # soft link # important: check for links first # to exclude them from further checks push( @links, $fullname ); } elsif ( -d $_ ) { # directories push( @dirs, $fullname ); } elsif ( -f $_ ) { # regular file push( @files, $fullname ); } else { push( @others, $fullname ); } } }, @_ ); } # don't rely on others. # If more specific file types are needed, # they will be added return { files => \@files, links => \@links, dirs => \@dirs, others => \@others }; } ##################################################################### # # functions sub help(;@) { if ( @_ == 0 ) { usage(); } else { print "help for @_: ...\n"; usage(); } } sub login(@) { check_parameter( @_, 1 ); check_env(); my $input_username = $_[0]; if ( not $input_username ) { my $output_username = ""; if ($DASSCM_USERNAME) { $output_username = " ($DASSCM_USERNAME)"; } print "Enter DASSCM user name", $output_username, ": "; $input_username = ; chomp($input_username); $input_username = $input_username || $DASSCM_USERNAME; } # hidden password input print "Enter password for $input_username: "; ReadMode('noecho'); my $input_password = ; ReadMode('normal'); chomp($input_password); print "\n"; # checking checkout username/password svn_check_credentials( $DASSCM_CHECKOUT_USERNAME, $DASSCM_CHECKOUT_PASSWORD ); print "checkout access okay\n"; svn_check_credentials( $input_username, $input_password ); # # set environment variables # $ENV{'DASSCM_USERNAME'} = "$input_username"; $ENV{'DASSCM_PASSWORD'} = "$input_password"; print "subversion access okay\n\n", "DASSCM_USERNAME: $input_username\n", "DASSCM_PASSWORD: (hidden)\n", "DASSCM_PROD: $DASSCM_PROD\n", "DASSCM_REPO: $DASSCM_REPO\n", "Server Repository: $DASSCM_SVN_REPOSITORY\n", "\n"; status(); print "\n[dasscm shell]\n\n"; my $shell = $SHELL || "bash"; exec($shell) or die "failed to start new shell"; } # # initialize local checkout directory (initial checkout) # sub init(@) { check_parameter( @_, 1 ); check_env(); # don't do repository creation (svn mkdir) here, # because then their must be a lot of prior checks # update complete repository # and create permission file my $retcode = run_interactive( "cd $DASSCM_LOCAL_REPOSITORY_BASE; $SVN checkout $svnCheckoutCredentials $svnOptions $DASSCM_SVN_REPOSITORY; mkdir -p `dirname $DASSCM_PERMISSION_FILE`; touch $DASSCM_PERMISSION_FILE" ); } sub ls(@) { check_parameter( @_, 1 ); check_env(); my @files = svn_ls(@_); if (@files) { print join( "\n", @files ); print "\n"; } } sub update(@) { check_parameter( @_, 1 ); check_env(); # # update local repository # svn_update(); } # # helper function for "add" command # sub add_helper(@) { ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames( $_[0] ); mkpath($dirname_repo); copy_file_to_repository( $filename_prod ); # already checked in? chdir $DASSCM_REPO; # also add the path to filename. for my $dir ( split( '/', $dirname_prod ) ) { if ($dir) { my ( $rc, @out ) = run_command("$SVN add --non-recursive '$dir'"); if ( $rc > 0 ) { print join( "\n", @out ); } chdir $dir; } } my ( $rc, @out ) = run_command("$SVN add '$basename'"); if ( $rc > 0 ) { print join( "\n", @out ); } chdir $StartDirectory; } # # adding new files (or directories) # sub add(@) { check_parameter( @_, 1 ); check_env(); # # update local repository # svn_update(); # get all regular files and links my $href_files = get_files(@_); #print Dumper( $href_files ); my @files = @{ $href_files->{files} }; my @links = @{ $href_files->{links} }; if (@files) { my $number = $#files + 1; print "files to check-in ($number): \n"; print join( "\n", @files ); print "\n"; } # TODO: check in links and also link target? At least warn about link target if (@links) { my $number = $#links + 1; print "\n"; print "ignoring links ($number):\n"; print join( "\n", @links ); print "\n"; } # TODO: confirm # copy files one by one to local repository for my $file (@files) { # add file add_helper($file); } # create new permissions file permissions(); # add permissions file add_helper($DASSCM_PERMISSION_FILE); if ( $options{'message'} ) { $svnOptions .= " --message \"$options{'message'}\" "; } # commit calls $EDITOR. # use "interactive" here, to display output my $retcode = run_interactive( "$SVN commit $svnOptions --username '$DASSCM_USERNAME' $svnPasswordCredentials $DASSCM_REPO" ); # svn commit does not deliever an error return code, if commit is canceld, # so a revert is performed in any case svn_revert(); } # # checks in all modified files # sub commit(@) { check_parameter( @_, 1 ); check_env(); ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames( $_[0] ); # # update local repository # svn_update(); ( my $refChangedFiles, my $refRemovedFiles ) = getModifiedFiles($filename_prod); my %changedfiles = %{$refChangedFiles}; my %removedfiles = %{$refRemovedFiles}; if (%removedfiles) { my $removedFilesString = '"' . join( '" "', values(%removedfiles) ) . '"'; my ( $rc, @out ) = run_command("$SVN rm $removedFilesString"); if ( $rc > 0 ) { print join( "\n", @out ); } } # copy files one by one to local repository for my $file ( keys(%changedfiles) ) { copy_file_to_repository($file); } # create new permissions file permissions(); # add permissions file add_helper($DASSCM_PERMISSION_FILE); if ( $options{'message'} ) { $svnOptions .= " --message \"$options{'message'}\" "; } # commit calls $EDITOR. # use "interactive" here, to display output my $retcode = run_interactive( "$SVN commit $svnOptions --username '$DASSCM_USERNAME' $svnPasswordCredentials $DASSCM_REPO" ); # svn commit does not deliever an error return code, if commit is canceld, # so a revert is performed in any case svn_revert(); } # # revert: copies files back from repository to system # sub revert(@) { check_parameter( @_, 1 ); check_env(); ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames( $_[0] ); # return code for the shell # default: error my $return_code = $RETURN_OK; # cleanup repository cleanup(); #svn_update(); ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) = getModifiedFiles($filename_prod); my %changedfiles = %{$refChangedFiles}; my %removedfiles = %{$refRemovedFiles}; my %unknownfiles = %{$refUnknownFiles}; if ( %removedfiles or %changedfiles or %unknownfiles ) { if (%removedfiles) { print "DELETED files and directories. Recreated from repository:\n"; my @removedPaths = ( sort { length $a > length $b } keys %removedfiles ); print join( "\n", @removedPaths ) . "\n\n"; # copy files one by one from local repository to system # and also create directories # paths are sorted, so that directories are created first for my $real_path (@removedPaths) { if ( -d $removedfiles{"$real_path"} ) { mkpath("$real_path"); } else { copy_file_from_repository_to_system( $real_path ); } } } if (%changedfiles) { print "MODIFIED files. Copied from repository to the system:\n"; print join( "\n", ( keys %changedfiles ) ) . "\n\n"; # copy files one by one from local repository to system for my $real_file ( keys(%changedfiles) ) { copy_file_from_repository_to_system( $real_file ); } } if (%unknownfiles) { print "UNKNOWN: insufficient permission to check files:\n"; print join( "\n", ( keys %unknownfiles ) ) . "\n\n"; $return_code = $RETURN_NOK; } } else { print "no modified files found in $dirname_repo\n"; } return $return_code; } sub blame(@) { check_parameter( @_, 1 ); check_env(); ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames( $_[0] ); my $retcode = run_interactive("$SVN blame $svnOptions $filename_repo"); } sub diff(@) { check_parameter( @_, 1 ); check_env(); ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames( $_[0] ); #print "$basename,$dirname_prod,$dirname_repo\n"; svn_update(); ( my $rc_diff, my @diff_result ) = run_command( $diff . " $filename_repo $filename_prod" ); print @diff_result; } sub status(@) { check_parameter( @_, 1 ); check_env(); ( my $basename, my $dirname_prod, my $dirname_repo, my $filename_prod, my $filename_repo ) = get_filenames( $_[0] || "/" ); # return code for the shell # default: error my $return_code = $RETURN_NOK; # # update local repository # #svn_update( $filename_prod ); # check, if permissions have changed permissions(); # get modified files ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) = getModifiedFiles($dirname_prod); my %changedfiles = %{$refChangedFiles}; my %removedfiles = %{$refRemovedFiles}; my %unknownfiles = %{$refUnknownFiles}; if ( %removedfiles or %changedfiles or %unknownfiles ) { if (%removedfiles) { print "DELETED: files found in repository, but not in system:\n"; print join( "\n", sort ( keys %removedfiles ) ) . "\n\n"; } if (%changedfiles) { print "MODIFIED: files differs between repository and system:\n"; print join( "\n", ( keys %changedfiles ) ) . "\n\n"; } if (%unknownfiles) { print "UNKNOWN: insufficient permission to check files:\n"; print join( "\n", ( keys %unknownfiles ) ) . "\n\n"; } } else { print "no modified files found in $dirname_repo\n"; $return_code = $RETURN_OK; } return $return_code; } # # return short status in Nagios plugin conform way # sub check() { check_env(); # return code for the shell my $return_code = $RETURN_OK; my $return_string = "OK: no modified files"; # check, if permissions have changed permissions(); # get modified files ( my $refChangedFiles, my $refRemovedFiles, my $refUnknownFiles ) = getModifiedFiles("/"); my %changedfiles = %{$refChangedFiles}; my %removedfiles = %{$refRemovedFiles}; my %unknownfiles = %{$refUnknownFiles}; if ( %removedfiles or %changedfiles ) { $return_string = "Warning: "; if (%changedfiles) { $return_string .= "changed: " . join( ", ", ( keys %changedfiles ) ) . ". "; } if (%removedfiles) { $return_string .= "removed: " . join( ", ", ( keys %removedfiles ) ) . ". "; } if (%unknownfiles) { $return_string .= "unknown: " . join( ", ", ( keys %unknownfiles ) ) . ". "; } $return_code = $RETURN_WARN; } # addition nagios Service Status #Critical #Unknown print "$return_string\n"; return $return_code; } sub permissions() { check_env(); my $return_code = $RETURN_OK; # # update local repository # #svn_update(); my $dir = $DASSCM_REPO; my @files = svn_ls("/"); if (@files) { # generieren der Permissions my @permissions = generatePermissionList(@files); my $OUTFILE; my $tofile = 0; # Status für schreiben in File if ( -w dirname($DASSCM_PERMISSION_FILE) ) { # Verzeichnis existiert => schreiben open( OUTFILE, ">$DASSCM_PERMISSION_FILE" ) || die("failed to write to $DASSCM_PERMISSION_FILE: $!"); $tofile = 1; # Merken, daß in File geschrieben wird print OUTFILE "#\n"; print OUTFILE "# created by dasscm permissions\n"; print OUTFILE "# It is intended to be used for restoring permissions\n"; print OUTFILE "#\n"; } else { if ( $command eq "permission" ) { # Pfad für Sicherungsdatei existiert nicht => schreiben auf stdout # Alias Filehandle für stdout erzeugen $return_code = $RETURN_WARN; *OUTFILE = *STDOUT; } else { # TODO: improve this. Check for diff? $return_code = $RETURN_CRIT; return $return_code; } } foreach my $line (@permissions) { print OUTFILE "$line\n"; } if ($tofile) { close(OUTFILE); } } return $return_code; } # # remove all uncommited changes in the repository # sub cleanup() { check_env(); svn_revert($DASSCM_REPO); svn_remove_unknown_files($DASSCM_REPO); } ##################################################################### # # main # my $return_code = $RETURN_OK; my $number_arguments = @ARGV; if ( $number_arguments > 0 ) { # get subcommand and remove it from @ARGV $command = $ARGV[0]; shift @ARGV; $DASSCM_LOCAL_REPOSITORY_BASE = $config->{'DASSCM_LOCAL_REPOSITORY_BASE'}; $DASSCM_REPOSITORY_NAME = $config->{'DASSCM_REPOSITORY_NAME'}; # TODO: check variables $DASSCM_SVN_REPOSITORY = $config->{'DASSCM_SVN_REPOSITORY_BASE'} . "/" . $DASSCM_REPOSITORY_NAME; $DASSCM_CHECKOUT_USERNAME = $config->{'DASSCM_CHECKOUT_USERNAME'}; $DASSCM_CHECKOUT_PASSWORD = $config->{'DASSCM_CHECKOUT_PASSWORD'}; # # if a user is given by dasscm configuration file, we use it. # Otherwise we expect that read-only account is configured # as local subversion configuration. # If this is also not the case, # user is required to type username and password. # This will be stored as local subversion configuration thereafter. # if ( $DASSCM_CHECKOUT_USERNAME && $DASSCM_CHECKOUT_PASSWORD ) { $svnCheckoutCredentials = " --username $DASSCM_CHECKOUT_USERNAME --password $DASSCM_CHECKOUT_PASSWORD "; } $DASSCM_PERMISSION_FILE = $config->{'DASSCM_PERMISSION_FILE'} || "/etc/permissions.d/dasscm.permission_backup"; # get command line options and store them in options hash my $result = GetOptions( \%options, 'verbose', 'message=s' ); # print options foreach my $option ( keys %options ) { print "${option}: $options{$option}\n"; } # set verbose to command line option $verbose = $options{'verbose'}; # # action accordinly to command are taken # $command is rewritten in standard format, # so we can test for it later on more simply # $_ = $command; if (m/^help$/i) { help(@ARGV); } elsif (m/^login$/i) { $command = "login"; login(@ARGV); } elsif (m/^init$/i) { $command = "init"; init(@ARGV); } elsif (m/^ls$/i) { $command = "ls"; ls(@ARGV); } elsif ( (m/^update$/i) || (m/^up$/i) ) { $command = "update"; update(@ARGV); } elsif (m/^add$/i) { $command = "add"; add(@ARGV); } elsif ( (m/^commit$/i) || (m/^checkin$/i) || (m/^ci$/i) ) { $command = "commit"; commit(@ARGV); } elsif (m/^revert$/i) { $command = "revert"; $return_code = revert(@ARGV); } elsif (m/^blame$/i) { $command = "blame"; blame(@ARGV); } elsif (m/^diff$/i) { $command = "diff"; diff(@ARGV); } elsif ( (m/^status$/i) || (m/^st$/i) ) { $command = "status"; $return_code = status(@ARGV); } elsif (m/^check$/i) { $command = "check"; $return_code = check(); } elsif (m/^permissions$/i) { $command = "permissions"; $return_code = permissions(); } elsif (m/^cleanup$/i) { $command = "cleanup"; cleanup(); } else { print "unknown command: $command\n\n"; usage(); check_env(); $return_code = $RETURN_NOK; } } exit $return_code;