Commit a986a085 authored by David Johnson's avatar David Johnson

Replace the Docker entrypoint/cmd/env implementation for augmented images.

(Also, add support for user to change container entrypoint at runtime.
Note also that the server side now stores the entrypoint/cmd/env
attributes as base64url-encoded virt_node_attributes, so that we can
just use the existing table_regex for those values.)

We add a new runit service (/etc/service/dockerentrypoint) to
clientside/tmcc/linux/docker/dockerfiles/common to handle the
entrypoint/cmd/env/workingdir/user emulation.  From the comments:

  Docker's semantics for ENTRYPOINT/CMD vary depending on if those
  values are specified as arrays of string, or simple as single strings
  (which must be interpreted by /bin/sh -c).

  Handling all the quoting possibilities in the shell is a major pain.
  So, this script handles the basic stuff (in particular, sourcing env
  vars, because we want the shell to interpret them!) -- then execs our
  perl companion script (run.pl) to deal with the entrypoint/command
  files that libvnode_docker::emulabizeImage and
  libvnode_docker::vnodeCreate populated.

  libvnode_docker creates these single-line files in /etc/emulab/docker
  as either string:hexstr(<entrypoint-or-cmd-string>), or
  array:hexstr(a[0]),hexstr(a[1])... .  This allows us to preserve the
  original type of the image's entrypoint/cmd as well as the runtime
  entrypoint/cmd, and to preserve the exact bytes for the eventual final
  call to exec.

  The static files builtin to an emulabized image are
  /etc/emulab/docker/{entrypoint.image,cmd.image}, and those created
  dynamically at runtime if user changes the entrypoint or cmd are
  bind-mounted to /etc/emulab/docker{entrypoint.runtime,cmd.runtime}.

  Given the presence (or absence!) of those files, this script
  implements the emulation, based upon the content in those files.
parent 993e9f8c
#!/bin/sh
#
# We never restart this service. If it stops, presumably user wants to
# login to see what happened.
#
sv down dockerentrypoint
#!/bin/sh
#
# This runit service emulates the normal Docker ENTRYPOINT/CMD handling,
# insofar as possible.
#
# Docker's semantics for ENTRYPOINT/CMD vary depending on if those
# values are specified as arrays of string, or simple as single strings
# (which must be interpreted by /bin/sh -c).
#
# Handling all the quoting possibilities in the shell is a major pain.
# So, this script handles the basic stuff (in particular, sourcing env
# vars, because we want the shell to interpret them!) -- then execs our
# perl companion script (run.pl) to deal with the entrypoint/command
# files that libvnode_docker::emulabizeImage and
# libvnode_docker::vnodeCreate populated.
#
# libvnode_docker creates these single-line files in /etc/emulab/docker
# as either string:hexstr(<entrypoint-or-cmd-string>), or
# array:hexstr(a[0]),hexstr(a[1])... . This allows us to preserve the
# original type of the image's entrypoint/cmd as well as the runtime
# entrypoint/cmd, and to preserve the exact bytes for the eventual final
# call to exec.
#
# The static files builtin to an emulabized image are
# /etc/emulab/docker/{entrypoint.image,cmd.image}, and those created
# dynamically at runtime if user changes the entrypoint or cmd are
# bind-mounted to /etc/emulab/docker{entrypoint.runtime,cmd.runtime}.
#
# Given the presence (or absence!) of those files, this script
# implements the emulation, based upon the content in those files:
#
# if entrypoint.runtime.type == string:
# Run exactly the command in entrypoint.runtime
# elif entrypoint.image.type == string:
# Run exactly the command in entrypoint.image
# else:
# cmd = ""
# if entrypoint.runtime != "":
# cmd = `cat entrypoint.runtime`
# elif entrypoint.image != "":
# cmd = `cat entrypoint.image`
# if type(cmd.runtime) == string:
# strings = `cat cmd.runtime`
# cmd = "$cmd /bin/sh -c $strings"
# elif cmd.image.type == string:
# strings = `cat cmd.image`
# cmd = "$cmd /bin/sh -c $strings"
# elif -n cmd.runtime:
# strings = `cat cmd.runtime`
# cmd = "$cmd $strings"
# elif -n cmd.image:
# strings = `cat cmd.image`
# cmd = "$cmd $strings"
#
# If we still have nothing to run, we down the service and exit.
#
# Before executing "$cmd", we include the dockerenv.image file, then
# include the dockerenv.runtime file, if either exists. Finally, we check
# to see if a USER was specified for the image; and if so, exec "$cmd"
# as that USER via chpst.
#
# When we run chpst, we also close stdin, and we redirect $cmd's outputs
# to /var/log/entrypoint.log. Initially, we redirect our own outputs to
# /var/log/entrypoint-debug.log .
#
mkdir -p /var/log
exec >> /var/log/entrypoint-debug.log
exec 2>&1
EXECTARGET=`pwd`/run.pl
CHPST=/usr/bin/chpst
PREFIX=/etc/emulab/docker
ENVFILE_G=$PREFIX/dockerenv.generated
ENVFILE_I=$PREFIX/dockerenv.image
ENVFILE_R=$PREFIX/dockerenv.runtime
USER=""
if [ -e $PREFIX/user ]; then
USER=`cat $PREFIX/user`
fi
WORKINGDIR=""
if [ -e $PREFIX/workingdir ]; then
WORKINGDIR=`cat $PREFIX/workingdir`
fi
if [ -z "$WORKINGDIR" ]; then
WORKINGDIR="/"
fi
echo `date`: setting environment...
if [ -e $ENVFILE_G ]; then
. $ENVFILE_G
fi
if [ -e $ENVFILE_I ]; then
. $ENVFILE_I
fi
if [ -e $ENVFILE_R ]; then
. $ENVFILE_R
fi
env
echo `date`: changing to $WORKINGDIR
cd $WORKINGDIR
HELPER="$CHPST -0"
if [ -n "$USER" ]; then
HELPER="$HELPER -U $USER -u $USER"
fi
echo `date`: executing $EXECTARGET $HELPER
exec $EXECTARGET $HELPER || {
echo "exec failed!"
exit 999
}
#!/usr/bin/perl -w
use strict;
sub rlog {
for (@_) {
print STDERR $_;
}
print STDERR "\n";
}
sub rlogts {
rlog(scalar(localtime()),": ",@_);
}
#mkdir("/var");
#mkdir("/var/log");
#open my $debuglog_fh ">>/var/log/entrypoint-debug.log";
#*STDOUT = $debuglog_fh;
#*STDERR = $debuglog_fh;
select(STDERR);
$| = 1;
select(STDOUT);
$| = 1;
rlogts("run.pl starting emulation");
my $PREFIX = "/etc/emulab/docker";
my ($epI,$epR,$cmdI,$cmdR);
my %fmap = (
"$PREFIX/entrypoint.image" => \$epI,
"$PREFIX/entrypoint.runtime" => \$epR,
"$PREFIX/cmd.image" => \$cmdI,
"$PREFIX/cmd.runtime" => \$cmdR );
for my $fname (keys(%fmap)) {
next
if (! -e "$fname");
my $size = (stat($fname))[7];
next
if ($size <= 0);
my $vref = $fmap{$fname};
open(FD,"$fname");
if ($?) {
rlog("ERROR: open($fname): $!");
next;
}
my $line = <FD>;
close(FD);
if ($line =~ /^string:(.*)$/) {
$$vref = pack("H*",$1);
}
elsif ($line =~ /^array:(.*)$/) {
my @a = split(/,/,$1);
@a = map { pack("H*",$_) } @a;
$$vref = \@a;
}
else {
rlog("ERROR: invalid line '$line' in $fname; skipping!");
next;
}
}
my @cmd = ();
# Prepend the helper to whatever we run.
for (my $i = 0; $i < @ARGV; ++$i) {
push(@cmd,$ARGV[$i]);
}
# Add the entrypoint/cmd goo.
if (defined($epR) && ref($epR) ne 'ARRAY') {
push(@cmd,"/bin/sh","-c",$epR);
}
elsif (defined($epI) && ref($epI) ne 'ARRAY') {
push(@cmd,"/bin/sh","-c",$epI);
}
else {
my $ep;
if (defined($epR)) {
$ep = $epR;
push(@cmd,@{$epR})
}
elsif (defined($epI)) {
$ep = $epI;
push(@cmd,@{$epI})
}
for my $c ($cmdR,$cmdI) {
if (defined($c) && ref($c) eq '') {
push(@cmd,"/bin/sh","-c",$c);
last;
}
elsif (defined($c) && ref($c) eq 'ARRAY') {
push(@cmd,@{$c});
last;
}
}
}
rlogts("Will exec '".join(" ",@cmd));
close(STDOUT);
close(STDERR);
open(STDOUT,">>","/var/log/entrypoint.log");
open(STDERR,'>&STDOUT');
exec(@cmd);
if ($?) {
open(STDOUT,">>","/var/log/entrypoint-debug.log");
open(STDERR,'>&STDOUT');
rlogts("exec failed: $! ($?)");
exit 999;
}
......@@ -2288,13 +2288,20 @@ sub GetTicketAuxAux($)
}
$attrkey = "DOCKER_EXEC_SHELL";
}
elsif ($setting eq "entrypoint") {
$attrkey = "DOCKER_ENTRYPOINT";
$attrvalue = "base64url:" .
MIME::Base64::encode_base64url($attrvalue);
}
elsif ($setting eq "cmd") {
$attrkey = "DOCKER_CMD";
#$attrvalue = DBQuoteSpecial($attrvalue);
$attrvalue = "base64url:" .
MIME::Base64::encode_base64url($attrvalue);
}
elsif ($setting eq "env") {
$attrkey = "DOCKER_ENV";
#$attrvalue = DBQuoteSpecial($attrvalue);
$attrvalue = "base64url:" .
MIME::Base64::encode_base64url($attrvalue);
}
elsif ($setting eq "privileged") {
$attrkey = "DOCKER_PRIVILEGED";
......
......@@ -1288,6 +1288,9 @@ sub GetDockerSettings($)
$tmp = GetText("exec_shell", $settings);
$result->{"exec_shell"} = $tmp
if (defined($tmp));
$tmp = GetText("entrypoint", $settings);
$result->{"entrypoint"} = $tmp
if (defined($tmp));
$tmp = GetText("cmd", $settings);
$result->{"cmd"} = $tmp
if (defined($tmp));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment