template_commit.in 12.1 KB
Newer Older
1 2 3
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2006, 2007 University of Utah and the Flux Group.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
# All rights reserved.
#
use English;
use strict;
use Getopt::Std;
use POSIX qw(isatty setsid);
use POSIX qw(strftime);
use Errno qw(EDQUOT);
use XML::Simple;
use Data::Dumper;

#
# Commit/Modify a template.
#
# Exit codes are important; they tell the web page what has happened so
# it can say something useful to the user. Fatal errors are mostly done
# with die(), but expected errors use this routine. At some point we will
# use the DB to communicate the actual error.
#
# $status < 0 - Fatal error. Something went wrong we did not expect.
# $status = 0 - Everything okay.
# $status > 0 - Expected error. User not allowed for some reason. 
# 
sub usage()
{
    print(STDERR
31
	  "Usage: template_commit [-q] -f <path>\n".
32
	  "       template_commit [-q] [-e eid | -r tag] <guid/vers>\n".
33
	  "       template_commit [-q] -p pid -e eid\n".
34 35 36
	  "switches and arguments:\n".
	  "-q          - be less chatty\n".
	  "-e <eid>    - Experiment instance to commit from\n".
37 38
	  "-E <str>    - A pithy sentence describing the new template\n".
	  "-t <tid>    - The template name (alphanumeric, no blanks)\n".
39
	  "-p <pid>    - Project for -e option\n".
40 41 42
	  "<guid/vers> - GUID and version to swapin\n");
    exit(-1);
}
43
my $optlist	 = "qe:dp:f:t:E:r:";
44 45 46
my %options      = ();
my $quiet        = 0;
my $debug        = 0;
47
my $frompath;
48 49 50
my $repotag;
my $tid;
my $description;
51
my $eid;
52
my $pid;
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
my $guid;
my $version;

#
# Configure variables
#
my $TB		= "@prefix@";
my $EVENTSYS	= @EVENTSYS@;
my $TBOPS	= "@TBOPSEMAIL@";
my $TBLOGS	= "@TBLOGSEMAIL@";
my $TBDOCBASE	= "@TBDOCBASE@";
my $TBBASE	= "@TBBASE@";
my $CONTROL     = "@USERNODE@";
my $checkquota  = "$TB/sbin/checkquota";
my $modify      = "$TB/bin/template_create";
my $archcontrol = "$TB/bin/archive_control";
my $TAR         = "/usr/bin/tar";
70
my $RSYNC	= "/usr/local/bin/rsync";
71 72 73 74

# Locals
my $template;
my $child_template;
75
my $cookie;
76 77 78 79 80 81

# Protos
sub ParseArgs();
sub fatal($$);
sub sighandler($);
sub cleanup();
82
sub CommitFromRepo();
83 84
sub CommitFromInstance();
sub CommitFromTemplate();
85
sub CommitFromCheckout();
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118

#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use libtblog;
use Template;
use Experiment;

#
# Turn off line buffering on output
#
$| = 1;

#
# Set umask for start/swap. We want other members in the project to be
# able to swap/end experiments, so the log and intermediate files need
# to be 664 since some are opened for append.
#
umask(0002);

#
# Untaint the path
#
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

#
# Verify user and get his DB uid.
#
119 120 121
my $this_user = User->ThisUser();
if (! defined($this_user)) {
    tbdie("You ($UID) do not exist!");
122 123 124 125 126 127 128 129
}

# Now parse arguments.
ParseArgs();

#
# Grab template info and do access check.
#
130 131 132 133 134 135 136 137 138
if (defined($pid)) {
    # Eid is also defined.
    my $experiment = Experiment->Lookup($pid, $eid);
    
    if (!defined($experiment)) {
	fatal(-1, "Could not get experiment record for experiment $pid/$eid!");
    }

    $template = Template->LookupByExptidx($experiment->idx());
139

140 141 142 143 144
    if (!defined($template)) {
	tbdie("Cannot find template for $pid/$eid!");
    }
    $guid    = $template->guid();
    $version = $template->vers();
145
}
146
else {
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
    if (defined($frompath)) {
	#
	# Figure out what template from cookie file.
	#
	$cookie  = "$frompath/.template";

	if (open(COOKIE, $cookie)) {
	    while (<COOKIE>) {
		if ($_ =~ /^GUID:\s*([\w]*)\/([\d]*)$/) {
		    $guid    = $1;
		    $version = $2;
		}
	    }
	    close(COOKIE);
	}
	else {
	    tbdie("Could not open $cookie!");
	}
	if (!(defined($guid) && defined($version))) {
	    tbdie("Could not parse $cookie!");
	}
    }
169 170 171 172 173 174 175
    $template = Template->Lookup($guid, $version);

    if (!defined($template)) {
	tbdie("Experiment template $guid/$version does not exist!");
    }
}

176
if (! $template->AccessCheck($this_user, TB_EXPT_MODIFY)) {
177 178 179 180 181 182 183 184 185
    tberror("You do not have permission to commit template $guid/$version");
    exit(1);
}

#
# Catch this so we can clean up.
#
$SIG{TERM} = \&sighandler;

186 187
if (defined($repotag)) {
    CommitFromRepo();
188
}
189 190 191
elsif (defined($eid)) {
    CommitFromInstance();
}
192
else {
193 194 195 196 197 198 199 200 201
    tbdie("Unsupported template commit operation!");
    
    if (defined($frompath)) {
	CommitFromCheckout();
    }
    else {
	CommitFromTemplate();
    }
}
202 203
exit(0);

204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
#
# Commit from a Repo.
#
sub CommitFromRepo()
{
    my $pid     = $template->pid();
    my $gid     = $template->gid();
    my $optarg  = ($quiet ? "-q" : "");

    # Optional override from commandline
    $tid = $template->tid()
	if (!defined($tid));
    
    #
    # Template modify to give us a new version. Giving it $frompath causes
    # the datastore to be imported from the directory instead of the parent
    # template.
    #
    system("$modify -w -m $guid/$version $optarg -g $gid -r $repotag ".
	   "$pid $tid");
    if ($?) {
	fatal($? >> 8, "Failed to modify $template!");
    }
    # Pick up changes to child guid/vers.
    $template->Refresh();
    my $child_guid = $template->child_guid();
    my $child_vers = $template->child_vers();
    my $child      = Template->Lookup($child_guid, $child_vers);
    if (!defined($child)) {
	fatal(-1, "Lookup of child template failed!");
    }
    $child->SetDescription($description)
	if (defined($description));

    return 0;
}

241 242 243 244 245 246 247 248 249 250
#
# Commit from a checkout. 
#
sub CommitFromCheckout()
{
    my $pid     = $template->pid();
    my $gid     = $template->gid();
    my $nsfile  = "$frompath/tbdata/nsfile.ns";
    my $optarg  = ($quiet ? "-q" : "");

251 252 253 254
    # Optional override from commandline
    $tid = $template->tid()
	if (!defined($tid));
    
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
    #
    # The NS file is taken from the checkout.
    #
    fatal(1, "There is no NS file in $frompath/tbdata!")
	if (! -e $nsfile);
  
    #
    # Template modify to give us a new version. Giving it $frompath causes
    # the datastore to be imported from the directory instead of the parent
    # template.
    #
    system("$modify -m $guid/$version -w $optarg -g $gid -f $frompath ".
	   "$pid $tid $nsfile");
    if ($?) {
	fatal($? >> 8, "Failed to modify $template!");
    }
    # Pick up changes to child guid/vers.
    $template->Refresh();
    my $child_guid = $template->child_guid();
    my $child_vers = $template->child_vers();
275 276 277 278 279 280
    my $child      = Template->Lookup($child_guid, $child_vers);
    if (!defined($child)) {
	fatal(-1, "Lookup of child template failed!");
    }
    $child->SetDescription($description)
	if (defined($description));
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296

    # Update the cookie so that the checkout refers to the new template
    # not the original template.
    unlink($cookie)
	if (-e $cookie);

    open(COOKIE, "> $cookie") or
	fatal(-1, "Could not create $cookie\n");
    print COOKIE "# Do not remove this file!\n";
    print COOKIE "GUID: $child_guid/$child_vers\n";
    print COOKIE "TIME: " . time() . "\n";
    close(COOKIE);

    return 0;
}

297 298 299 300 301 302 303 304
#
# Commit a template. This is basically a template modify operation.
#
sub CommitFromTemplate()
{
    my $pid     = $template->pid();
    my $gid     = $template->gid();
    my $userdir = $template->path();
305
    my $nsfile  = "$userdir/tbdata/nsfile.ns";
306 307
    my $optarg  = ($quiet ? "-q" : "");
    
308 309 310 311
    # Optional override from commandline
    $tid = $template->tid()
	if (!defined($tid));

312 313 314
    #
    # The NS file is taken from the template.
    #
315
    fatal(1, "There is no NS file in $userdir/tbdata!")
316 317 318 319 320 321 322 323 324
	if (! -e $nsfile);

    #
    # Do a template modify of the current template.
    #
    system("$modify -m $guid/$version -w $optarg -g $gid $pid $tid $nsfile");
    if ($?) {
	fatal($? >> 8, "Failed to modify template!");
    }
325 326 327 328 329 330 331 332 333 334 335 336 337

    # Pick up changes to child guid/vers.
    $template->Refresh();
    my $child_guid = $template->child_guid();
    my $child_vers = $template->child_vers();
    my $child      = Template->Lookup($child_guid, $child_vers);
    if (!defined($child)) {
	fatal(-1, "Lookup of child template failed!");
    }
    $child->SetDescription($description)
	if (defined($description));

    return 0;
338 339
}

340 341 342 343 344 345
#
# Commit from an instance.
#
sub CommitFromInstance()
{
    my $pid = $template->pid();
346
    my $gid = $template->gid();
347 348 349 350 351 352 353 354 355 356 357 358 359
    
    my $experiment = Experiment->Lookup($pid, $eid);
    if (!defined($experiment)) {
	fatal(-1, "Could not get experiment record for experiment $pid/$eid!");
    }

    my $idx      = $experiment->idx();
    my $instance = Template::Instance->LookupByExptidx($idx);
    if (!defined($instance)) {
	fatal(-1, "Could not get instance record for experiment index $idx!");
    }

    my $userdir = $instance->path();
360
    my $nsfile  = "$userdir/tbdata/nsfile.ns";
361 362 363 364

    #
    # The NS file is taken from the instance. 
    #
365
    fatal(1, "There is no NS file in $userdir/tbdata!")
366 367
	if (! -e $nsfile);

368 369 370 371
    # Optional override from commandline
    $tid = $template->tid()
	if (!defined($tid));

372 373 374
    #
    # Start with a plain template modify of the current template.
    #
375 376
    system("$modify -f $frompath ".
	   "  -m $guid/$version -w -g $gid $pid $tid $nsfile");
377 378 379 380
    if ($?) {
	fatal($? >> 8, "Failed to commit instance to template!");
    }
    # Pick up changes to child guid/vers.
381
    # Pick up changes to child guid/vers.
382
    $template->Refresh();
383 384 385 386
    my $child_guid = $template->child_guid();
    my $child_vers = $template->child_vers();
    my $child      = Template->Lookup($child_guid, $child_vers);
    if (!defined($child)) {
387 388
	fatal(-1, "Lookup of child template failed!");
    }
389 390
    $child->SetDescription($description)
	if (defined($description));
391 392 393 394 395 396 397 398 399 400 401 402 403
}

#
# Parse command arguments. Once we return from getopts, all that are
# left are the required arguments.
#
sub ParseArgs()
{
    if (! getopts($optlist, \%options)) {
	usage();
    }

    #
404
    # Allow pid to be used instead of GUID.
405
    #
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
    if (@ARGV == 1) {
	#
	# Pick up guid/version first and untaint.
	#
	my $tmp = shift(@ARGV);

	if ($tmp =~ /^([\w]*)\/([\d]*)$/) {
	    $guid = $1;
	    $version = $2;
	}
	else {
	    tbdie("Bad data in argument: $tmp");
	}
    }
    elsif (defined($options{"p"})) {
	usage()
	    if (!defined($options{"e"}));
	
	$pid = $options{"p"};

	if ($pid =~ /^([-\w]+)$/) {
	    $pid = $1;
	}
	else {
	    tbdie("Bad data in argument: $pid.");
	}
	if (! TBcheck_dbslot($pid, "projects", "pid",
			   TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
	    tbdie("Improper project name (pid)!");
	}
436
    }
437 438 439 440 441 442 443 444
    elsif (defined($options{"f"})) {
	$frompath = $options{"f"};

	# The Archive library has a nice routine to validate this path.
	if (Archive::ValidatePath(\$frompath) != 0) {
	    tbdie("Invalid path $frompath");
	}
    }
445
    else {
446
	usage();
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
    }

    if (defined($options{"e"})) {
	$eid = $options{"e"};

	if ($eid =~ /^([-\w]+)$/) {
	    $eid = $1;
	}
	else {
	    tbdie("Bad data in argument: $eid.");
	}
	if (! TBcheck_dbslot($eid, "experiments", "eid",
			   TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
	    tbdie("Improper experiment name (id)!");
	}
    }
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
    elsif (defined($options{"r"})) {
	$repotag = $options{"r"};

	if ($repotag =~ /^([-\w:]+)$/) {
	    $repotag = $1;
	}
	else {
	    tbdie("Bad data in argument: $repotag");
	}
    }

    if (defined($options{"t"})) {
	$tid = $options{"t"};

	if ($tid =~ /^([-\w]+)$/) {
	    $tid = $1;
	}
	else {
	    tbdie("Bad data in argument: $tid.");
	}
	if (! TBcheck_dbslot($tid, "experiments", "eid",
			     TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
	    tbdie("Improper template name (id)!");
	}
    }

    if (defined($options{"E"})) {
	if (! TBcheck_dbslot($options{"E"},
			     "experiment_templates", "description",
			     TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
	    tbdie("Improper template description!");
	}
	$description = $options{"E"};
    }
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534

    if (defined($options{"q"})) {
	$quiet = 1;
    }
    if (defined($options{"d"})) {
	$debug = 2;
    }
}

#
# Cleanup the mess.
#
sub cleanup()
{
    $child_template->Delete()
	if (defined($child_template));
}

sub sighandler ($) {
    my ($signame) = @_;
    
    $SIG{TERM} = 'IGNORE';
    my $pgrp = getpgrp(0);
    kill('TERM', -$pgrp);
    sleep(1);
    fatal(-1, "Caught SIG${signame}! Cleaning up ...");
}

sub fatal($$)
{
    my ($errorstat, $msg) = @_;
    
    tberror $msg;
    tbinfo "Cleaning up and exiting with status $errorstat ...";
    cleanup();
    exit($errorstat);
}