2012-03-27 18:36:43 +02:00
|
|
|
package Gitolite::Triggers::Mirroring;
|
|
|
|
|
|
|
|
use Gitolite::Rc;
|
|
|
|
use Gitolite::Common;
|
|
|
|
use Gitolite::Conf::Load;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
|
2012-04-18 02:56:18 +02:00
|
|
|
my $hn = $rc{HOSTNAME};
|
2012-03-27 18:36:43 +02:00
|
|
|
|
2012-06-01 08:17:17 +02:00
|
|
|
my ( $mode, $master, %slaves, %trusted_slaves );
|
|
|
|
|
2012-03-27 18:36:43 +02:00
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
|
|
|
sub input {
|
|
|
|
return unless $ARGV[0] =~ /^server-(\S+)$/;
|
|
|
|
|
2012-06-03 04:08:19 +02:00
|
|
|
# note: we treat %rc as our own internal "poor man's %ENV"
|
|
|
|
$rc{FROM_SERVER} = $1;
|
|
|
|
trace( 3, "from_server: $1" );
|
|
|
|
my $sender = $rc{FROM_SERVER} || '';
|
|
|
|
|
2012-06-01 08:17:17 +02:00
|
|
|
# custom peer-to-peer commands. At present the only one is 'perms -c',
|
|
|
|
# sent from a mirror command
|
|
|
|
if ($ENV{SSH_ORIGINAL_COMMAND} =~ /^CREATOR=(\S+) perms -c '(\S+)'$/) {
|
|
|
|
$ENV{GL_USER} = $1;
|
|
|
|
|
|
|
|
my $repo = $2;
|
|
|
|
details($repo);
|
|
|
|
_die "$hn: '$repo' is local" if $mode eq 'local';
|
|
|
|
_die "$hn: '$repo' is native" if $mode eq 'master';
|
2012-06-03 04:08:19 +02:00
|
|
|
_die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
|
2012-06-01 08:17:17 +02:00
|
|
|
|
|
|
|
# this expects valid perms content on STDIN
|
|
|
|
_system("gitolite perms -c $repo");
|
|
|
|
|
|
|
|
# we're done. Yes, really...
|
|
|
|
exit 0;
|
|
|
|
}
|
|
|
|
|
2012-03-27 18:36:43 +02:00
|
|
|
if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/ ) {
|
|
|
|
# my ($user, $newsoc, $repo) = ($1, $2, $3);
|
|
|
|
$ENV{SSH_ORIGINAL_COMMAND} = $2;
|
2012-04-18 02:56:18 +02:00
|
|
|
@ARGV = ($1);
|
|
|
|
$rc{REDIRECTED_PUSH} = 1;
|
|
|
|
trace( 3, "redirected_push for user $1" );
|
2012-03-27 18:36:43 +02:00
|
|
|
} else {
|
|
|
|
# master -> slave push, no access checks needed
|
|
|
|
$ENV{GL_BYPASS_ACCESS_CHECKS} = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
|
|
|
sub pre_git {
|
|
|
|
return unless $hn;
|
|
|
|
# nothing, and I mean NOTHING, happens if HOSTNAME is not set
|
2012-04-18 02:56:18 +02:00
|
|
|
trace( 1, "pre_git() on $hn" );
|
2012-03-27 18:36:43 +02:00
|
|
|
|
2012-04-18 02:56:18 +02:00
|
|
|
my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
|
2012-03-27 18:36:43 +02:00
|
|
|
|
|
|
|
my $sender = $rc{FROM_SERVER} || '';
|
|
|
|
$user = '' if $sender and not exists $rc{REDIRECTED_PUSH};
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
# now you know the repo, get its mirroring details
|
|
|
|
details($repo);
|
|
|
|
|
|
|
|
# we don't deal with any reads. Note that for pre-git this check must
|
|
|
|
# happen *after* getting details, to give mode() a chance to die on "known
|
|
|
|
# unknown" repos (repos that are in the config, but mirror settings
|
|
|
|
# exclude this host from both the master and slave lists)
|
|
|
|
return if $aa eq 'R';
|
|
|
|
|
2012-04-18 02:56:18 +02:00
|
|
|
trace( 1, "mirror", "pre_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) );
|
2012-03-27 18:36:43 +02:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
# case 1: we're master or slave, normal user pushing to us
|
2012-04-18 02:56:18 +02:00
|
|
|
if ( $user and not $rc{REDIRECTED_PUSH} ) {
|
|
|
|
trace( 3, "case 1, user push" );
|
2012-03-27 18:36:43 +02:00
|
|
|
return if $mode eq 'local' or $mode eq 'master';
|
2012-04-18 02:56:18 +02:00
|
|
|
if ( $trusted_slaves{$hn} ) {
|
|
|
|
trace( 3, "redirecting to $master" );
|
|
|
|
trace( 1, "redirect to $master" );
|
|
|
|
exec( "ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}" );
|
2012-03-27 18:36:43 +02:00
|
|
|
} else {
|
|
|
|
_die "$hn: pushing '$repo' to slave '$hn' not allowed";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
# case 2: we're slave, master pushing to us
|
2012-04-18 02:56:18 +02:00
|
|
|
if ( $sender and not $rc{REDIRECTED_PUSH} ) {
|
|
|
|
trace( 3, "case 2, master push" );
|
|
|
|
_die "$hn: '$repo' is local" if $mode eq 'local';
|
2012-03-27 18:36:43 +02:00
|
|
|
_die "$hn: '$repo' is native" if $mode eq 'master';
|
|
|
|
_die "$hn: '$sender' is not the master for '$repo'" if $master ne $sender;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
# case 3: we're master, slave sending a redirected push to us
|
2012-04-18 02:56:18 +02:00
|
|
|
if ( $sender and $rc{REDIRECTED_PUSH} ) {
|
|
|
|
trace( 3, "case 2, slave redirect" );
|
|
|
|
_die "$hn: '$repo' is local" if $mode eq 'local';
|
2012-03-27 18:36:43 +02:00
|
|
|
_die "$hn: '$repo' is not native" if $mode eq 'slave';
|
|
|
|
_die "$hn: '$sender' is not a valid slave for '$repo'" if not $slaves{$sender};
|
2012-04-18 02:56:18 +02:00
|
|
|
_die "$hn: redirection not allowed from '$sender'" if not $trusted_slaves{$sender};
|
2012-03-27 18:36:43 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_die "$hn: should not reach this line";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
|
|
|
sub post_git {
|
|
|
|
return unless $hn;
|
|
|
|
# nothing, and I mean NOTHING, happens if HOSTNAME is not set
|
2012-04-18 02:56:18 +02:00
|
|
|
trace( 1, "post_git() on $hn" );
|
2012-03-27 18:36:43 +02:00
|
|
|
|
2012-04-18 02:56:18 +02:00
|
|
|
my ( $repo, $user, $aa ) = @_[ 1, 2, 3 ];
|
2012-03-27 18:36:43 +02:00
|
|
|
# we don't deal with any reads
|
|
|
|
return if $aa eq 'R';
|
|
|
|
|
|
|
|
my $sender = $rc{FROM_SERVER} || '';
|
|
|
|
$user = '' if $sender;
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
# now you know the repo, get its mirroring details
|
|
|
|
details($repo);
|
|
|
|
|
2012-04-18 02:56:18 +02:00
|
|
|
trace( 1, "mirror", "post_git", $repo, "user=$user", "sender=$sender", "mode=$mode", ( $rc{REDIRECTED_PUSH} ? ("redirected") : () ) );
|
2012-03-27 18:36:43 +02:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
# case 1: we're master or slave, normal user pushing to us
|
2012-04-18 02:56:18 +02:00
|
|
|
if ( $user and not $rc{REDIRECTED_PUSH} ) {
|
|
|
|
trace( 3, "case 1, user push" );
|
2012-03-27 18:36:43 +02:00
|
|
|
return if $mode eq 'local';
|
|
|
|
# slave was eliminated earlier anyway, so that leaves 'master'
|
|
|
|
|
|
|
|
# find all slaves and push to each of them
|
|
|
|
push_to_slaves($repo);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
# case 2: we're slave, master pushing to us
|
2012-04-18 02:56:18 +02:00
|
|
|
if ( $sender and not $rc{REDIRECTED_PUSH} ) {
|
|
|
|
trace( 3, "case 2, master push" );
|
2012-03-27 18:36:43 +02:00
|
|
|
# nothing to do
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
|
|
# case 3: we're master, slave sending a redirected push to us
|
2012-04-18 02:56:18 +02:00
|
|
|
if ( $sender and $rc{REDIRECTED_PUSH} ) {
|
|
|
|
trace( 3, "case 2, slave redirect" );
|
2012-03-27 18:36:43 +02:00
|
|
|
|
|
|
|
# find all slaves and push to each of them
|
|
|
|
push_to_slaves($repo);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
my $lastrepo = '';
|
|
|
|
|
|
|
|
sub details {
|
|
|
|
my $repo = shift;
|
|
|
|
return if $lastrepo eq $repo;
|
|
|
|
|
2012-04-18 02:56:18 +02:00
|
|
|
$master = master($repo);
|
|
|
|
%slaves = slaves($repo);
|
|
|
|
$mode = mode($repo);
|
2012-03-27 18:36:43 +02:00
|
|
|
%trusted_slaves = trusted_slaves($repo);
|
2012-04-18 02:56:18 +02:00
|
|
|
trace( 3, $master, $mode, join( ",", sort keys %slaves ), join( ",", sort keys %trusted_slaves ) );
|
2012-03-27 18:36:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub master {
|
2012-04-18 02:56:18 +02:00
|
|
|
return option( +shift, 'mirror.master' );
|
2012-03-27 18:36:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
sub slaves {
|
2012-04-18 02:56:18 +02:00
|
|
|
my $ref = git_config( +shift, "^gitolite-options\\.mirror\\.slaves.*" );
|
2012-03-27 18:36:43 +02:00
|
|
|
my %out = map { $_ => 1 } map { split } values %$ref;
|
|
|
|
return %out;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub trusted_slaves {
|
2012-04-18 02:56:18 +02:00
|
|
|
my $ref = git_config( +shift, "^gitolite-options\\.mirror\\.redirectOK.*" );
|
2012-03-27 18:36:43 +02:00
|
|
|
# the list of trusted slaves (where we accept redirected pushes from)
|
|
|
|
# is either explicitly given...
|
|
|
|
my @out = map { split } values %$ref;
|
|
|
|
my %out = map { $_ => 1 } @out;
|
|
|
|
# ...or it's all the slaves mentioned if the list is just a "all"
|
2012-04-18 02:56:18 +02:00
|
|
|
%out = %slaves if ( @out == 1 and $out[0] eq 'all' );
|
2012-03-27 18:36:43 +02:00
|
|
|
return %out;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub mode {
|
|
|
|
my $repo = shift;
|
2012-04-18 02:56:18 +02:00
|
|
|
return 'local' if not $hn;
|
2012-03-27 18:36:43 +02:00
|
|
|
return 'master' if $master eq $hn;
|
2012-04-18 02:56:18 +02:00
|
|
|
return 'slave' if $slaves{$hn};
|
|
|
|
return 'local' if not $master and not %slaves;
|
2012-03-27 18:36:43 +02:00
|
|
|
_die "$hn: '$repo' is mirrored but not here";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub push_to_slaves {
|
|
|
|
my $repo = shift;
|
|
|
|
|
|
|
|
my $u = $ENV{GL_USER};
|
2012-04-18 02:56:18 +02:00
|
|
|
delete $ENV{GL_USER}; # why? see src/commands/mirror
|
2012-03-27 18:36:43 +02:00
|
|
|
|
2012-04-18 02:56:18 +02:00
|
|
|
for my $s ( sort keys %slaves ) {
|
2012-03-27 18:36:43 +02:00
|
|
|
system("gitolite mirror push $s $repo &");
|
|
|
|
}
|
|
|
|
|
|
|
|
$ENV{GL_USER} = $u;
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|