Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save caseydentinger/710be7507d9f9c1519c082392acc2f40 to your computer and use it in GitHub Desktop.
Save caseydentinger/710be7507d9f9c1519c082392acc2f40 to your computer and use it in GitHub Desktop.
#!/usr/bin/perl
# frack-puppet:files/default/usr/local/bin/fundraising_code_update
use strict;
use Getopt::Std;
use Storable qw(nstore retrieve);
use FrDeploy;
# sanity check then import config file
my ($project,$program,$sync) = FrDeploy::readconf();
# exit unless invoked as configured local user
FrDeploy::checkuser($program->{'local_user'});
# collect command-line options
our ($opt_d,$opt_h,$opt_i,$opt_p);
Getopt::Std::getopts ("dhip:");
# file where state is kept between runs, make sure dir exists
my $ident = ($0 =~ /([^\/]+)$/) ? $1 : $0;
my $state_file = "$program->{'state_dir'}/$ident.state";
FrDeploy::rmkdir($program->{'state_dir'}) unless -d $program->{'state_dir'};
# set 'interactive session' flag based on whether filehandles are open
my $interactive = 1 if (-t STDIN && -t STDOUT);
# load up previous state if state_file exists
my $state = Storable::retrieve($state_file) if -e $state_file;
# we're not expecting anything on @ARGV, so barf and trigger the help page
if (defined $ARGV[0]) {
print "\nHey, I don't know what to do with $ARGV[0], did you mean '-p $ARGV[0]' perhaps?\n";
$opt_h = 1;
}
# Config Tree is a special 'project' for installable configuration files, which
# needs to refresh before other projects, and does not employ revision rollback
# or locking.
my $conf_tree = $program->{'config_repository'};
$conf_tree->{'install'} = "$conf_tree->{'dir'}/$conf_tree->{'loc'}";
$conf_tree->{'attr'} = $program->{'default_attributes'} unless defined $conf_tree->{'attr'};
$conf_tree->{'debug'} = 1 if defined $opt_d;
$conf_tree->{'name'} = "Config Tree";
# do initial ConfigTree checkout as necessary
my $c;
if (-d "$conf_tree->{'install'}/.git") {
$c = FrDeploy::cc($conf_tree,'revision');
} else {
FrDeploy::rmkdir($conf_tree->{'dir'});
die ("$conf_tree->{'dir'} missing! ($conf_tree->{'name'})") unless -d $conf_tree->{'dir'};
print " checkout to $conf_tree->{'install'}\n";
$c = FrDeploy::cc($conf_tree,'checkout');
}
$state->{'Config Tree'}->{'revision'} = $c->{'revision'};
# help has been requested
if ($opt_h) {
print "\nUsage: $0 [OPTION]\n\n" .
" -p project1,project2=REVISION\n\n" .
" By default, only projects configured autoupdate=y are updated and\n" .
" updates follow the last revision setting (see below). The -p option\n" .
" overrides the default behavior with specific projects or revisions\n\n" .
" REVISION:\n" .
" =head set project to track branch head (normal operation)\n" .
" =123456 lock project to a specific revision/commit\n" .
" =rollback roll back to the previously installed revision/commit and lock\n\n" .
" -h display this informative help page\n\n" .
" -d debug\n\n" .
" -i information about installed projects\n\n";
exit;
}
# display information on the special Config Tree project
if ($opt_i) {
print "\n$conf_tree->{'name'}\n";
print " repo: $conf_tree->{'repo'}\n" if defined $conf_tree->{'repo'};
print " branch: $conf_tree->{'branch'}\n" if defined $conf_tree->{'branch'};
print " install: $conf_tree->{'install'}\n";
print " revision: $state->{'Config Tree'}->{'revision'}\n" if defined $state->{'Config Tree'}->{'revision'};
}
# loop through project defs and sanity check them
my @do_project;
for my $proj (sort keys %{$project}) {
# sanity check project location spec
die ("no 'loc' defined for $proj") unless defined $project->{$proj}->{'loc'};
# prepopulate a couple handy project variables
$project->{$proj}->{'name'} = $proj;
$project->{$proj}->{'install'} = "$project->{$proj}->{'dir'}/$project->{$proj}->{'loc'}";
$project->{$proj}->{'attr'} = $program->{'default_attributes'} unless defined $project->{$proj}->{'attr'};
$project->{$proj}->{'laststate'} = $state->{$proj};
$project->{$proj}->{'debug'} = 1 if defined $opt_d;
$project->{$proj}->{'config_dir'} = "$conf_tree->{'install'}/$proj";
# build the default project list to update
push @do_project, $proj if $project->{$proj}->{'autoupdate'} eq 'y';
if ($opt_i) {
print "\n$proj\n";
print " repo: $project->{$proj}->{'repo'}\n" if defined $project->{$proj}->{'repo'};
print " branch: $project->{$proj}->{'branch'}\n" if defined $project->{$proj}->{'branch'};
print " install: $project->{$proj}->{'install'}\n";
print " autoupdate: $project->{$proj}->{'autoupdate'}\n" if defined $project->{$proj}->{'autoupdate'};
}
if (-d $project->{$proj}->{'install'}) {
if (defined $project->{$proj}->{'repo'}) {
# check on-disk revision against what was observed at last run
# and invalidate rollback if something else is doing updates
my $r;
$r = FrDeploy::vc($project->{$proj},'revision');
if ((defined $state->{$proj}->{'revision'}) and ($state->{$proj}->{'revision'} ne $r->{'revision'})) {
print " warning on-disk revision ($r->{'revision'}) has changed unexpectedly\n";
delete $state->{$proj}->{'rollback'};
}
$state->{$proj}->{'revision'} = $r->{'revision'};
if (defined $opt_i) {
if ($state->{$proj}->{'revision-lock'}) {
print " revision: $state->{$proj}->{'revision-lock'} (locked)\n";
} else {
print " revision: $state->{$proj}->{'revision'}\n";
}
print " rollback: $state->{$proj}->{'rollback'}\n" if defined $state->{$proj}->{'rollback'};
}
}
} else {
print " is not installed!?\n" if defined $opt_i;
delete $state->{$proj};
}
}
# get some run parameters
if ($opt_i) { # info flag was thrown, no updates ok!
print "\n";
exit;
} elsif ($opt_p) {
# if -p was thrown, override autoupdate=y with requested list of projects to update
undef @do_project;
my @project_input_errors;
for my $proj (split /,/, $opt_p) {
my $rev_lock;
if ($proj =~ /^([-\w]+)=(head|rollback|\d+|[0-9a-f]{6,40})$/i) {
$proj = $1;
$rev_lock = $2;
}
if (defined $project->{$proj}) {
push @do_project, $proj;
# for any instance of -p <project>=<revision> set appropriate revision-lock flag
if (defined $rev_lock) {
if ($rev_lock =~ /head/i) {
delete $project->{$proj}->{'revision-lock'};
delete $state->{$proj}->{'revision-lock'};
} elsif ($rev_lock =~ /rollback/i) {
if (defined $state->{$proj}->{'rollback'}) {
$project->{$proj}->{'revision-lock'} = $state->{$proj}->{'rollback'};
} else {
die ("sorry, can't roll back because prior revision is unknown");
}
} else {
$project->{$proj}->{'revision-lock'} = $rev_lock;
}
}
} else {
push @project_input_errors, $proj;
}
}
die ("bad project name(s) input: '" . join(',', @project_input_errors) . "'") if @project_input_errors;
}
# preview
print "\nHere's what we're planning to do this run:\n";
print " $conf_tree->{'name'} ($conf_tree->{'install'})\n" .
" $state->{'Config Tree'}->{'revision'} --> head\n\n";
for my $proj (@do_project) {
print " $proj ($project->{$proj}->{'install'})\n" .
" $state->{$proj}->{'revision'}";
if ($project->{$proj}->{'revision-lock'}) {
print " --> $project->{$proj}->{'revision-lock'}\n";
} elsif (defined $state->{$proj}->{'revision-lock'}) {
print " (locked)\n";
} else {
print " --> head\n";
}
}
my $proceed = FrDeploy::affirm("Look ok?","yes");
if ($proceed eq "no") {
print "\nEek--punt!\n\nreminder: $0 -h for usage info\n\n";
exit;
}
# fetch Config Tree repository unless we're in information-only mode
unless ($opt_i) {
my $r;
print "\nrefreshing $conf_tree->{'name'}\n";
# update/install depending on what we find
print " update of $conf_tree->{'install'}\n";
$r = FrDeploy::cc($conf_tree,'update');
$state->{'Config Tree'}->{'revision'} = $r->{'revision'};
print " revision is $r->{'revision'}\n";
FrDeploy::printlog($ident,"revision for $conf_tree->{'name'} is $r->{'revision'}");
FrDeploy::printlog('log_to_irc',"$ident: revision for $conf_tree->{'name'} is $r->{'revision'}");
}
# walk the list of requested projects
for my $proj (@do_project) {
my $r;
die ("no project \"$proj\" defined") unless defined $project->{$proj};
print "\n$proj\n";
# make project dir as necessary
FrDeploy::rmkdir($project->{$proj}->{'dir'}) unless -d $project->{$proj}->{'dir'};
die ("$project->{$proj}->{'dir'} missing! ($proj)") unless -d $project->{$proj}->{'dir'};
# update/install depending on what we find
if (-d "$project->{$proj}->{'install'}/.git") { # stupid test of whether checkout has been done
print " update of $project->{$proj}->{'install'}\n";
$r = FrDeploy::vc($project->{$proj},'update');
} else { # no checkout whatsoever, start fresh
print " checkout to $project->{$proj}->{'install'}\n";
$r = FrDeploy::vc($project->{$proj},'checkout');
}
# set rollback revision to the last revision if there's an update
if ($r->{'revision'} ne $state->{$proj}->{'revision'}) {
print " revision for $proj changed...\n ";
my $change .= " from $state->{$proj}->{'revision'}" if defined $state->{$proj}->{'revision'};
$change .= " to $r->{'revision'}";
print "$change\n";
FrDeploy::printlog($ident,"revision for $proj changed $change");
FrDeploy::printlog('log_to_irc',"$ident: revision for $proj changed $change");
$state->{$proj}->{'rollback'} = $state->{$proj}->{'revision'};
$state->{$proj}->{'revision'} = $r->{'revision'};
} else {
print " revision is $r->{'revision'}\n";
FrDeploy::printlog($ident,"revision for $proj is $r->{'revision'}");
FrDeploy::printlog('log_to_irc',"$ident: revision for $proj is $r->{'revision'}");
}
# check on outcome of an attempted revision lock
if (defined $project->{$proj}->{'revision-lock'}) {
if ($r->{'revision'} =~ /^$project->{$proj}->{'revision-lock'}/) { # hack to support short git hashes
print " revision for $proj locked to $r->{'revision'}\n";
FrDeploy::printlog($ident,"revision for $proj locked to $r->{'revision'}");
FrDeploy::printlog('log_to_irc',"$ident: revision for $proj locked to $r->{'revision'}");
$state->{$proj}->{'revision-lock'} = $r->{'revision'};
} else {
die (" revision lock $proj to $project->{$proj}->{'revision-lock'} was unsuccessful!");
}
}
}
# store the current state
Storable::nstore $state, $state_file or die "couldn't write $state_file: $!\n" if $state;
print "\nDone!\n\n";
exit;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment