mirroring without sausages
(or at least without showing the making of said sausages)
This commit is contained in:
parent
b78466b164
commit
25bb1c00db
6 changed files with 1047 additions and 0 deletions
212
src/Gitolite/Triggers/Mirroring.pm
Normal file
212
src/Gitolite/Triggers/Mirroring.pm
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
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";
|
||||||
|
my $hn = $rc{HOSTNAME};
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
sub input {
|
||||||
|
return unless $ARGV[0] =~ /^server-(\S+)$/;
|
||||||
|
|
||||||
|
# note: we treat %rc as our own internal "poor man's %ENV"
|
||||||
|
$rc{FROM_SERVER} = $1;
|
||||||
|
trace(3, "from_server: $1");
|
||||||
|
|
||||||
|
if ( $ENV{SSH_ORIGINAL_COMMAND} =~ /^USER=(\S+) SOC=(git-receive-pack '(\S+)')$/ ) {
|
||||||
|
# my ($user, $newsoc, $repo) = ($1, $2, $3);
|
||||||
|
$ENV{SSH_ORIGINAL_COMMAND} = $2;
|
||||||
|
@ARGV = ($1);
|
||||||
|
$rc{REDIRECTED_PUSH} = 1;
|
||||||
|
trace(3, "redirected_push for user $1");
|
||||||
|
} else {
|
||||||
|
# master -> slave push, no access checks needed
|
||||||
|
$ENV{GL_BYPASS_ACCESS_CHECKS} = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
my ($mode, $master, %slaves, %trusted_slaves);
|
||||||
|
|
||||||
|
sub pre_git {
|
||||||
|
return unless $hn;
|
||||||
|
# nothing, and I mean NOTHING, happens if HOSTNAME is not set
|
||||||
|
trace(1, "pre_git() on $hn");
|
||||||
|
|
||||||
|
my ($repo, $user, $aa) = @_[1, 2, 3];
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
trace(1, "mirror", "pre_git", $repo, "user=$user", "sender=$sender", "mode=$mode",
|
||||||
|
($rc{REDIRECTED_PUSH} ? ("redirected") : ()));
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# case 1: we're master or slave, normal user pushing to us
|
||||||
|
if ($user and not $rc{REDIRECTED_PUSH}) {
|
||||||
|
trace(3, "case 1, user push");
|
||||||
|
return if $mode eq 'local' or $mode eq 'master';
|
||||||
|
if ($trusted_slaves{$hn}) {
|
||||||
|
trace(3, "redirecting to $master");
|
||||||
|
trace(1, "redirect to $master");
|
||||||
|
exec("ssh", $master, "USER=$user", "SOC=$ENV{SSH_ORIGINAL_COMMAND}");
|
||||||
|
} else {
|
||||||
|
_die "$hn: pushing '$repo' to slave '$hn' not allowed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# case 2: we're slave, master pushing to us
|
||||||
|
if ($sender and not $rc{REDIRECTED_PUSH}) {
|
||||||
|
trace(3, "case 2, master push");
|
||||||
|
_die "$hn: '$repo' is local" if $mode eq 'local';
|
||||||
|
_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
|
||||||
|
if ($sender and $rc{REDIRECTED_PUSH}) {
|
||||||
|
trace(3, "case 2, slave redirect");
|
||||||
|
_die "$hn: '$repo' is local" if $mode eq 'local';
|
||||||
|
_die "$hn: '$repo' is not native" if $mode eq 'slave';
|
||||||
|
_die "$hn: '$sender' is not a valid slave for '$repo'" if not $slaves{$sender};
|
||||||
|
_die "$hn: redirection not allowed from '$sender'" if not $trusted_slaves{$sender};
|
||||||
|
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
|
||||||
|
trace(1, "post_git() on $hn");
|
||||||
|
|
||||||
|
my ($repo, $user, $aa) = @_[1, 2, 3];
|
||||||
|
# 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);
|
||||||
|
|
||||||
|
trace(1, "mirror", "post_git", $repo, "user=$user", "sender=$sender", "mode=$mode",
|
||||||
|
($rc{REDIRECTED_PUSH} ? ("redirected") : ()));
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# case 1: we're master or slave, normal user pushing to us
|
||||||
|
if ($user and not $rc{REDIRECTED_PUSH}) {
|
||||||
|
trace(3, "case 1, user push");
|
||||||
|
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
|
||||||
|
if ($sender and not $rc{REDIRECTED_PUSH}) {
|
||||||
|
trace(3, "case 2, master push");
|
||||||
|
# nothing to do
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# case 3: we're master, slave sending a redirected push to us
|
||||||
|
if ($sender and $rc{REDIRECTED_PUSH}) {
|
||||||
|
trace(3, "case 2, slave redirect");
|
||||||
|
|
||||||
|
# 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;
|
||||||
|
|
||||||
|
$master = master($repo);
|
||||||
|
%slaves = slaves($repo);
|
||||||
|
$mode = mode($repo);
|
||||||
|
%trusted_slaves = trusted_slaves($repo);
|
||||||
|
trace(3, $master, $mode, join(",", sort keys %slaves), join(",", sort keys %trusted_slaves) );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub master {
|
||||||
|
return option(+shift, 'mirror.master');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub slaves {
|
||||||
|
my $ref = git_config(+shift, "^gitolite-options\\.mirror\\.slaves.*");
|
||||||
|
my %out = map { $_ => 1 } map { split } values %$ref;
|
||||||
|
return %out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub trusted_slaves {
|
||||||
|
my $ref = git_config(+shift, "^gitolite-options\\.mirror\\.redirectOK.*");
|
||||||
|
# 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"
|
||||||
|
%out = %slaves if (@out == 1 and $out[0] eq 'all');
|
||||||
|
return %out;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub mode {
|
||||||
|
my $repo = shift;
|
||||||
|
return 'local' if not $hn;
|
||||||
|
return 'master' if $master eq $hn;
|
||||||
|
return 'slave' if $slaves{$hn};
|
||||||
|
return 'local' if not $master and not %slaves;
|
||||||
|
_die "$hn: '$repo' is mirrored but not here";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub push_to_slaves {
|
||||||
|
my $repo = shift;
|
||||||
|
|
||||||
|
my $u = $ENV{GL_USER};
|
||||||
|
delete $ENV{GL_USER}; # why? see src/commands/mirror
|
||||||
|
|
||||||
|
for my $s (sort keys %slaves) {
|
||||||
|
system("gitolite mirror push $s $repo &");
|
||||||
|
}
|
||||||
|
|
||||||
|
$ENV{GL_USER} = $u;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
69
src/commands/mirror
Executable file
69
src/commands/mirror
Executable file
|
@ -0,0 +1,69 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
my $tid;
|
||||||
|
BEGIN {
|
||||||
|
$tid = $ENV{GL_TID} || 0;
|
||||||
|
delete $ENV{GL_TID};
|
||||||
|
}
|
||||||
|
|
||||||
|
use lib $ENV{GL_BINDIR};
|
||||||
|
use Gitolite::Rc;
|
||||||
|
use Gitolite::Common;
|
||||||
|
use Gitolite::Conf::Load;
|
||||||
|
|
||||||
|
=for usage
|
||||||
|
Usage 1: gitolite mirror push <slave> <repo>
|
||||||
|
Usage 2: ssh git@master-server mirror push <slave> <repo>
|
||||||
|
|
||||||
|
Forces a push of one repo to one slave.
|
||||||
|
|
||||||
|
Usage 1 is directly on the master server. Nothing is checked; if the slave
|
||||||
|
accepts it, the push happens, even if the slave is not in any slaves
|
||||||
|
option. This is how you do delayed or lagged pushes to servers that do not
|
||||||
|
need real-time updates or have bandwidth/connectivity issues.
|
||||||
|
|
||||||
|
Usage 2 can be initiated by *any* user who has *any* gitolite access to the
|
||||||
|
master server, but it checks that the slave is in one of the slaves options
|
||||||
|
before doing the push.
|
||||||
|
=cut
|
||||||
|
|
||||||
|
usage() if not @ARGV or $ARGV[0] eq '-h';
|
||||||
|
|
||||||
|
_die "HOSTNAME not set" if not $rc{HOSTNAME};
|
||||||
|
|
||||||
|
my ($cmd, $host, $repo) = @ARGV;
|
||||||
|
usage() if not $repo;
|
||||||
|
|
||||||
|
if ($cmd eq 'push') {
|
||||||
|
valid_slave($host, $repo) if exists $ENV{GL_USER};
|
||||||
|
# will die if host not in slaves for repo
|
||||||
|
|
||||||
|
trace(1, "TID=$tid host=$host repo=$repo", "gitolite mirror push started");
|
||||||
|
_chdir($rc{GL_REPO_BASE});
|
||||||
|
_chdir("$repo.git");
|
||||||
|
|
||||||
|
my $errors = 0;
|
||||||
|
for (`git push --mirror $host:$repo 2>&1`) {
|
||||||
|
print STDERR "$_" if -t STDERR or exists $ENV{GL_USER};
|
||||||
|
chomp;
|
||||||
|
if (/FATAL/) {
|
||||||
|
$errors++;
|
||||||
|
gl_log('mirror', $_);
|
||||||
|
} else {
|
||||||
|
trace(1, "mirror: $_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub valid_slave {
|
||||||
|
my ($host, $repo) = @_;
|
||||||
|
_die "invalid repo '$repo'" unless $repo =~ $REPONAME_PATT;
|
||||||
|
|
||||||
|
my $ref = git_config($repo, "^gitolite-options\\.mirror\\.slaves.*");
|
||||||
|
my %list = map { $_ => 1 } map { split } values %$ref;
|
||||||
|
|
||||||
|
_die "'$host' not a valid slave for '$repo'" unless $list{$host};
|
||||||
|
}
|
435
t/mirror-test
Executable file
435
t/mirror-test
Executable file
|
@ -0,0 +1,435 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# you need 3 disposable userids: sam, frodo, gollum. Then the test user (say
|
||||||
|
# "g3") needs to be able to sudo into them. Put this in /etc/sudoers:
|
||||||
|
|
||||||
|
# g3 ALL = (sam,frodo,gollum) NOPASSWD: ALL
|
||||||
|
|
||||||
|
$ENV{TSH_ERREXIT} = 1;
|
||||||
|
|
||||||
|
# this is hardcoded; change it if needed
|
||||||
|
use lib "src";
|
||||||
|
use Gitolite::Test;
|
||||||
|
use Cwd;
|
||||||
|
my $workdir = getcwd();
|
||||||
|
my $h = $ENV{HOME};
|
||||||
|
my ($t, $t2); # temp vars
|
||||||
|
|
||||||
|
# basic tests
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
try "plan 152";
|
||||||
|
## try "DEF POK = !/DENIED/; !/failed to push/";
|
||||||
|
|
||||||
|
## confreset;confadd '
|
||||||
|
|
||||||
|
## ';
|
||||||
|
|
||||||
|
## try "ADMIN_PUSH set1; !/FATAL/" or die text();
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
# switch keys
|
||||||
|
sub swk {
|
||||||
|
my $h = $ENV{HOME};
|
||||||
|
my $k = shift;
|
||||||
|
system("cp $h/.ssh/$k $h/.ssh/id_rsa");
|
||||||
|
system("cp $h/.ssh/$k.pub $h/.ssh/id_rsa.pub");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub all {
|
||||||
|
try "F " . join(" ", @_);
|
||||||
|
try "S " . join(" ", @_);
|
||||||
|
try "G " . join(" ", @_);
|
||||||
|
}
|
||||||
|
|
||||||
|
try "
|
||||||
|
DEF F = sudo -u frodo -i
|
||||||
|
DEF S = sudo -u sam -i
|
||||||
|
DEF G = sudo -u gollum -i
|
||||||
|
";
|
||||||
|
|
||||||
|
my $bd = `gitolite query-rc -n GL_BINDIR`;
|
||||||
|
|
||||||
|
try "
|
||||||
|
$bd/../t/mirror-test-setup.sh; ok or die mirror setup shell script failed
|
||||||
|
/hello server-frodo, this is frodo/
|
||||||
|
/hello server-sam, this is frodo/
|
||||||
|
/hello server-gollum, this is frodo/
|
||||||
|
/hello server-frodo, this is sam/
|
||||||
|
/hello server-sam, this is sam/
|
||||||
|
/hello server-gollum, this is sam/
|
||||||
|
/hello server-frodo, this is gollum/
|
||||||
|
/hello server-sam, this is gollum/
|
||||||
|
/hello server-gollum, this is gollum/
|
||||||
|
/hello admin, this is frodo/
|
||||||
|
/Initialized empty .*/gitolite-admin.git/
|
||||||
|
/Initialized empty .*/r1.git/
|
||||||
|
/Initialized empty .*/r2.git/
|
||||||
|
/Initialized empty .*/testing.git/
|
||||||
|
/Initialized empty .*/gitolite-admin.git/
|
||||||
|
/Initialized empty .*/r1.git/
|
||||||
|
/Initialized empty .*/r2.git/
|
||||||
|
/Initialized empty .*/testing.git/
|
||||||
|
/Initialized empty .*/gitolite-admin.git/
|
||||||
|
/Initialized empty .*/r1.git/
|
||||||
|
/Initialized empty .*/r2.git/
|
||||||
|
/Initialized empty .*/testing.git/
|
||||||
|
";
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# SECTION 1: gitolite-admin shenanigans
|
||||||
|
|
||||||
|
# push to frodo and see sam and gollum change
|
||||||
|
try "
|
||||||
|
git clone frodo\@localhost:gitolite-admin fga
|
||||||
|
ok; /Cloning into 'fga'.../
|
||||||
|
cd fga; ok
|
||||||
|
cp $h/.ssh/u?.pub keydir; ok
|
||||||
|
git add keydir; ok
|
||||||
|
git commit -m 6keys; ok
|
||||||
|
git push; ok
|
||||||
|
/To frodo\@localhost:gitolite-admin/
|
||||||
|
/master -> master/
|
||||||
|
git rev-parse HEAD
|
||||||
|
";
|
||||||
|
|
||||||
|
chomp($t = text());
|
||||||
|
|
||||||
|
try "
|
||||||
|
git ls-remote sam\@localhost:gitolite-admin
|
||||||
|
ok; /$t/
|
||||||
|
git ls-remote gollum\@localhost:gitolite-admin
|
||||||
|
ok; /$t/
|
||||||
|
";
|
||||||
|
|
||||||
|
try "
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
";
|
||||||
|
|
||||||
|
# push to sam and see frodo and gollum change
|
||||||
|
try "
|
||||||
|
git clone sam\@localhost:gitolite-admin sga
|
||||||
|
ok; /Cloning into 'sga'.../
|
||||||
|
cd sga; ok
|
||||||
|
empty; ok
|
||||||
|
git push; ok
|
||||||
|
/To sam\@localhost:gitolite-admin/
|
||||||
|
/master -> master/
|
||||||
|
git rev-parse HEAD
|
||||||
|
";
|
||||||
|
|
||||||
|
chomp($t = text());
|
||||||
|
|
||||||
|
try "
|
||||||
|
git ls-remote frodo\@localhost:gitolite-admin
|
||||||
|
ok; /$t/
|
||||||
|
git ls-remote gollum\@localhost:gitolite-admin
|
||||||
|
ok; /$t/
|
||||||
|
";
|
||||||
|
|
||||||
|
try "
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
";
|
||||||
|
|
||||||
|
# push to gollum and fail at gollum
|
||||||
|
try "
|
||||||
|
git clone gollum\@localhost:gitolite-admin gga
|
||||||
|
ok; /Cloning into 'gga'.../
|
||||||
|
cd gga; ok
|
||||||
|
empty; ok
|
||||||
|
git push; !ok
|
||||||
|
!/To gollum\@localhost:gitolite-admin/
|
||||||
|
!/master -> master/
|
||||||
|
/gollum: pushing 'gitolite-admin' to slave 'gollum' not allowed/
|
||||||
|
git rev-parse HEAD
|
||||||
|
";
|
||||||
|
|
||||||
|
chomp($t2 = text());
|
||||||
|
|
||||||
|
try "
|
||||||
|
git ls-remote frodo\@localhost:gitolite-admin
|
||||||
|
ok; /$t/; !/$t2/
|
||||||
|
git ls-remote sam\@localhost:gitolite-admin
|
||||||
|
ok; /$t/; !/$t2/
|
||||||
|
git ls-remote gollum\@localhost:gitolite-admin
|
||||||
|
ok; /$t/; !/$t2/
|
||||||
|
";
|
||||||
|
|
||||||
|
# fake out the gollum failure to continue the redirected push and fail at frodo
|
||||||
|
try "
|
||||||
|
sudo -u gollum -i gitolite git-config -r gitolite-admin .
|
||||||
|
ok
|
||||||
|
/redirectOK.*sam/
|
||||||
|
!/redirectOK.*gollum/
|
||||||
|
|
||||||
|
sudo -u gollum -i bash -c 'echo repo gitolite-admin > junk'
|
||||||
|
sudo -u gollum -i bash -c 'echo option mirror.redirectOK-1 = gollum >> junk'
|
||||||
|
sudo -u gollum -i bash -c 'cat junk >> .gitolite/conf/gitolite.conf'
|
||||||
|
sudo -u gollum -i gitolite compile
|
||||||
|
sudo -u gollum -i gitolite git-config -r gitolite-admin .
|
||||||
|
ok
|
||||||
|
/redirectOK.*sam/
|
||||||
|
/redirectOK.*gollum/
|
||||||
|
|
||||||
|
git push; !ok
|
||||||
|
/frodo: redirection not allowed from 'gollum'/
|
||||||
|
!/To gollum\@localhost:gitolite-admin/
|
||||||
|
!/master -> master/
|
||||||
|
";
|
||||||
|
|
||||||
|
# reset gollum via frodo
|
||||||
|
try "
|
||||||
|
cd ..
|
||||||
|
rm -rf fga
|
||||||
|
git clone frodo\@localhost:gitolite-admin fga
|
||||||
|
ok; /Cloning into 'fga'.../
|
||||||
|
cd fga; ok
|
||||||
|
empty; ok
|
||||||
|
git push; ok
|
||||||
|
/To frodo\@localhost:gitolite-admin/
|
||||||
|
/master -> master/
|
||||||
|
|
||||||
|
sudo -u gollum -i gitolite git-config -r gitolite-admin .
|
||||||
|
ok
|
||||||
|
/redirectOK.*sam/
|
||||||
|
!/redirectOK.*gollum/
|
||||||
|
|
||||||
|
git rev-parse HEAD
|
||||||
|
";
|
||||||
|
|
||||||
|
chomp($t = text());
|
||||||
|
|
||||||
|
try "
|
||||||
|
git ls-remote sam\@localhost:gitolite-admin
|
||||||
|
ok; /$t/
|
||||||
|
git ls-remote gollum\@localhost:gitolite-admin
|
||||||
|
ok; /$t/
|
||||||
|
";
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# user repo shenanigans
|
||||||
|
|
||||||
|
# for a recap of the perms see t/mirror-test-setup.sh
|
||||||
|
|
||||||
|
try "
|
||||||
|
cd ..
|
||||||
|
pwd
|
||||||
|
/tmp/tsh_tempdir/ or die not in the right place
|
||||||
|
" or die;
|
||||||
|
|
||||||
|
swk('u1');
|
||||||
|
|
||||||
|
# u1 sam r1, R ok, W ok
|
||||||
|
try "
|
||||||
|
rm -rf fga sga gga
|
||||||
|
|
||||||
|
git clone sam\@localhost:r1 sr1
|
||||||
|
/Cloning into 'sr1'.../
|
||||||
|
/warning: You appear to have cloned an empty repository/
|
||||||
|
cd sr1
|
||||||
|
empty
|
||||||
|
git push origin master
|
||||||
|
/new branch/; /master -> master/
|
||||||
|
git rev-parse HEAD
|
||||||
|
";
|
||||||
|
chomp($t = text());
|
||||||
|
|
||||||
|
# u1 sam r1, W mirrors to frodo but not gollum
|
||||||
|
try "
|
||||||
|
git ls-remote sam\@localhost:r1
|
||||||
|
/$t/
|
||||||
|
git ls-remote frodo\@localhost:r1
|
||||||
|
/$t/
|
||||||
|
git ls-remote gollum\@localhost:r1
|
||||||
|
/gollum: 'r1' is mirrored but not here/
|
||||||
|
";
|
||||||
|
|
||||||
|
swk("u2");
|
||||||
|
try "
|
||||||
|
empty
|
||||||
|
git rev-parse HEAD
|
||||||
|
";
|
||||||
|
chomp($t2 = text());
|
||||||
|
|
||||||
|
# u2 sam r2 W ok, mirrors to all
|
||||||
|
try "
|
||||||
|
git push sam\@localhost:r2 master
|
||||||
|
/new branch/; /master -> master/
|
||||||
|
/master -> master/
|
||||||
|
git ls-remote frodo\@localhost:r2
|
||||||
|
!/$t/
|
||||||
|
/$t2/
|
||||||
|
git ls-remote gollum\@localhost:r2
|
||||||
|
!/$t/
|
||||||
|
/$t2/
|
||||||
|
";
|
||||||
|
|
||||||
|
swk("u1");
|
||||||
|
|
||||||
|
# u1 gollum r1 -- "known unknown" :-)
|
||||||
|
# u1 frodo r1 R ok, W not ok
|
||||||
|
# u1 sam r1 R ok, W ok
|
||||||
|
try "
|
||||||
|
cd ..
|
||||||
|
rm -rf sr1
|
||||||
|
|
||||||
|
git clone gollum\@localhost:r1 fr1
|
||||||
|
/gollum: 'r1' is mirrored but not here/
|
||||||
|
|
||||||
|
git clone frodo\@localhost:r1 fr1; ok
|
||||||
|
cd fr1
|
||||||
|
empty
|
||||||
|
git push
|
||||||
|
/frodo: pushing 'r1' to slave 'frodo' not allowed/
|
||||||
|
cd ..
|
||||||
|
git clone sam\@localhost:r1 sr1; ok
|
||||||
|
cd sr1
|
||||||
|
empty
|
||||||
|
git push; ok
|
||||||
|
/master -> master/
|
||||||
|
git rev-parse HEAD
|
||||||
|
";
|
||||||
|
chomp($t = text());
|
||||||
|
|
||||||
|
# u1 sam r1 W mirrored to frodo but not gollum
|
||||||
|
try "
|
||||||
|
git ls-remote sam\@localhost:r1
|
||||||
|
/$t/
|
||||||
|
git ls-remote frodo\@localhost:r1
|
||||||
|
/$t/
|
||||||
|
|
||||||
|
git ls-remote gollum\@localhost:r1
|
||||||
|
/gollum: 'r1' is mirrored but not here/
|
||||||
|
|
||||||
|
git reset --hard HEAD^; ok
|
||||||
|
tc a
|
||||||
|
git push; !ok
|
||||||
|
/rejected/
|
||||||
|
/failed to push/
|
||||||
|
|
||||||
|
git push -f
|
||||||
|
/\\+ .......\\.\\.\\........ master -> master .forced update/
|
||||||
|
";
|
||||||
|
|
||||||
|
swk("u2");
|
||||||
|
|
||||||
|
# u2 frodo r1 R ok, W not allowed (no redirectOK)
|
||||||
|
# u2 frodo r2 W ok
|
||||||
|
try "
|
||||||
|
cd ..
|
||||||
|
rm -rf fr1 sr1
|
||||||
|
|
||||||
|
git clone frodo\@localhost:r1 fr1; ok
|
||||||
|
cd fr1
|
||||||
|
tc b
|
||||||
|
git push
|
||||||
|
/frodo: pushing 'r1' to slave 'frodo' not allowed/
|
||||||
|
cd ..
|
||||||
|
git clone frodo\@localhost:r2 fr2; ok
|
||||||
|
cd fr2
|
||||||
|
tc c
|
||||||
|
git push
|
||||||
|
/master -> master/
|
||||||
|
git rev-parse HEAD
|
||||||
|
";
|
||||||
|
chomp($t = text());
|
||||||
|
|
||||||
|
# u2 frodo r2 W mirrors to sam and gollum
|
||||||
|
try "
|
||||||
|
git ls-remote sam\@localhost:r2
|
||||||
|
/$t/
|
||||||
|
git ls-remote gollum\@localhost:r2
|
||||||
|
/$t/
|
||||||
|
|
||||||
|
git reset --hard HEAD^; ok
|
||||||
|
tc d
|
||||||
|
git push
|
||||||
|
/rejected/
|
||||||
|
/failed to push/
|
||||||
|
|
||||||
|
git push -f
|
||||||
|
/\\+ .......\\.\\.\\........ master -> master .forced update/
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
rm -rf fr1 fr2
|
||||||
|
";
|
||||||
|
|
||||||
|
swk("u3");
|
||||||
|
|
||||||
|
# u3 frodo r2 R ok W ok
|
||||||
|
try "
|
||||||
|
git clone frodo\@localhost:r2 fr2; ok
|
||||||
|
cd fr2
|
||||||
|
tc e
|
||||||
|
git push; ok
|
||||||
|
|
||||||
|
git rev-parse HEAD
|
||||||
|
";
|
||||||
|
chomp($t = text());
|
||||||
|
|
||||||
|
# u3 frodo r2 W mirrors to sam and gollum
|
||||||
|
try "
|
||||||
|
git ls-remote sam\@localhost:r2
|
||||||
|
/$t/
|
||||||
|
git ls-remote gollum\@localhost:r2
|
||||||
|
/$t/
|
||||||
|
|
||||||
|
git reset --hard HEAD^; ok
|
||||||
|
tc f
|
||||||
|
git push
|
||||||
|
/rejected/
|
||||||
|
/failed to push/
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
git push -f
|
||||||
|
/\\+ refs/heads/master r2 u3 DENIED by fallthru/
|
||||||
|
/hook declined/
|
||||||
|
/rejected/
|
||||||
|
";
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# all those vague edge cases where the two servers have totally wrong ideas
|
||||||
|
# about each other
|
||||||
|
|
||||||
|
swk('u1');
|
||||||
|
|
||||||
|
try "sudo -u frodo -i ls .gitolite/logs";
|
||||||
|
chomp($t = text());
|
||||||
|
my $lfn = ".gitolite/logs/$t";
|
||||||
|
|
||||||
|
try "
|
||||||
|
ssh sam\@localhost mirror push frodo lfrodo; !ok
|
||||||
|
/FATAL: frodo: 'lfrodo' is local/
|
||||||
|
|
||||||
|
ssh sam\@localhost mirror push frodo mboth; !ok
|
||||||
|
/FATAL: frodo: 'mboth' is native/
|
||||||
|
|
||||||
|
ssh sam\@localhost mirror push frodo mnotsam; !ok
|
||||||
|
/FATAL: frodo: 'sam' is not the master for 'mnotsam'/
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
git clone sam\@localhost:lfrodo2 lfrodo2; ok
|
||||||
|
cd lfrodo2
|
||||||
|
empty
|
||||||
|
git push origin master; !ok
|
||||||
|
/FATAL: frodo: 'lfrodo2' is local/
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
git clone sam\@localhost:nnfrodo nnfrodo; ok
|
||||||
|
cd nnfrodo
|
||||||
|
empty
|
||||||
|
git push origin master; !ok
|
||||||
|
/FATAL: frodo: 'nnfrodo' is not native/
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
git clone sam\@localhost:nvsfrodo nvsfrodo; ok
|
||||||
|
cd nvsfrodo
|
||||||
|
empty
|
||||||
|
git push origin master; !ok
|
||||||
|
/FATAL: frodo: 'sam' is not a valid slave for 'nvsfrodo'/
|
||||||
|
";
|
127
t/mirror-test-rc
Normal file
127
t/mirror-test-rc
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
# configuration variables for gitolite
|
||||||
|
|
||||||
|
# This file is in perl syntax. But you do NOT need to know perl to edit it --
|
||||||
|
# just mind the commas, use single quotes unless you know what you're doing,
|
||||||
|
# and make sure the brackets and braces stay matched up!
|
||||||
|
|
||||||
|
# (Tip: perl allows a comma after the last item in a list also!)
|
||||||
|
|
||||||
|
%RC = (
|
||||||
|
HOSTNAME => '%HOSTNAME',
|
||||||
|
UMASK => 0077,
|
||||||
|
GIT_CONFIG_KEYS => '',
|
||||||
|
|
||||||
|
# comment out if you don't need all the extra detail in the logfile
|
||||||
|
LOG_EXTRA => 1,
|
||||||
|
|
||||||
|
# settings used by external programs; uncomment and change as needed. You
|
||||||
|
# can add your own variables for use in your own external programs; take a
|
||||||
|
# look at the cpu-time and desc commands for perl and shell samples.
|
||||||
|
|
||||||
|
# used by the cpu-time command
|
||||||
|
# DISPLAY_CPU_TIME => 1,
|
||||||
|
# CPU_TIME_WARN_LIMIT => 0.1,
|
||||||
|
# used by the desc command
|
||||||
|
# WRITER_CAN_UPDATE_DESC => 1,
|
||||||
|
# used by the info command
|
||||||
|
# SITE_INFO => 'Please see http://blahblah/gitolite for more help',
|
||||||
|
|
||||||
|
# add more roles (like MANAGER, TESTER, ...) here.
|
||||||
|
# WARNING: if you make changes to this hash, you MUST run 'gitolite
|
||||||
|
# compile' afterward, and possibly also 'gitolite trigger POST_COMPILE'
|
||||||
|
ROLES =>
|
||||||
|
{
|
||||||
|
READERS => 1,
|
||||||
|
WRITERS => 1,
|
||||||
|
},
|
||||||
|
# uncomment (and change) this if you wish
|
||||||
|
# DEFAULT_ROLE_PERMS => 'READERS @all',
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these are available to remote users
|
||||||
|
COMMANDS =>
|
||||||
|
{
|
||||||
|
'help' => 1,
|
||||||
|
'info' => 1,
|
||||||
|
'desc' => 1,
|
||||||
|
'perms' => 1,
|
||||||
|
'mirror' => 1,
|
||||||
|
'writable' => 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence during the conf file parse
|
||||||
|
SYNTACTIC_SUGAR =>
|
||||||
|
[
|
||||||
|
# 'continuation-lines',
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence to modify the input (arguments and environment)
|
||||||
|
INPUT =>
|
||||||
|
[
|
||||||
|
'Mirroring::input',
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence just after the first access check is done
|
||||||
|
ACCESS_1 =>
|
||||||
|
[
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence at the start, before a git operation has started
|
||||||
|
PRE_GIT =>
|
||||||
|
[
|
||||||
|
# if you use this, make this the first item in the list
|
||||||
|
# 'renice 10',
|
||||||
|
|
||||||
|
'Mirroring::pre_git',
|
||||||
|
|
||||||
|
# see docs ("list of non-core programs shipped") for details
|
||||||
|
# 'partial-copy',
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence just after the second access check is done
|
||||||
|
ACCESS_2 =>
|
||||||
|
[
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence at the end, after a git operation has ended
|
||||||
|
POST_GIT =>
|
||||||
|
[
|
||||||
|
# if you use this, make this the last item in the list
|
||||||
|
# 'cpu-time',
|
||||||
|
'Mirroring::post_git',
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence after a new wild repo is created
|
||||||
|
POST_CREATE =>
|
||||||
|
[
|
||||||
|
'post-compile/update-git-configs',
|
||||||
|
'post-compile/update-gitweb-access-list',
|
||||||
|
'post-compile/update-git-daemon-access-list',
|
||||||
|
],
|
||||||
|
|
||||||
|
# comment out or uncomment as needed
|
||||||
|
# these will run in sequence after post-update
|
||||||
|
POST_COMPILE =>
|
||||||
|
[
|
||||||
|
'post-compile/ssh-authkeys',
|
||||||
|
'post-compile/update-git-configs',
|
||||||
|
'post-compile/update-gitweb-access-list',
|
||||||
|
'post-compile/update-git-daemon-access-list',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# per perl rules, this should be the last line in such a file:
|
||||||
|
1;
|
||||||
|
|
||||||
|
# Local variables:
|
||||||
|
# mode: perl
|
||||||
|
# End:
|
||||||
|
# vim: set syn=perl:
|
193
t/mirror-test-setup.sh
Executable file
193
t/mirror-test-setup.sh
Executable file
|
@ -0,0 +1,193 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
hosts="frodo sam gollum"
|
||||||
|
mainhost=frodo
|
||||||
|
|
||||||
|
# setup software
|
||||||
|
bd=`gitolite query-rc -n GL_BINDIR`
|
||||||
|
rm -rf /tmp/g3/src
|
||||||
|
cp -a $bd /tmp/g3/src
|
||||||
|
chmod -R go+rX /tmp/g3
|
||||||
|
|
||||||
|
# setup symlinks in frodo, sam, and gollum's accounts
|
||||||
|
for h in $hosts
|
||||||
|
do
|
||||||
|
sudo -u $h -i bash -c "rm -rf *.pub bin .ssh projects.list repositories .gitolite .gitolite.rc"
|
||||||
|
done
|
||||||
|
|
||||||
|
[ "$1" = "clear" ] && exit
|
||||||
|
|
||||||
|
cd /tmp/g3
|
||||||
|
[ -d keys ] || {
|
||||||
|
mkdir keys
|
||||||
|
cd keys
|
||||||
|
for h in $hosts
|
||||||
|
do
|
||||||
|
ssh-keygen -N '' -q -f server-$h -C $h
|
||||||
|
chmod go+r /tmp/g3/keys/server-$h
|
||||||
|
done
|
||||||
|
cp $bd/../t/mirror-test-ssh-config ssh-config
|
||||||
|
}
|
||||||
|
|
||||||
|
for h in $hosts
|
||||||
|
do
|
||||||
|
sudo -u $h -i bash -c "mkdir -p bin; ln -sf /tmp/g3/src/gitolite bin; mkdir -p .ssh; chmod 0700 .ssh"
|
||||||
|
|
||||||
|
sudo -u $h -i cp /tmp/g3/keys/ssh-config .ssh/config
|
||||||
|
sudo -u $h -i cp /tmp/g3/keys/server-$h .ssh/id_rsa
|
||||||
|
sudo -u $h -i cp /tmp/g3/keys/server-$h.pub .ssh/id_rsa.pub
|
||||||
|
sudo -u $h -i chmod go-rwx .ssh/id_rsa .ssh/config
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
# add all pubkeys to all servers
|
||||||
|
for h in $hosts
|
||||||
|
do
|
||||||
|
sudo -u $h -i gitolite setup -a admin
|
||||||
|
for j in $hosts
|
||||||
|
do
|
||||||
|
sudo -u $h -i gitolite setup -pk /tmp/g3/keys/server-$j.pub
|
||||||
|
echo sudo _u $j _i ssh $h@localhost info
|
||||||
|
sudo -u $j -i ssh -o StrictHostKeyChecking=no $h@localhost info
|
||||||
|
done
|
||||||
|
echo ----
|
||||||
|
done
|
||||||
|
|
||||||
|
# now copy our admin key to the main host
|
||||||
|
cd;cd .ssh
|
||||||
|
cp admin id_rsa; cp admin.pub id_rsa.pub
|
||||||
|
cp admin.pub /tmp/g3/keys; chmod go+r /tmp/g3/keys/admin.pub
|
||||||
|
sudo -u $mainhost -i gitolite setup -pk /tmp/g3/keys/admin.pub
|
||||||
|
ssh $mainhost@localhost info
|
||||||
|
|
||||||
|
lines="
|
||||||
|
repo gitolite-admin
|
||||||
|
option mirror.master = frodo
|
||||||
|
option mirror.slaves-1 = sam gollum
|
||||||
|
option mirror.redirectOK = sam
|
||||||
|
|
||||||
|
repo r1
|
||||||
|
RW+ = u1
|
||||||
|
RW = u2
|
||||||
|
R = u3
|
||||||
|
option mirror.master = sam
|
||||||
|
option mirror.slaves-1 = frodo
|
||||||
|
|
||||||
|
repo r2
|
||||||
|
RW+ = u2
|
||||||
|
RW = u3
|
||||||
|
R = u4
|
||||||
|
option mirror.master = sam
|
||||||
|
option mirror.slaves-1 = frodo gollum
|
||||||
|
option mirror.redirectOK = all
|
||||||
|
|
||||||
|
include \"%HOSTNAME.conf\"
|
||||||
|
"
|
||||||
|
|
||||||
|
lines2="
|
||||||
|
repo l-%HOSTNAME
|
||||||
|
RW = u1
|
||||||
|
"
|
||||||
|
|
||||||
|
# for each server, set the HOSTNAME to the rc, add the mirror options to the
|
||||||
|
# conf file, and compile
|
||||||
|
for h in $hosts
|
||||||
|
do
|
||||||
|
cat $bd/../t/mirror-test-rc | perl -pe "s/%HOSTNAME/$h/" > /tmp/g3/temp
|
||||||
|
sudo -u $h -i cp /tmp/g3/temp .gitolite.rc
|
||||||
|
echo "$lines" | sudo -u $h -i sh -c 'cat >> .gitolite/conf/gitolite.conf'
|
||||||
|
echo "$lines2" | sudo -u $h -i sh -c "cat >> .gitolite/conf/$h.conf"
|
||||||
|
sudo -u $h -i gitolite setup
|
||||||
|
done
|
||||||
|
|
||||||
|
# goes on frodo
|
||||||
|
lines="
|
||||||
|
# local to frodo but sam thinks frodo is a slave
|
||||||
|
repo lfrodo
|
||||||
|
RW = u1
|
||||||
|
|
||||||
|
# both think they're master
|
||||||
|
repo mboth
|
||||||
|
RW = u1
|
||||||
|
option mirror.master = frodo
|
||||||
|
option mirror.slaves = sam
|
||||||
|
|
||||||
|
# frodo thinks someone else is the master but sam thinks he is
|
||||||
|
repo mnotsam
|
||||||
|
RW = u1
|
||||||
|
option mirror.master = merry
|
||||||
|
option mirror.slaves = frodo
|
||||||
|
|
||||||
|
# local to frodo but sam thinks frodo is a master and redirect is OK
|
||||||
|
repo lfrodo2
|
||||||
|
RW = u1
|
||||||
|
|
||||||
|
# non-native to frodo but sam thinks frodo is master
|
||||||
|
repo nnfrodo
|
||||||
|
RW = u1
|
||||||
|
option mirror.master = gollum
|
||||||
|
option mirror.slaves = frodo
|
||||||
|
option mirror.redirectOK = all
|
||||||
|
|
||||||
|
# sam is not a valid slave to send stuff to frodo
|
||||||
|
repo nvsfrodo
|
||||||
|
RW = u1
|
||||||
|
option mirror.master = frodo
|
||||||
|
option mirror.slaves = gollum
|
||||||
|
option mirror.redirectOK = all
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "$lines" | sudo -u frodo -i sh -c "cat >> .gitolite/conf/frodo.conf"
|
||||||
|
|
||||||
|
# goes on sam
|
||||||
|
lines="
|
||||||
|
# local to frodo but sam thinks frodo is a slave
|
||||||
|
repo lfrodo
|
||||||
|
RW = u1
|
||||||
|
option mirror.master = sam
|
||||||
|
option mirror.slaves = frodo
|
||||||
|
|
||||||
|
# both think they're master
|
||||||
|
repo mboth
|
||||||
|
RW = u1
|
||||||
|
option mirror.master = sam
|
||||||
|
option mirror.slaves = frodo
|
||||||
|
|
||||||
|
# frodo thinks someone else is the master but sam thinks he is
|
||||||
|
repo mnotsam
|
||||||
|
RW = u1
|
||||||
|
option mirror.master = sam
|
||||||
|
option mirror.slaves = frodo
|
||||||
|
|
||||||
|
# local to frodo but sam thinks frodo is a master and redirect is OK
|
||||||
|
repo lfrodo2
|
||||||
|
RW = u1
|
||||||
|
option mirror.master = frodo
|
||||||
|
option mirror.slaves = sam
|
||||||
|
option mirror.redirectOK = all
|
||||||
|
|
||||||
|
# non-native to frodo but sam thinks frodo is master
|
||||||
|
repo nnfrodo
|
||||||
|
RW = u1
|
||||||
|
option mirror.master = frodo
|
||||||
|
option mirror.slaves = sam
|
||||||
|
option mirror.redirectOK = all
|
||||||
|
|
||||||
|
# sam is not a valid slave to send stuff to frodo
|
||||||
|
repo nvsfrodo
|
||||||
|
RW = u1
|
||||||
|
option mirror.master = frodo
|
||||||
|
option mirror.slaves = sam
|
||||||
|
option mirror.redirectOK = all
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "$lines" | sudo -u sam -i sh -c "cat >> .gitolite/conf/sam.conf"
|
||||||
|
|
||||||
|
for h in $hosts
|
||||||
|
do
|
||||||
|
sudo -u $h -i gitolite setup
|
||||||
|
done
|
||||||
|
|
||||||
|
# that ends the setup phase
|
||||||
|
echo ======================================================================
|
11
t/mirror-test-ssh-config
Normal file
11
t/mirror-test-ssh-config
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
host frodo
|
||||||
|
user frodo
|
||||||
|
hostname localhost
|
||||||
|
|
||||||
|
host sam
|
||||||
|
user sam
|
||||||
|
hostname localhost
|
||||||
|
|
||||||
|
host gollum
|
||||||
|
user gollum
|
||||||
|
hostname localhost
|
Loading…
Reference in a new issue