All new accounts created on Gitlab now require administrator approval. If you invite any collaborators, please let Flux staff know so they can approve the accounts.

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