linux-fai-devel  

Re: [Fai-commit] r4287 - people/h01ger

Sam Vilain
Mon, 07 May 2007 16:07:42 -0700

Ooo, softupdate and git.

What are you doing here Holger?  I have a script which runs softupdate
with various guards (eg, don't push a softupdate unless its remote
revision is in the history of the revision we're pushing).

Ah, they're rough and ready but they're attached and perhaps they'll be
interesting for you to look at.

There's also the idea of using git as a safety net for /etc (something
like the program 'IsiSetup'), so that the changes performed by a
softupdate can be easily tracked and even backed out if required.  This
could potentially kill two birds with one stone - allowing systems like
"tiger" and "integrit" to be mostly replaced with the moral equivalent
of "git-status".  In the case of vservers, intrusion detection security
could be taken to the next level by running the git safety net outside
of the vserver.  There are problems to be overcome there, such as
archiving old history, etc.  But I think the idea holds a lot of promise.

Sam.

Holger Levsen wrote:
> Author: h01ger-guest
> Date: 2007-05-04 11:27:06 +0000 (Fri, 04 May 2007)
> New Revision: 4287
>
> Added:
>    people/h01ger/softupdate-git/
> Log:
> keep two branches for now:
>
> softupdate-git: what previously softupdate-git-rpm was, but with 
> install_packages suited for debian-based systems too
> softupdate-git-rpm: get rid of depends on libapt-pkg-perl in install_packages 
> the hackish way - only for rpm only systems
>
> Besides this, I will keep those two branches in sync, and follow trunk (where 
> suited for etch based fai)
>
> I need help in fixing the above issue. This is bug #422225
>
>
>
> Copied: people/h01ger/softupdate-git (from rev 4286, 
> people/h01ger/softupdate-git-rpm)
>
>
> _______________________________________________
> Fai-commit mailing list
> [EMAIL PROTECTED]
> http://lists.alioth.debian.org/mailman/listinfo/fai-commit
>   

#!/usr/bin/perl -w
#
#     faiupdate -- connect to host, start the fai update and keep stdout
#                  for parsing in FAI::Updater
#

use FindBin qw($Bin);
use lib "$Bin/../lib";
use strict;
use warnings;

use Scriptalicious;
use GreatExpectations ":all";
use Perl6::Junction qw(any);

my $timeout = 900;
my $st = 45;
my $user;
my %options = qw( StrictHostKeyChecking no
                  ForwardAgent          no
                  ForwardX11            no
                );
my @extra_ssh_options;
my $yes;
my $new_class;

getopt("timeout|t=i" => \$timeout,
       "user|u=s" => \$user,
       "ssh-option|o=s@" => [EMAIL PROTECTED],
       "yes|y" => \$yes,
       "newclass|N" => \$new_class,
      );

barf "ENOTIMPLEMENTEDYET" if @extra_ssh_options;

my $host = shift or abort "no hostname given";
@ARGV and abort "junk at end of command: @ARGV";

system("ping -c 1 $host >/dev/null") == 0
    or barf "can't ping $host";

my $password;

if ( -t STDIN ) {
    my $x = ($user ? "$user\@" : "");
    $password = prompt_passwd("login/sudo password for $x$host: ");
    print "\n"; # HACK :)
}
else {
    alarm 1;
    defined($password = <STDIN>) or abort "no password fed on STDIN";
    alarm 0;
}

# start an interactive shell :)
my @ssh_cmd = ("ssh", (map {( "-o", "$_ $options{$_}" )} (keys %options)),
               "-q", $host);

mutter "Logging in.";
setup_expect;
$exp->spawn(@ssh_cmd);
wait_prompt:
my $sent_passwd = 0;
$exp->expect(60,
             [ qr/password: /i => sub {
                   whisper "sending password";
                   $exp->send_slow( (1/9600), $password."\r" );
                   $sent_passwd = 1;
               }, ],
             [ qr/\$ \Z/i => sub {
                   whisper "seen prompt";
               } ],
            );
goto wait_prompt if $sent_passwd;

# ok, so, first, we should check the production level of the remote
# end.
$exp->send('. /etc/catalyst-machineinfo; echo "#$ROLE#"'."\r");
my $role;
$exp->expect($st, [ qr/#(\w*)#/ => sub {
                        $role = ($exp->matchlist)[0];
                   } ]) or vomit "no role response seen";
mutter "production level of $host is ".($role||"not set");

# let's find out what we're syncing.
(my $fai_dir = $Bin) =~ s{/[^/]+/?$}{};
open GIT_STATUS, "cd $fai_dir && git-status |" or die $!;
(my $fai_branch = <GIT_STATUS>) =~ s{^# On branch (\S+)(?s:.*)}{$1}
    or barf "failed to parse git-status output";
my $dirty;
while (<GIT_STATUS>) {
    m{^nothing to commit} && next;
    $dirty++;
}
close GIT_STATUS;

# ok, so what's the commit ID
my $commit_id = `cd $fai_dir && git-rev-parse $fai_branch`
    or barf "failed to get commit ID for $fai_branch";
chomp($commit_id);
$fai_branch =~ s{refs/heads/}{};

if ( $role eq any(qw(internal production staging)) ) {
    # abort if we're not sending a tagged release
    barf "refusing to push softupdate of dirty directory to $role host"
        if $dirty;
}

# lots of sanity checks that we don't deploy the wrong branch
mutter "checking remote profile version";
$exp->send_slow(1/9600, "echo %\$CATALYST_FAI_REV,\$CATALYST_FAI_BRANCH%\r");
my ($r_commit_id, $r_fai_branch);
$exp->expect ($st, [ qr/%([0-9a-f]*),([^%]*)%/ => sub {
                        ($r_commit_id, $r_fai_branch) = ($exp->matchlist)
                    } ]) or vomit "no revision seen";

# first question, is the remote revision in the history of the one
# we're deploying?
if ( length($r_commit_id) ) {
    unless ( $r_commit_id eq $commit_id or
             grep m{\s$r_commit_id},
             `cd $fai_dir && git-rev-list --parents 
${r_commit_id}..${commit_id}` ) {
        moan("The profile revision on the target server is not an ancestor of 
the one you are deploying.  Usually this means you are deploying the wrong 
profile branch to a server.");
        if ( $yes ) {
            moan("--yes used on command-line, going ahead anyway!");
        } else {
            say("Remote: $r_commit_id, us: $commit_id");
            if ( -t STDIN ) {
                exit(1) unless prompt_yN("Are you SURE you want to do this? ");
            } else {
                barf "refusing to deploy bad revision to $host";
            }
        }
    }
    if ( $r_fai_branch ne $fai_branch ) {
        moan("The FAI branch is changing from $r_fai_branch to $fai_branch");
        if ( $yes ) {
            moan("--yes used on command-line, going ahead anyway!");
        } else {
            if ( -t STDIN ) {
                exit(1) unless prompt_yN("Are you SURE you want to do this? ");
            } else {
                barf "refusing to deploy different branch to $host";
            }
        }
    }
}
else {
    moan("$host has not been successfully FAI softupdated yet.");
    if ( $yes ) {
        moan("--yes used on command-line, going ahead anyway!");
    } else {
        if ( -t STDIN ) {
            exit(1) unless prompt_yN("Are you SURE you want to softupdate this 
host? ");
        }
        else {
            barf "Refusing to softupdate for the first time to $host";
        }
    }
}

say "sending $fai_dir ($fai_branch:".substr($commit_id,0,6)
    .($dirty?"+":"").") to $host";

my $exp_2 = Expect->new;
$exp_2->raw_pty(1);
$exp_2->log_stdout(0) unless $VERBOSE > 1;
$exp_2->spawn("rsync", "-ruvza", "--partial", "--delete",
              "--exclude", ".git*", "$fai_dir/.", "$host:fai");

my $in_rsync = 1;
while ( $in_rsync ) {
    $exp_2->expect
        ($timeout,
         [ qr/password: /i => sub {
               whisper "sending password";
               $exp_2->send_slow( (1/9600), $password."\r" );
               $sent_passwd = 1;
           }, ],
         [ qr/total size is .* speedup is/ => sub {
               $in_rsync = 0;
           } ],
        );
}
$exp_2->soft_close;

mutter "Now running softupdate";

$exp->send("echo ready\r");

$exp->send_slow
    ((1/9600),
     "sudo fai/libexec/softupdate-remote "
     .($new_class ? "--new-class " : "")
     ."$fai_branch $commit_id $dirty; exit\r");

my $in_softupdate = 0;
$sent_passwd = 0;
my $pre_state = "starting";
while ( $in_softupdate or $pre_state ) {
    $exp->expect
        ( ($pre_state ? $st : $timeout),

          ($pre_state eq "starting"
           ? ( [ qr/password:/i => sub {
                     whisper "sending password";
                     $exp->send_slow( (1/9600), $password."\r" );
                     $pre_state = "sent_passwd";
                 }, ] )
           : () ),

          ($pre_state eq any("starting", "sent_passwd")
           ? ( [ qr/softupdate-remote: starting/i => sub {
                     start_timer;
                     mutter "softupdate now in progress";
                     $exp->set_accum($exp->after);
                     $exp->log_stdout(1);
                     $pre_state = "";
                     $in_softupdate = 1;
                 }, ],
               [ qr/is not in the sudoers file|is not allowed to run sudo/
                 => sub {
                     barf "we can't sudo on this host";
                 } ],
               [ qr/Sorry, try again./ => sub {
                     barf "sudo password failed!";
                 } ],
             )
           : () ),

          [ eof => sub {
                if ( $pre_state ) {
                    vomit("saw EOF in state $pre_state");
                } else {
                    say "softupdate finished in ".show_elapsed;
                }
                $in_softupdate = 0;
                $pre_state = "";
            }
          ],

          [ timeout => sub {
                vomit("softupdate timed out in state 
${\($pre_state||'softupdate')}; hope aborting is OK");
            } ],
        );
}
#!/usr/bin/perl -w

use strict;
use FindBin qw($Bin);
use lib "$Bin/../lib";
use Getopt::Long;

die "not root" if $< and $>;

my $run_defclass;

GetOptions("new-class" => \$run_defclass);

my $fai_branch = shift;
my $commit_id = shift;
my $is_dirty = shift;

# first, check fai-client is installed and auto-install it if not
my @missing = grep {
    my $pkg = $_;
    my $dpkg_status = `dpkg -s $pkg | grep Status:`;
    !( $dpkg_status =~ m{ installed\b} );
} qw(fai-client libyaml-syck-perl libnewt-perl libperl6-junction-perl);

if ( @missing ) {
    # cross fingers...
    system("apt-get -y install @missing") == 0
        or die "could not install fai-client";
}

(my $fai_dir = $Bin) =~ s{/[^/]+/?$}{};

# second, make a link from /fai to $fai_dir
if ( -l "/fai" ) {
    unlink("/fai");
} elsif ( -d "/fai" ) {
    rename("/fai", "/fai.dead$$") or die $!;
}
symlink($fai_dir, "/fai") or die "symlink failed; $!";

# now go for it!
print "softupdate-remote: starting\n";
$ENV{NEW_CATALYST_FAI_REV} = $commit_id;
$ENV{NEW_CATALYST_FAI_BRANCH} = $fai_branch;
$ENV{NEW_CATALYST_FAI_REV_DIRTY} = ($is_dirty ? "1" : "");

system("$Bin/../bin/sai_store", "host.unattended", 1);

system("/usr/sbin/fai", "softupdate", ($run_defclass ? ("-N") : ()) );
unlink("/fai");
  • Re: [Fai-commit] r4287 - people/h01ger Sam Vilain