Created
July 18, 2016 22:34
-
-
Save caseydentinger/710be7507d9f9c1519c082392acc2f40 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 "\n[31mHey, I don't know what to do with $ARGV[0], did you mean '-p $ARGV[0]' perhaps?[0m\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[1m$conf_tree->{'name'}[0m\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[1m$proj[0m\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 "\n[1mHere's what we're planning to do this run:[0m\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 "\n[1mEek--punt![0m\n\nreminder: [1m$0 -h[0m for usage info\n\n"; | |
exit; | |
} | |
# fetch Config Tree repository unless we're in information-only mode | |
unless ($opt_i) { | |
my $r; | |
print "\n[1mrefreshing $conf_tree->{'name'}[0m\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[1m$proj[0m\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 "\n[1mDone![0m\n\n"; | |
exit; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment