Skip to content

Commit

Permalink
s2i windows build enablement
Browse files Browse the repository at this point in the history
- enable use of cygwin to build and test s2i
- simplify git and file downloaders
- track posix file permissions on windows
- remove unnecessary runtime.GOOS == "windows" checks
- unit test fixes
  • Loading branch information
Jim Minter committed Nov 29, 2016
1 parent eb59eca commit 251057d
Show file tree
Hide file tree
Showing 46 changed files with 1,125 additions and 643 deletions.
15 changes: 13 additions & 2 deletions hack/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 158,13 @@ s2i::build::create_gopath_tree() {
rm -f "${go_pkg_dir}"

# TODO: This symlink should be relative.
ln -s "${S2I_ROOT}" "${go_pkg_dir}"
if [[ "$OSTYPE" == "cygwin" ]]; then
S2I_ROOT_cyg=$(cygpath -w ${S2I_ROOT})
go_pkg_dir_cyg=$(cygpath -w ${go_pkg_dir})
cmd /c "mklink ${go_pkg_dir_cyg} ${S2I_ROOT_cyg}" &>/dev/null
else
ln -s "${S2I_ROOT}" "${go_pkg_dir}"
fi
}


Expand Down Expand Up @@ -222,6 228,11 @@ EOF
if [[ -z ${S2I_NO_GODEPS:-} ]]; then
GOPATH="${GOPATH}:${S2I_ROOT}/vendor"
fi

if [[ "$OSTYPE" == "cygwin" ]]; then
GOPATH=$(cygpath -w -p $GOPATH)
fi

export GOPATH

# Unset GOBIN in case it already exists in the current session.
Expand Down Expand Up @@ -333,7 344,7 @@ s2i::build::make_binary_symlinks() {
if [[ -f "${S2I_OUTPUT_BINPATH}/${platform}/s2i" ]]; then
for linkname in "${S2I_BINARY_SYMLINKS[@]}"; do
if [[ $platform == "windows/amd64" ]]; then
cp s2i "${S2I_OUTPUT_BINPATH}/${platform}/${linkname}.exe"
cp "${S2I_OUTPUT_BINPATH}/${platform}/s2i.exe" "${S2I_OUTPUT_BINPATH}/${platform}/${linkname}.exe"
else
ln -sf s2i "${S2I_OUTPUT_BINPATH}/${platform}/${linkname}"
fi
Expand Down
15 changes: 13 additions & 2 deletions hack/test-go.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 44,25 @@ if [[ -n "${S2I_COVER}" && -n "${OUTPUT_COVERAGE}" ]]; then
for test_package in "${test_packages[@]}"
do
mkdir -p "$OUTPUT_COVERAGE/$test_package"
S2I_COVER_PROFILE="-coverprofile=$OUTPUT_COVERAGE/$test_package/profile.out"
PROFILEPATH=${OUTPUT_COVERAGE}/${test_package}/profile.out
if [[ ${OSTYPE} == "cygwin" ]]; then
PROFILEPATH=$(cygpath -w ${PROFILEPATH})
fi
S2I_COVER_PROFILE="-coverprofile=${PROFILEPATH}"

go test $S2I_RACE $S2I_TIMEOUT $S2I_COVER "$S2I_COVER_PROFILE" "$test_package" "${@:2}"
done

echo 'mode: atomic' > ${OUTPUT_COVERAGE}/profiles.out
find $OUTPUT_COVERAGE -name profile.out | xargs sed '/^mode: atomic$/d' >> ${OUTPUT_COVERAGE}/profiles.out
go tool cover "-html=${OUTPUT_COVERAGE}/profiles.out" -o "${OUTPUT_COVERAGE}/coverage.html"
PROFILES_OUT=${OUTPUT_COVERAGE}/profiles.out
COVERAGE_HTML=${OUTPUT_COVERAGE}/coverage.html
if [[ ${OSTYPE} == "cygwin" ]]; then
PROFILES_OUT=$(cygpath -w ${PROFILES_OUT})
COVERAGE_HTML=$(cygpath -w ${COVERAGE_HTML})
fi

go tool cover -html=${PROFILES_OUT} -o ${COVERAGE_HTML}

# remove ${OUTPUT_COVERAGE}/github.com
rm -rf $OUTPUT_COVERAGE/${S2I_GO_PACKAGE%%/*}
Expand Down
22 changes: 15 additions & 7 deletions hack/test-stirunimage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 4,19 @@ set -o errexit
set -o nounset
set -o pipefail

S2I_ROOT=$(dirname "${BASH_SOURCE}")/..
export KILLDONE=""
export PATH="$PWD/_output/local/bin/$(go env GOHOSTOS)/$(go env GOHOSTARCH):$PATH"

function time_now()
{
echo $(date %s000)
date %s000
}

mkdir -p /tmp/sti
WORK_DIR=$(mktemp -d /tmp/sti/test-work.XXXX)
S2I_WORK_DIR=${WORK_DIR}
if [[ "$OSTYPE" == "cygwin" ]]; then
S2I_WORK_DIR=$(cygpath -w ${WORK_DIR})
fi
mkdir -p ${WORK_DIR}
NEEDKILL="yes"
S2I_PID=""
Expand Down Expand Up @@ -87,12 90,12 @@ popd

test_debug "s2i build with absolute path with file://"

s2i build "file://${WORK_DIR}/cakephp-ex" openshift/php-55-centos7 test --loglevel=5 &> "${WORK_DIR}/s2i-abs-proto.log"
s2i build "file://${S2I_WORK_DIR}/cakephp-ex" openshift/php-55-centos7 test --loglevel=5 &> "${WORK_DIR}/s2i-abs-proto.log"
check_result $? "${WORK_DIR}/s2i-abs-proto.log"

test_debug "s2i build with absolute path without file://"

s2i build "${WORK_DIR}/cakephp-ex" openshift/php-55-centos7 test --loglevel=5 &> "${WORK_DIR}/s2i-abs-noproto.log"
s2i build "${S2I_WORK_DIR}/cakephp-ex" openshift/php-55-centos7 test --loglevel=5 &> "${WORK_DIR}/s2i-abs-noproto.log"
check_result $? "${WORK_DIR}/s2i-abs-noproto.log"

## don't do ssh tests here because credentials are needed (even for the git user), which
Expand All @@ -101,7 104,7 @@ check_result $? "${WORK_DIR}/s2i-abs-noproto.log"
test_debug "s2i build with non-git repo file location"

rm -rf "${WORK_DIR}/cakephp-ex/.git"
s2i build "${WORK_DIR}/cakephp-ex" openshift/php-55-centos7 test --loglevel=5 --loglevel=5 &> "${WORK_DIR}/s2i-non-repo.log"
s2i build "${S2I_WORK_DIR}/cakephp-ex" openshift/php-55-centos7 test --loglevel=5 --loglevel=5 &> "${WORK_DIR}/s2i-non-repo.log"
check_result $? ""
grep "Copying sources" "${WORK_DIR}/s2i-non-repo.log"
check_result $? "${WORK_DIR}/s2i-non-repo.log"
Expand All @@ -125,7 128,12 @@ s2i build git://github.com/openshift/cakephp-ex openshift/php-55-centos7 test --
check_result $? "${WORK_DIR}/s2i-git-proto.log"

test_debug "s2i build with --run==true option"
s2i build git://github.com/bparees/openshift-jee-sample openshift/wildfly-90-centos7 test-jee-app --run=true --loglevel=5 &> "${WORK_DIR}/s2i-run.log" &
if [[ "$OSTYPE" == "cygwin" ]]; then
( cd hack/windows/sigintwrap && make )
hack/windows/sigintwrap/sigintwrap 's2i build git://github.com/bparees/openshift-jee-sample openshift/wildfly-90-centos7 test-jee-app --run=true --loglevel=5' &> "${WORK_DIR}/s2i-run.log" &
else
s2i build git://github.com/bparees/openshift-jee-sample openshift/wildfly-90-centos7 test-jee-app --run=true --loglevel=5 &> "${WORK_DIR}/s2i-run.log" &
fi
S2I_PID=$!
TIME_SEC=1000
TIME_MIN=$((60 * $TIME_SEC))
Expand Down
24 changes: 24 additions & 0 deletions hack/windows/Vagrantfile
Original file line number Diff line number Diff line change
@@ -0,0 1,24 @@
Vagrant.configure("2") do |config|
config.openshift.autoconfigure_aws = true

config.vm.provider "aws" do |aws|
aws.instance_type = "m4.large"
aws.subnet_id = "subnet-cf57c596"
end

config.vm.define "rhel" do |rhel|
rhel.vm.provider "aws" do |aws|
aws.tags = {"Name" => "#{ENV['USER']}-rhel"}
aws.ami = "rhel7:deps"
end
rhel.vm.provision "configure-docker-server-linux"
end

config.vm.define "windows" do |windows|
windows.vm.provider "aws" do |aws|
aws.tags = {"Name" => "#{ENV['USER']}-windows"}
aws.ami = "windows2012r2:deps"
end
windows.vm.provision "configure-docker-client-windows"
end
end
8 changes: 8 additions & 0 deletions hack/windows/sigintwrap/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 1,8 @@
CFLAGS=-Wall -Os -s

sigintwrap:

clean:
@rm -f sigintwrap

.PHONY: clean
27 changes: 27 additions & 0 deletions hack/windows/sigintwrap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 1,27 @@
sigintwrap is an executable wrapper used in the automated testing of s2i on
Windows. In hack/test-stirunimage.sh it is verified that sending an interrupt
to a running s2i process (i.e. sending a SIGINT on Linux, or specifically
pressing CTRL C or CTRL BREAK on Windows) causes the process to clean up a
running Docker container before exiting.

Cygwin is used for Windows build and testing of s2i, but note that the s2i
binary itself has no dependency on Cygwin (in general, Go and the executables it
compiles have no knowledge or dependency on Cygwin).

For the above test to be valid and succeed on Windows, it is necessary to bridge
between receiving the SIGINT sent by the test framework (note: on Windows,
POSIX-style signal handling is implemented only in Cygwin-compiled/-aware
executables, in userspace) and Golang acting on a native CTRL C/CTRL BREAK
event.

sigintwrap is a Cygwin-compiled executable which spawns a named executable in a
new Windows process group and waits until it exits. If before that time
sigintwrap receives a SIGINT signal it sends a synthetic CTRL BREAK event to the
process it started.

This approach is used (rather than, say, simply using the
GenerateConsoleCtrlEvent API directly to create a synthetic event) because there
is no obvious and straightforward way to spawn a new process into a new Windows
process group in Cygwin without resorting to C, nor is there an obvious free
lightweight pre-existing utility which can be used to send a synthetic
CTRL BREAK event to a given process.
116 changes: 116 additions & 0 deletions hack/windows/sigintwrap/sigintwrap.c
Original file line number Diff line number Diff line change
@@ -0,0 1,116 @@
// sigintwrap: wrapper for non-Cygwin executables, capturing Cygwin SIGINTs and
// forwarding them as a CTRL BREAK events to the non-Cygwin executable. After
// "Solution For Handling Signals In Non-Cygwin Apps With
// SetConsoleCtrlHandler", Anthony DeRosa,
// http://marc.info/?l=cygwin&m=111047278517873


#include <sys/cygwin.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <windows.h>


static PROCESS_INFORMATION pi;


static void *
wait_for_process(void *ptr) {
WaitForSingleObject(pi.hProcess, INFINITE);
return NULL;
}


static void
sigint(int signal) {
GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pi.dwProcessId);
}


static int
needs_path_conversion(const char *s) {
// See winsup/cygwin/environ.cc.
return !(strncmp(s, "HOME=", 5) &&
strncmp(s, "LD_LIBRARY_PATH=", 16) &&
strncmp(s, "PATH=", 5) &&
strncmp(s, "TEMP=", 5) &&
strncmp(s, "TMP=", 4) &&
strncmp(s, "TMPDIR=", 7));
}


static char *
prepare_env() {
char **p;

int len = 1;
for(p = environ; *p; p )
if(needs_path_conversion(*p)) {
char *eq = strchr(*p, '=');
len = eq - *p 1;
len = cygwin_conv_path_list(CCP_POSIX_TO_WIN_A, eq 1, NULL, 0);
} else
len = strlen(*p) 1;

char *env = (char *)malloc(len);
char *e = env;
for(p = environ; *p; p )
if(needs_path_conversion(*p)) {
char *eq = strchr(*p, '=');
e = stpncpy(e, *p, eq - *p 1);
cygwin_conv_path_list(CCP_POSIX_TO_WIN_A, eq 1, e, env len - e - 1);
while(*e );
} else
e = stpcpy(e, *p) 1;
*e = '\0';

return env;
}


int
main(int argc, char **argv) {
if(argc != 2) {
fprintf(stderr, "usage: %s 'c:\\path\\to\\command.exe [arg...]'\n",
argv[0]);
return 1;
}

STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);

char *env = prepare_env();

if(!CreateProcess(NULL, argv[1], NULL, NULL, FALSE, CREATE_NEW_PROCESS_GROUP,
env, NULL, &si, &pi)) {
LPTSTR msg;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), 0,
(LPTSTR)&msg, 0, NULL);
fputs(msg, stderr);
LocalFree(msg);
free(env);
return 1;
}

free(env);

signal(SIGINT, sigint);

// We call WaitForSingleObject on another thread because it cannot be
// interrupted by cygwin signals. pthread_join can be.
pthread_t thread;
pthread_create(&thread, NULL, wait_for_process, NULL);
pthread_join(thread, NULL);

DWORD exitcode;
GetExitCodeProcess(pi.hProcess, &exitcode);

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

return exitcode;
}
22 changes: 10 additions & 12 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 22,7 @@ const (

// invalidFilenameCharacters contains a list of character we consider malicious
// when injecting the directories into containers.
const invalidFilenameCharacters = `\:;*?"<>|%#$! {}&[],"'` "`"
const invalidFilenameCharacters = `;*?"<>|%#$! {}&[],"'` "`"

const (
// PullAlways means that we always attempt to pull the latest image.
Expand Down Expand Up @@ -500,18 500,16 @@ func (l *VolumeList) Set(value string) error {
if len(value) == 0 {
return errors.New("invalid format, must be source:destination")
}
mount := strings.Split(value, ":")
switch len(mount) {
case 1:
mount = append(mount, "")
fallthrough
case 2:
mount[0] = strings.Trim(mount[0], `"'`)
mount[1] = strings.Trim(mount[1], `"'`)
default:
return errors.New("invalid source:path definition")
var mount []string
pos := strings.LastIndex(value, ":")
if pos == -1 {
mount = []string{value, ""}
} else {
mount = []string{value[:pos], value[pos 1:]}
}
s := VolumeSpec{Source: filepath.Clean(mount[0]), Destination: filepath.Clean(mount[1])}
mount[0] = strings.Trim(mount[0], `"'`)
mount[1] = strings.Trim(mount[1], `"'`)
s := VolumeSpec{Source: filepath.Clean(mount[0]), Destination: filepath.ToSlash(filepath.Clean(mount[1]))}
if IsInvalidFilename(s.Source) || IsInvalidFilename(s.Destination) {
return fmt.Errorf("invalid characters in filename: %q", value)
}
Expand Down
Loading

0 comments on commit 251057d

Please sign in to comment.