You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@openwhisk.apache.org by GitBox <gi...@apache.org> on 2018/03/24 19:19:50 UTC

[GitHub] sciabarracom closed pull request #2: Releasing the revised Golang runtime

sciabarracom closed pull request #2: Releasing the revised Golang runtime
URL: https://github.com/apache/incubator-openwhisk-runtime-go/pull/2
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d9794c7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.gogradle/
+.gradle/
+.vscode/
+action/
+vendor/
+*.log
+*.zip.json
+core/proxy
+core/*/proxy
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..c29a74e
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,70 @@
+[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more contributor 
+# license agreements.  See the NOTICE file distributed with this work for additional 
+# information regarding copyright ownership.  The ASF licenses this file to you
+# under the Apache License, Version 2.0 (the # "License"); you may not use this 
+# file except in compliance with the License.  You may obtain a copy of the License 
+# at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed 
+# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
+# CONDITIONS OF ANY KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations under the License.
+#
+-->
+
+# Contributing to Apache OpenWhisk
+
+Anyone can contribute to the OpenWhisk project and we welcome your contributions.
+
+There are multiple ways to contribute: report bugs, improve the docs, and
+contribute code, but you must follow these prerequisites and guidelines:
+
+ - [Contributor License Agreement](#contributor-license-agreement)
+ - [Raising issues](#raising-issues)
+ - [Coding Standards](#coding-standards)
+
+### Contributor License Agreement
+
+All contributors must sign and submit an Apache CLA (Contributor License Agreement).
+
+Instructions on how to do this can be found here:
+[http://www.apache.org/licenses/#clas](http://www.apache.org/licenses/#clas)
+
+Once submitted, you will receive a confirmation email from the Apache Software Foundation (ASF) and be added to
+the following list: http://people.apache.org/unlistedclas.html. 
+
+Project committers will use this list to verify pull requests (PRs) come from contributors that have signed a CLA.
+
+We look forward to your contributions! 
+
+## Raising issues
+
+Please raise any bug reports or enhancement requests on the respective project repository's GitHub issue tracker. Be sure to search the 
+list to see if your issue has already been raised.
+
+A good bug report is one that make it easy for us to understand what you were trying to do and what went wrong. 
+Provide as much context as possible so we can try to recreate the issue.
+
+A good enhancement request comes with an explanation of what you are trying to do and how that enhancement would help you.
+
+### Discussion
+
+Please use the project's developer email list to engage our community:
+[dev@openwhisk.incubator.apache.org](dev@openwhisk.incubator.apache.org)
+
+In addition, we provide a "dev" Slack team channel for conversations at:
+https://openwhisk-team.slack.com/messages/dev/
+
+### Coding standards
+
+Please ensure you follow the coding standards used throughout the existing
+code base. Some basic rules include:
+
+ - all files must have the Apache license in the header.
+ - all PRs must have passing builds for all operating systems.
+ - the code is correctly formatted as defined in the [Scalariform plugin properties](tools/eclipse/scala.properties). If you use IntelliJ for development this [page](https://plugins.jetbrains.com/plugin/7480-scalariform) describes the setup and configuration of the plugin.
diff --git a/CREDITS.txt b/CREDITS.txt
new file mode 100644
index 0000000..b6da19b
--- /dev/null
+++ b/CREDITS.txt
@@ -0,0 +1 @@
+Michele Sciabarra <mi...@sciabarra.com>
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..a2fe52f
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2015-2016  IBM Corporation
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/NOTICE.md b/NOTICE.md
new file mode 100644
index 0000000..59c6d70
--- /dev/null
+++ b/NOTICE.md
@@ -0,0 +1,5 @@
+Apache OpenWhisk Runtime Docker
+Copyright 2016-2018 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/README.md b/README.md
index d76119c..70d2067 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,10 @@
-# incubator-openwhisk-runtime-go
-Apache openwhisk runtime for go actions
+# Apache OpenWhisk Runtime for Go
+
+This is an  OpenWhisk runtime for  Golang.
+
+Local development
+
+`./gradle :docker:distDocker`
+
+This will produce the image `whisk/golang`
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..776c945
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath "cz.alenkacz:gradle-scalafmt:${gradle.scalafmt.version}"
+    }
+}
+
+plugins {
+    id 'com.github.blindpirate.gogradle' version '0.8.1'
+}
+
+subprojects {
+    apply plugin: 'scalafmt'
+    scalafmt.configFilePath = gradle.scalafmt.config
+}
+
+golang {
+  packagePath = 'github.com/sciabarracom/incubator-openwhisk-runtime-go'
+}
+
+
+build.dependsOn check
+
+build.dependsOn vendor
+
+build {
+  targetPlatform = ['linux-amd64']
+  go 'build -o core/proxy main/proxy.go'
+}
diff --git a/core/go19action/Dockerfile b/core/go19action/Dockerfile
new file mode 100644
index 0000000..96a312a
--- /dev/null
+++ b/core/go19action/Dockerfile
@@ -0,0 +1,8 @@
+FROM ibmcom/ubuntu:14.04
+RUN apt-get -y update && apt-get -y upgrade && apt-get -y install curl jq
+RUN mkdir /action
+WORKDIR /action
+ADD proxy /proxy
+ENTRYPOINT /proxy
+RUN curl https://dl.google.com/go/go1.9.4.linux-amd64.tar.gz  | tar xzvf - -C /usr/local
+ENV GOROOT /usr/local/go
diff --git a/core/go19action/build.gradle b/core/go19action/build.gradle
new file mode 100644
index 0000000..fc81578
--- /dev/null
+++ b/core/go19action/build.gradle
@@ -0,0 +1,10 @@
+ext.dockerImageName = 'action-golang-v1.9'
+apply from: '../../gradle/docker.gradle'
+
+distDocker.dependsOn ':build'
+distDocker.dependsOn 'copyProxy'
+
+task copyProxy(type: Copy) {
+    from '../proxy'
+    into '.'
+}
diff --git a/core/goproxy/.gitignore b/core/goproxy/.gitignore
new file mode 100644
index 0000000..125f526
--- /dev/null
+++ b/core/goproxy/.gitignore
@@ -0,0 +1 @@
+proxy
diff --git a/core/goproxy/Dockerfile b/core/goproxy/Dockerfile
new file mode 100644
index 0000000..150b535
--- /dev/null
+++ b/core/goproxy/Dockerfile
@@ -0,0 +1,6 @@
+FROM ibmcom/ubuntu:14.04
+RUN apt-get -y update && apt-get -y upgrade && apt-get -y install curl jq
+RUN mkdir /action
+WORKDIR /action
+ADD proxy /proxy
+ENTRYPOINT /proxy
diff --git a/core/goproxy/build.gradle b/core/goproxy/build.gradle
new file mode 100644
index 0000000..dbd95b4
--- /dev/null
+++ b/core/goproxy/build.gradle
@@ -0,0 +1,10 @@
+ext.dockerImageName = 'goproxy'
+apply from: '../../gradle/docker.gradle'
+
+distDocker.dependsOn ':build'
+distDocker.dependsOn 'copyProxy'
+
+task copyProxy(type: Copy) {
+    from '../proxy'
+    into '.'
+}
\ No newline at end of file
diff --git a/gradle/README.md b/gradle/README.md
new file mode 100644
index 0000000..5b83fdb
--- /dev/null
+++ b/gradle/README.md
@@ -0,0 +1,63 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more contributor 
+# license agreements.  See the NOTICE file distributed with this work for additional 
+# information regarding copyright ownership.  The ASF licenses this file to you
+# under the Apache License, Version 2.0 (the # "License"); you may not use this 
+# file except in compliance with the License.  You may obtain a copy of the License 
+# at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed 
+# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
+# CONDITIONS OF ANY KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations under the License.
+#
+-->
+
+# Gradle
+
+Gradle is used to build OpenWhisk. It does not need to be pre-installed as it installs itself using the [Gradle Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html). To use it without installing, simply invoke the `gradlew` command at the root of the repository. You can also install `gradle` via [`apt`](http://linuxg.net/how-to-install-gradle-2-1-on-ubuntu-14-10-ubuntu-14-04-ubuntu-12-04-and-derivatives/) on Ubuntu or [`brew`](http://www.brewformulas.org/Gradle) on Mac. In the following we use `gradle` and `gradlew` as synonymous.
+
+## Usage
+
+In general, project level properties are set via `-P{propertyName}={propertyValue}`. A task is called via `gradle {taskName}` and a subproject task is called via `gradle :path:to:subproject:{taskName}`. To run tasks in parallel, use the `--parallel` flag (**Note:** It's an incubating feature and might break stuff).
+
+### Build
+
+To build all Docker images use `gradle distDocker` at the top level project, to build a specific component use `gradle :core:controller:distDocker`.
+
+Project level options that can be used on `distDocker`:
+
+- `dockerImageName` (*required*): The name of the image to build (e.g. whisk/controller)
+- `dockerHost` (*optional*): The docker host to run commands on, default behaviour is docker's own `DOCKER_HOST` environment variable
+- `dockerRegistry` (*optional*): The registry to push to
+- `dockerImageTag` (*optional*, default 'latest'): The tag for the image
+- `dockerTimeout` (*optional*, default 240): Timeout for docker operations in seconds
+- `dockerRetries` (*optional*, default 3): How many times to retry docker operations
+- `dockerBinary` (*optional*, default `docker`): The binary to execute docker commands
+
+### Test
+
+To run tests one uses the `test` task. OpenWhisk consolidates tests into a single `tests` project. Hence the command to run all tests is `gradle :tests:test`.
+
+It is possible to run specific tests using [Gradle testfilters](https://docs.gradle.org/current/userguide/java_plugin.html#test_filtering). For example `gradle :tests:test --tests "your.package.name.TestClass.evenMethodName"`. Wildcard `*` may be used anywhere.
+
+## Build your own `build.gradle`
+In Gradle, most of the tasks we use are default tasks provided by plugins in Gradle. The [`scala` Plugin](https://docs.gradle.org/current/userguide/scala_plugin.html) for example includes tasks, that are needed to build Scala projects. Moreover, Gradle is aware of *Applications*. The [`application` Plugin](https://docs.gradle.org/current/userguide/application_plugin.html) provides tasks that are required to distribute a self-contained application. When `application` and `scala` are used in conjunction, they hook into each other and provide the tasks needed to distribute a Scala application. `distTar` for example compiles the Scala code, creates a jar containing the compiled classes and resources and creates a Tarball including that jar and all of its dependencies (defined in the dependencies section of `build.gradle`). It also creates a start-script which correctly sets the classpath for all those dependencies and starts the app.
+
+In OpenWhisk, we want to distribute our application via Docker images. Hence we wrote a "plugin" that creates the task `distDocker`. That task will build an image from the `Dockerfile` that is located next to the `build.gradle` it is called from, for example Controller's `Dockerfile` and `build.gradle` are both located at `core/controller`.
+
+If you want to create a new `build.gradle` for your component, simply put the `Dockerfile` right next to it and include `docker.gradle` by using
+
+```
+ext.dockerImageName = 'openwwhisk/{IMAGENAME}'
+apply from: 'path/to/docker.gradle'
+```
+
+If your component needs to be build before you can build the image, make `distDocker` depend on any task needed to run before it, for example:
+
+```
+distDocker.dependsOn ':common:scala:distDocker', 'distTar'
+```
diff --git a/gradle/docker.gradle b/gradle/docker.gradle
new file mode 100644
index 0000000..f716c7b
--- /dev/null
+++ b/gradle/docker.gradle
@@ -0,0 +1,99 @@
+import groovy.time.*
+
+/**
+ * Utility to build docker images based in gradle projects
+ *
+ * This extends gradle's 'application' plugin logic with a 'distDocker' task which builds
+ * a docker image from the Dockerfile of the project that applies this file. The image
+ * is automatically tagged and pushed if a tag and/or a registry is given.
+ *
+ * Parameters that can be set on project level:
+ * - dockerImageName (required): The name of the image to build (e.g. controller)
+ * - dockerRegistry (optional): The registry to push to
+ * - dockerImageTag (optional, default 'latest'): The tag for the image
+ * - dockerImagePrefix (optional, default 'whisk'): The prefix for the image,
+ *       'controller' becomes 'whisk/controller' per default
+ * - dockerTimeout (optional, default 840): Timeout for docker operations in seconds
+ * - dockerRetries (optional, default 3): How many times to retry docker operations
+ * - dockerBinary (optional, default 'docker'): The binary to execute docker commands
+ * - dockerBuildArgs (options, default ''): Project specific custom docker build arguments
+ * - dockerHost (optional): The docker host to run commands on, default behaviour is
+ *       docker's own DOCKER_HOST environment variable
+ */
+
+ext {
+    dockerRegistry = project.hasProperty('dockerRegistry') ? dockerRegistry + '/' : ''
+    dockerImageTag = project.hasProperty('dockerImageTag') ? dockerImageTag : 'latest'
+    dockerImagePrefix = project.hasProperty('dockerImagePrefix') ? dockerImagePrefix : 'whisk'
+    dockerTimeout = project.hasProperty('dockerTimeout') ? dockerTimeout.toInteger() : 840
+    dockerRetries = project.hasProperty('dockerRetries') ? dockerRetries.toInteger() : 3
+    dockerBinary = project.hasProperty('dockerBinary') ? [dockerBinary] : ['docker']
+    dockerBuildArg = ['build']
+}
+ext.dockerTaggedImageName = dockerRegistry + dockerImagePrefix + '/' + dockerImageName + ':' + dockerImageTag
+
+if(project.hasProperty('dockerHost')) {
+    dockerBinary += ['--host', project.dockerHost]
+}
+
+if(project.hasProperty('dockerBuildArgs')) {
+    dockerBuildArgs.each { arg  ->
+        dockerBuildArg += ['--build-arg', arg]
+    }
+}
+
+task distDocker {
+    doLast {
+        def start = new Date()
+        def cmd = dockerBinary + dockerBuildArg + ['-t', dockerImageName, project.buildscript.sourceFile.getParentFile().getAbsolutePath()]
+        retry(cmd, dockerRetries, dockerTimeout)
+        println("Building '${dockerImageName}' took ${TimeCategory.minus(new Date(), start)}")
+    }
+}
+task tagImage {
+    doLast {
+        def versionString = (dockerBinary + ['-v']).execute().text
+        def matched = (versionString =~ /(\d+)\.(\d+)\.(\d+)/)
+
+        def major = matched[0][1] as int
+        def minor = matched[0][2] as int
+
+        def dockerCmd = ['tag']
+        if(major == 1 && minor < 12) {
+            dockerCmd += ['-f']
+        }
+        retry(dockerBinary + dockerCmd + [dockerImageName, dockerTaggedImageName], dockerRetries, dockerTimeout)
+    }
+}
+
+task pushImage {
+    doLast {
+        def cmd = dockerBinary + ['push', dockerTaggedImageName]
+        retry(cmd, dockerRetries, dockerTimeout)
+    }
+}
+pushImage.dependsOn tagImage
+pushImage.onlyIf { dockerRegistry != '' }
+distDocker.finalizedBy pushImage
+
+def retry(cmd, retries, timeout) {
+    println("${new Date()}: Executing '${cmd.join(" ")}'")
+    def proc = cmd.execute()
+    proc.consumeProcessOutput(System.out, System.err)
+    proc.waitForOrKill(timeout * 1000)
+    if(proc.exitValue() != 0) {
+        def message = "${new Date()}: Command '${cmd.join(" ")}' failed with exitCode ${proc.exitValue()}"
+        if(proc.exitValue() == 143) { // 143 means the process was killed (SIGTERM signal)
+            message = "${new Date()}: Command '${cmd.join(" ")}' was killed after ${timeout} seconds"
+        }
+
+        if(retries > 1) {
+            println("${message}, ${retries-1} retries left, retrying...")
+            retry(cmd, retries-1, timeout)
+        }
+        else {
+            println("${message}, no more retries left, aborting...")
+            throw new GradleException(message)
+        }
+    }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..7a3265e
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..f16d266
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-bin.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/main/proxy.go b/main/proxy.go
new file mode 100644
index 0000000..fe8c0bc
--- /dev/null
+++ b/main/proxy.go
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package main
+
+import (
+	"flag"
+	"io/ioutil"
+	"log"
+
+	"github.com/sciabarracom/incubator-openwhisk-runtime-go/openwhisk"
+)
+
+// disable stderr except when debugging
+var debug = flag.Bool("debug", false, "enable debug output")
+
+func main() {
+	flag.Parse()
+	if !*debug {
+		log.SetOutput(ioutil.Discard)
+	}
+	openwhisk.Start()
+}
diff --git a/misc/.gitignore b/misc/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/misc/HelloGoSwift.jmx b/misc/HelloGoSwift.jmx
new file mode 100644
index 0000000..1870fd2
--- /dev/null
+++ b/misc/HelloGoSwift.jmx
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="2.9" jmeter="3.0 r1743807">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+      <stringProp name="TestPlan.comments"></stringProp>
+      <boolProp name="TestPlan.functional_mode">false</boolProp>
+      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments"/>
+      </elementProp>
+      <stringProp name="TestPlan.user_define_classpath"></stringProp>
+    </TestPlan>
+    <hashTree>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Docker" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">200</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">50</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <longProp name="ThreadGroup.start_time">1518869523000</longProp>
+        <longProp name="ThreadGroup.end_time">1518869523000</longProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Old " enabled="true">
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value">{&quot;value&quot;:{&quot;name&quot;: &quot;Mike&quot;}}</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">localhost</stringProp>
+          <stringProp name="HTTPSampler.port">8085</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          <stringProp name="HTTPSampler.protocol">http</stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/run</stringProp>
+          <stringProp name="HTTPSampler.method">POST</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <boolProp name="HTTPSampler.monitor">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree/>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Go+Swift" enabled="true">
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value">{&quot;value&quot;:{&quot;name&quot;: &quot;Mike&quot;}}</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">localhost</stringProp>
+          <stringProp name="HTTPSampler.port">8084</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          <stringProp name="HTTPSampler.protocol">http</stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/run</stringProp>
+          <stringProp name="HTTPSampler.method">POST</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <boolProp name="HTTPSampler.monitor">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree/>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="false">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="Aggregate Report" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+      </hashTree>
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>
diff --git a/misc/HelloStress.jmx b/misc/HelloStress.jmx
new file mode 100644
index 0000000..08e832b
--- /dev/null
+++ b/misc/HelloStress.jmx
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="2.9" jmeter="3.0 r1743807">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+      <stringProp name="TestPlan.comments"></stringProp>
+      <boolProp name="TestPlan.functional_mode">false</boolProp>
+      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments"/>
+      </elementProp>
+      <stringProp name="TestPlan.user_define_classpath"></stringProp>
+    </TestPlan>
+    <hashTree>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Docker" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">100</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">100</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <longProp name="ThreadGroup.start_time">1518869523000</longProp>
+        <longProp name="ThreadGroup.end_time">1518869523000</longProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Python+System" enabled="true">
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value">{&quot;value&quot;:{&quot;name&quot;: &quot;Mike&quot;}}</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">localhost</stringProp>
+          <stringProp name="HTTPSampler.port">8080</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          <stringProp name="HTTPSampler.protocol">http</stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/run</stringProp>
+          <stringProp name="HTTPSampler.method">POST</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <boolProp name="HTTPSampler.monitor">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree/>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GoServer" enabled="true">
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value">{&quot;value&quot;:{&quot;name&quot;: &quot;Mike&quot;}}</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">localhost</stringProp>
+          <stringProp name="HTTPSampler.port">8081</stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          <stringProp name="HTTPSampler.protocol">http</stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/run</stringProp>
+          <stringProp name="HTTPSampler.method">POST</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">false</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.implementation">HttpClient4</stringProp>
+          <boolProp name="HTTPSampler.monitor">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree/>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="false">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="Aggregate Report" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+      </hashTree>
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>
diff --git a/misc/aggregate.csv b/misc/aggregate.csv
new file mode 100644
index 0000000..33340c3
--- /dev/null
+++ b/misc/aggregate.csv
@@ -0,0 +1,5 @@
+Label,# Samples,Average,Median,90% Line,95% Line,99% Line,Min,Max,Error %,Throughput,KB/sec
+Python+System,10000,2043,2038,2396,2605,3031,94,4016,0.00%,48.2,6.4
+GoServer,10000,4,4,7,8,16,1,61,0.00%,48.3,7.3
+PipeExec,10000,5,5,9,11,20,2,132,0.00%,48.3,7.2
+TOTAL,30000,684,6,2121,2258,2689,1,4016,0.00%,144.7,20.9
diff --git a/misc/demo.png b/misc/demo.png
new file mode 100644
index 0000000..cfe74ea
Binary files /dev/null and b/misc/demo.png differ
diff --git a/misc/goswift.csv b/misc/goswift.csv
new file mode 100644
index 0000000..3c7b74e
--- /dev/null
+++ b/misc/goswift.csv
@@ -0,0 +1,4 @@
+Label,# Samples,Average,Median,90% Line,95% Line,99% Line,Min,Max,Error %,Throughput,KB/sec
+Old ,10000,1097,1101,1169,1191,1243,19,2132,0.00%,45.1,6.1
+Go+Swift,10000,4,4,6,6,10,2,1030,0.06%,45.1,7.0
+TOTAL,20000,551,66,1148,1169,1221,2,2132,0.03%,90.2,13.1
diff --git a/misc/hello.go b/misc/hello.go
new file mode 100644
index 0000000..f7b936e
--- /dev/null
+++ b/misc/hello.go
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package main
+
+import "encoding/json"
+import "fmt"
+import "os"
+
+func main() {
+	// native actions receive one argument, the JSON object as a string
+	arg := os.Args[1]
+
+	// unmarshal the string to a JSON object
+	var obj map[string]interface{}
+	json.Unmarshal([]byte(arg), &obj)
+	name, ok := obj["name"].(string)
+	if !ok {
+		name = "Stranger"
+	}
+	msg := map[string]string{"msg": ("Hello, " + name + "!")}
+	res, _ := json.Marshal(msg)
+	fmt.Printf("name=%s\n", name)
+	fmt.Println(string(res))
+}
diff --git a/misc/swift/.gitignore b/misc/swift/.gitignore
new file mode 100644
index 0000000..a0939b6
--- /dev/null
+++ b/misc/swift/.gitignore
@@ -0,0 +1,2 @@
+.build
+*.zip
diff --git a/misc/swift/Dockerfile b/misc/swift/Dockerfile
new file mode 100644
index 0000000..e9fc8c0
--- /dev/null
+++ b/misc/swift/Dockerfile
@@ -0,0 +1,5 @@
+FROM ibmcom/swift-ubuntu:4.0.3
+RUN mkdir /action
+WORKDIR /action
+ADD proxy /proxy
+CMD /proxy
diff --git a/misc/swift/Package.swift b/misc/swift/Package.swift
new file mode 100644
index 0000000..59fcf9c
--- /dev/null
+++ b/misc/swift/Package.swift
@@ -0,0 +1,24 @@
+// swift-tools-version:4.0
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+    name: "exec",
+    products:[
+        .executable(name: "exec", targets: ["exec"]),
+        .library(name: "exec", type: .dynamic, targets: ["exec"])
+    ],
+    dependencies: [
+        .package(url: "https://github.com/IBM-Swift/SwiftyJSON", from: "17.0.0"),
+        .package(url: "https://github.com/IBM-Swift/HeliumLogger", from: "1.7.1"),
+       
+    ],
+    targets: [
+        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
+        .target(
+            name: "exec",
+            dependencies: ["SwiftyJSON", "HeliumLogger"]),
+    ]
+)
diff --git a/misc/swift/Sources/Hello/main.swift b/misc/swift/Sources/Hello/main.swift
new file mode 100644
index 0000000..bbc127c
--- /dev/null
+++ b/misc/swift/Sources/Hello/main.swift
@@ -0,0 +1,35 @@
+import SwiftyJSON
+
+#if os(Linux)
+import Glibc
+#endif
+
+func hello(json: JSON) -> JSON {
+    let name = json["name"].string ?? "stranger"
+    return JSON([ "greeting" : "Hello \(name)!" ])
+}
+
+func printJson(json: JSON) {
+    let res = json.rawString()?.replacingOccurrences(of: "\n", with: " ")
+    print( res ?? "{\"error:\": \"Invalid JSON\"}")
+#if os(Linux)
+    fflush(stdout)
+#endif
+}
+
+func parseJson(str: String) -> JSON {
+    if let data = str.data(using: .utf8, allowLossyConversion: false) {
+        return JSON(data: data)
+    } else {
+        return JSON()
+    }
+}
+
+if CommandLine.arguments.count >= 2 {
+    printJson(json: hello(json: parseJson(str: CommandLine.arguments[1])))
+} else {
+    printJson(json: ["openwhisk", 1])
+    while let input = readLine() {
+        printJson(json: hello(json: parseJson(str: input)))
+    }
+}
diff --git a/openwhisk/_test/bc.sh b/openwhisk/_test/bc.sh
new file mode 100755
index 0000000..ff78878
--- /dev/null
+++ b/openwhisk/_test/bc.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+bc -q >&3
+
diff --git a/openwhisk/_test/exec b/openwhisk/_test/exec
new file mode 100755
index 0000000..67aee49
Binary files /dev/null and b/openwhisk/_test/exec differ
diff --git a/openwhisk/_test/exec.zip b/openwhisk/_test/exec.zip
new file mode 100644
index 0000000..bb7a22a
Binary files /dev/null and b/openwhisk/_test/exec.zip differ
diff --git a/openwhisk/_test/first/3/.gitignore b/openwhisk/_test/first/3/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/openwhisk/_test/first/3/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/openwhisk/_test/hello.sh b/openwhisk/_test/hello.sh
new file mode 100755
index 0000000..cc38507
--- /dev/null
+++ b/openwhisk/_test/hello.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+while read line
+do
+   name="$(echo $line | jq -r .name)" 
+   if [ "$name" == "*" ]
+   then echo "Goodbye!" >&2 
+        exit 0
+   fi
+   echo msg="hello $name"
+   echo '{"hello": "'$name'"}' >&3
+done
+
diff --git a/openwhisk/_test/second/17/.gitignore b/openwhisk/_test/second/17/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/openwhisk/_test/second/17/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/openwhisk/_test/second/3/.gitignore b/openwhisk/_test/second/3/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/openwhisk/_test/second/3/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/openwhisk/_test/second/8/.gitignore b/openwhisk/_test/second/8/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/openwhisk/_test/second/8/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/openwhisk/_test/second/ignore/.gitignore b/openwhisk/_test/second/ignore/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/openwhisk/_test/second/ignore/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/openwhisk/actionProxy.go b/openwhisk/actionProxy.go
new file mode 100644
index 0000000..5889b01
--- /dev/null
+++ b/openwhisk/actionProxy.go
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package openwhisk
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+	"os"
+)
+
+// theServer is the current server
+var theServer http.Server
+
+// theChannel is the channel communicating with the action
+var theExecutor *Executor
+
+// StartLatestAction tries to start
+// the more recently uplodaded
+// action if valid, otherwise remove it
+// and fallback to the previous, if any
+func StartLatestAction() error {
+
+	// find the action if any
+	highestDir := highestDir("./action")
+	if highestDir == 0 {
+		log.Println("no action found")
+		theExecutor = nil
+		return fmt.Errorf("no valid actions available")
+	}
+
+	// save the current executor
+	curExecutor := theExecutor
+
+	// try to launch the action
+	executable := fmt.Sprintf("./action/%d/exec", highestDir)
+	newExecutor := NewExecutor(executable)
+	log.Printf("starting %s", executable)
+	err := newExecutor.Start()
+	if err == nil {
+		theExecutor = newExecutor
+		if curExecutor != nil {
+			log.Println("stopping old executor")
+			curExecutor.Stop()
+		}
+		return nil
+	}
+
+	// cannot start, removing the action
+	// and leaving the current executor running
+
+	exeDir := fmt.Sprintf("./action/%d/", highestDir)
+	log.Printf("removing the failed action in %s", exeDir)
+	os.RemoveAll(exeDir)
+	return err
+}
+
+// Start creates a proxy to execute actions
+func Start() {
+	// handle initialization
+	http.HandleFunc("/init", initHandler)
+	// handle execution
+	http.HandleFunc("/run", runHandler)
+
+	// start
+	log.Println("Started!")
+	theServer.Addr = ":8080"
+	log.Fatal(theServer.ListenAndServe())
+}
diff --git a/openwhisk/actionProxy_test.go b/openwhisk/actionProxy_test.go
new file mode 100644
index 0000000..b46ca3d
--- /dev/null
+++ b/openwhisk/actionProxy_test.go
@@ -0,0 +1,44 @@
+package openwhisk
+
+import (
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestStartLatestAction(t *testing.T) {
+
+	// cleanup
+	os.RemoveAll("./action")
+	theExecutor = nil
+
+	// start an action that terminate immediately
+	buf := []byte("#!/bin/sh\ntrue\n")
+	extractAction(&buf, true)
+	StartLatestAction()
+	assert.Nil(t, theExecutor)
+
+	// start the action that emits 1
+	buf = []byte("#!/bin/sh\nwhile read a; do echo 1 >&3 ; done\n")
+	extractAction(&buf, true)
+	StartLatestAction()
+	theExecutor.io <- "x"
+	assert.Equal(t, <-theExecutor.io, "1")
+
+	// now start an action that terminate immediately
+	buf = []byte("#!/bin/sh\ntrue\n")
+	extractAction(&buf, true)
+	StartLatestAction()
+	theExecutor.io <- "y"
+	assert.Equal(t, <-theExecutor.io, "1")
+
+	// start the action that emits 2
+	buf = []byte("#!/bin/sh\nwhile read a; do echo 2 >&3 ; done\n")
+	extractAction(&buf, true)
+	StartLatestAction()
+	theExecutor.io <- "z"
+	assert.Equal(t, <-theExecutor.io, "2")
+	/**/
+	theExecutor.Stop()
+}
diff --git a/openwhisk/executor.go b/openwhisk/executor.go
new file mode 100644
index 0000000..6a48f4b
--- /dev/null
+++ b/openwhisk/executor.go
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package openwhisk
+
+import (
+	"bufio"
+	"fmt"
+	"log"
+	"os"
+	"os/exec"
+	"runtime"
+	"time"
+)
+
+// TIMEOUT to wait for process to start
+// and log to be produced
+const TIMEOUT = 5 * time.Millisecond
+
+// Executor is the container and the guardian  of a child process
+// It starts a command, feeds input and output, read logs and control its termination
+type Executor struct {
+	io      chan string
+	log     chan bool
+	exit    chan error
+	_cmd    *exec.Cmd
+	_input  *bufio.Writer
+	_output *bufio.Scanner
+	_logout *bufio.Scanner
+	_logerr *bufio.Scanner
+}
+
+// NewExecutor creates a child subprocess using the provided command line.
+// You can then start it getting a communication channel
+func NewExecutor(command string, args ...string) (proc *Executor) {
+	cmd := exec.Command(command, args...)
+
+	stdin, err := cmd.StdinPipe()
+	if err != nil {
+		return nil
+	}
+
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		return nil
+	}
+
+	stderr, err := cmd.StderrPipe()
+	if err != nil {
+		return nil
+	}
+
+	pipeOut, pipeIn, err := os.Pipe()
+	if err != nil {
+		return nil
+	}
+	cmd.ExtraFiles = []*os.File{pipeIn}
+
+	return &Executor{
+		make(chan string),
+		make(chan bool),
+		make(chan error),
+		cmd,
+		bufio.NewWriter(stdin),
+		bufio.NewScanner(pipeOut),
+		bufio.NewScanner(stdout),
+		bufio.NewScanner(stderr),
+	}
+}
+
+// collect log from a stream
+func _collect(ch chan string, scan *bufio.Scanner) {
+	for scan.Scan() {
+		ch <- scan.Text()
+	}
+}
+
+// loop over the command executing
+// returning when the command exits
+func (proc *Executor) run() {
+	log.Println("run: start")
+	err := proc._cmd.Start()
+	if err != nil {
+		proc.exit <- err
+		log.Println("run: early exit")
+		proc._cmd = nil // do not kill
+		return
+	}
+	// wait for the exit
+	proc.exit <- proc._cmd.Wait()
+	proc._cmd = nil // do not kill
+	log.Println("run: end")
+}
+
+// manage copying stdout and stder in output
+// with log guards
+func (proc *Executor) logger() {
+	log.Println("logger: start")
+	// poll stdout and stderr
+	chOut := make(chan string)
+	go _collect(chOut, proc._logout)
+	chErr := make(chan string)
+	go _collect(chErr, proc._logerr)
+
+	// wait for the signal
+	for <-proc.log {
+		// flush stdout
+		runtime.Gosched()
+		for loop := true; loop; {
+			select {
+			case buf := <-chOut:
+				fmt.Println(buf)
+			case <-time.After(TIMEOUT):
+				loop = false
+			}
+		}
+		fmt.Println("XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX")
+
+		// flush stderr
+		runtime.Gosched()
+		for loop := true; loop; {
+			select {
+			case buf := <-chErr:
+				fmt.Println(buf)
+			case <-time.After(TIMEOUT):
+				loop = false
+			}
+		}
+		fmt.Println("XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX")
+	}
+	log.Printf("logger: end")
+}
+
+// main service function
+// writing in input
+// and reading in output
+// using the provide channels
+func (proc *Executor) service() {
+	log.Println("service: start")
+	for {
+		in := <-proc.io
+		if in == "" {
+			log.Println("terminated upon request")
+			break
+		}
+		// input/output with the process
+		log.Printf(">>>%s\n", in)
+		proc._input.WriteString(in + "\n")
+		proc._input.Flush()
+		if proc._output.Scan() {
+			out := proc._output.Text()
+			log.Printf("<<<%s\n", out)
+			proc.io <- out
+			if out == "" {
+				break
+			}
+		}
+	}
+	log.Printf("service: end")
+}
+
+// Start execution of the command
+// returns an error if the program fails
+func (proc *Executor) Start() error {
+	// start the underlying executable
+	// check if died
+	go proc.run()
+	select {
+	case <-proc.exit:
+		// oops, it died
+		return fmt.Errorf("command exited")
+	case <-time.After(TIMEOUT):
+		// ok let's process it
+		go proc.service()
+		go proc.logger()
+	}
+	return nil
+}
+
+// Stop will kill the process
+// and close the channels
+func (proc *Executor) Stop() {
+	log.Println("stopping")
+	if proc._cmd != nil {
+		proc.log <- false
+		proc.io <- ""
+		proc._cmd.Process.Kill()
+		<-proc.exit
+		proc._cmd = nil
+	}
+	close(proc.io)
+	close(proc.exit)
+	close(proc.log)
+}
diff --git a/openwhisk/executor_test.go b/openwhisk/executor_test.go
new file mode 100644
index 0000000..b904cb3
--- /dev/null
+++ b/openwhisk/executor_test.go
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package openwhisk
+
+import (
+	"fmt"
+	"time"
+)
+
+func ExampleNewExecutor_failed() {
+	proc := NewExecutor("true")
+	err := proc.Start()
+	fmt.Println(err)
+	proc.Stop()
+	proc = NewExecutor("/bin/pwd")
+	err = proc.Start()
+	fmt.Println(err)
+	proc.Stop()
+	proc = NewExecutor("donotexist")
+	err = proc.Start()
+	fmt.Println(err)
+	proc.Stop()
+	proc = NewExecutor("/etc/passwd")
+	err = proc.Start()
+	fmt.Println(err)
+	proc.Stop()
+	// Output:
+	// command exited
+	// command exited
+	// command exited
+	// command exited
+}
+
+func ExampleNewExecutor_bc() {
+	proc := NewExecutor("_test/bc.sh")
+	err := proc.Start()
+	fmt.Println(err)
+	//proc.log <- true
+	proc.io <- "2+2"
+	fmt.Println(<-proc.io)
+	// and now, exit detection
+	proc.io <- "quit"
+	proc.log <- true
+	select {
+	case in := <-proc.io:
+		fmt.Println(in)
+	case <-proc.exit:
+		fmt.Println("exit")
+	}
+	time.Sleep(100 * time.Millisecond)
+	proc.Stop()
+	// Output:
+	// <nil>
+	// 4
+	// exit
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+}
+
+func ExampleNewExecutor_hello() {
+	proc := NewExecutor("_test/hello.sh")
+	err := proc.Start()
+	fmt.Println(err)
+	proc.io <- `{"name":"Mike"}`
+	fmt.Println(<-proc.io)
+	proc.log <- true
+	time.Sleep(100 * time.Millisecond)
+	proc.Stop()
+	time.Sleep(100 * time.Millisecond)
+	_, ok := <-proc.io
+	fmt.Printf("io %v\n", ok)
+	// Unordered output:
+	// <nil>
+	// {"hello": "Mike"}
+	// msg=hello Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// io false
+}
+
+func ExampleNewExecutor_term() {
+	proc := NewExecutor("_test/hello.sh")
+	err := proc.Start()
+	fmt.Println(err)
+	proc.io <- `{"name":"*"}`
+	var exited bool
+	select {
+	case <-proc.io:
+		exited = false
+	case <-proc.exit:
+		exited = true
+	}
+	proc.log <- true
+	fmt.Printf("exit %v\n", exited)
+	time.Sleep(100 * time.Millisecond)
+	proc.Stop()
+	time.Sleep(100 * time.Millisecond)
+	_, ok := <-proc.io
+	fmt.Printf("io %v\n", ok)
+	// Unordered output:
+	// <nil>
+	// exit true
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// Goodbye!
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// io false
+}
diff --git a/openwhisk/extractor.go b/openwhisk/extractor.go
new file mode 100644
index 0000000..17965bb
--- /dev/null
+++ b/openwhisk/extractor.go
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package openwhisk
+
+import (
+	"archive/zip"
+	"bytes"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"strconv"
+
+	"github.com/h2non/filetype"
+)
+
+func unzip(src []byte, dest string) error {
+	reader := bytes.NewReader(src)
+	r, err := zip.NewReader(reader, int64(len(src)))
+	if err != nil {
+		return err
+	}
+
+	os.MkdirAll(dest, 0755)
+
+	// Closure to address file descriptors issue with all the deferred .Close() methods
+	extractAndWriteFile := func(f *zip.File) error {
+		rc, err := f.Open()
+		if err != nil {
+			return err
+		}
+		defer func() {
+			rc.Close()
+		}()
+
+		path := filepath.Join(dest, f.Name)
+
+		if f.FileInfo().IsDir() {
+			os.MkdirAll(path, f.Mode())
+		} else {
+			os.MkdirAll(filepath.Dir(path), f.Mode())
+			f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
+			if err != nil {
+				return err
+			}
+			defer func() {
+				f.Close()
+			}()
+
+			_, err = io.Copy(f, rc)
+			if err != nil {
+				return err
+			}
+		}
+		return nil
+	}
+
+	for _, f := range r.File {
+		err := extractAndWriteFile(f)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// higherDir will find the highest numeric name a sub directory has
+// 0 if no numeric dir names found
+func highestDir(dir string) int {
+	files, err := ioutil.ReadDir(dir)
+	if err != nil {
+		return 0
+	}
+	max := 0
+	for _, file := range files {
+		n, err := strconv.Atoi(file.Name())
+		if err == nil {
+			if n > max {
+				max = n
+			}
+		}
+	}
+	return max
+}
+
+var currentDir = highestDir("./action")
+
+// extractAction accept a byte array write it to a file
+func extractAction(buf *[]byte, isScript bool) error {
+	if buf == nil || len(*buf) == 0 {
+		return fmt.Errorf("no file")
+	}
+	currentDir++
+	newDir := fmt.Sprintf("./action/%d", currentDir)
+	os.MkdirAll(newDir, 0755)
+	kind, err := filetype.Match(*buf)
+	if err != nil {
+		return err
+	}
+	if kind.Extension == "zip" {
+		log.Println("Extract Action, assuming a zip")
+		return unzip(*buf, newDir)
+	}
+	if kind.Extension == "elf" || isScript {
+		if isScript {
+			log.Println("Extract Action, assuming a script")
+		} else {
+			log.Println("Extract Action, assuming a binary")
+		}
+		return ioutil.WriteFile(newDir+"/exec", *buf, 0755)
+	}
+	log.Println("No valid action found")
+	return fmt.Errorf("unknown filetype %s", kind)
+}
diff --git a/openwhisk/extractor_test.go b/openwhisk/extractor_test.go
new file mode 100644
index 0000000..90db00a
--- /dev/null
+++ b/openwhisk/extractor_test.go
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package openwhisk
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"testing"
+
+	"github.com/h2non/filetype"
+	"github.com/stretchr/testify/assert"
+)
+
+func sys(cli string) {
+	cmd := exec.Command(cli)
+	out, err := cmd.CombinedOutput()
+	if err == nil {
+		fmt.Printf(">>>%s\n%s<<<\n", cli, string(out))
+	} else {
+		fmt.Println("KO")
+		log.Print(err)
+	}
+}
+
+func TestExtractActionTest_exec(t *testing.T) {
+	//sys("pwd")
+	// cleanup
+	assert.Nil(t, os.RemoveAll("./action"))
+	file, _ := ioutil.ReadFile("_test/exec")
+	extractAction(&file, false)
+	assert.Nil(t, exists("./action", "exec"))
+}
+
+func exists(dir, filename string) error {
+	path := fmt.Sprintf("%s/%d/%s", dir, highestDir(dir), filename)
+	_, err := os.Stat(path)
+	return err
+}
+
+func detect(dir, filename string) string {
+	path := fmt.Sprintf("%s/%d/%s", dir, highestDir(dir), filename)
+	file, _ := ioutil.ReadFile(path)
+	kind, _ := filetype.Match(file)
+	return kind.Extension
+}
+func TestExtractActionTest_exe(t *testing.T) {
+	//sys("pwd")
+	// cleanup
+	assert.Nil(t, os.RemoveAll("./action"))
+	// match  exe
+	file, _ := ioutil.ReadFile("_test/exec")
+	extractAction(&file, false)
+	assert.Equal(t, detect("./action", "exec"), "elf")
+}
+
+func TestExtractActionTest_zip(t *testing.T) {
+	//sys("pwd")
+	// cleanup
+	assert.Nil(t, os.RemoveAll("./action"))
+	// match  exe
+	file, _ := ioutil.ReadFile("_test/exec.zip")
+	extractAction(&file, false)
+	assert.Equal(t, detect("./action", "exec"), "elf")
+	assert.Nil(t, exists("./action", "etc"))
+	assert.Nil(t, exists("./action", "dir/etc"))
+}
+
+func TestExtractAction_script(t *testing.T) {
+	buf := []byte("#!/bin/sh\necho ok")
+	assert.NotNil(t, extractAction(&buf, false))
+	assert.Nil(t, extractAction(&buf, true))
+}
+
+func TestHighestDir(t *testing.T) {
+	assert.Equal(t, highestDir("./_test"), 0)
+	assert.Equal(t, highestDir("./_test/first"), 3)
+	assert.Equal(t, highestDir("./_test/second"), 17)
+}
diff --git a/openwhisk/initHandler.go b/openwhisk/initHandler.go
new file mode 100644
index 0000000..59dce64
--- /dev/null
+++ b/openwhisk/initHandler.go
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package openwhisk
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+)
+
+type initRequest struct {
+	Value struct {
+		Code   string `json:",omitempty"`
+		Binary bool   `json:",omitempty"`
+	} `json:",omitempty"`
+}
+
+func sendOK(w http.ResponseWriter) {
+	// answer OK
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("Content-Length", "12")
+	w.Write([]byte("{\"ok\":true}\n"))
+	if f, ok := w.(http.Flusher); ok {
+		f.Flush()
+	}
+}
+
+func initHandler(w http.ResponseWriter, r *http.Request) {
+
+	// read body of the request
+	// log.Println("init: reading")
+	body, err := ioutil.ReadAll(r.Body)
+	defer r.Body.Close()
+	if err != nil {
+		sendError(w, http.StatusBadRequest, fmt.Sprintf("%v", err))
+	}
+
+	// decode request parameters
+	//log.Println("init: decoding")
+	var request initRequest
+	err = json.Unmarshal(body, &request)
+
+	if err != nil {
+		sendError(w, http.StatusBadRequest, fmt.Sprintf("Error unmarshaling request: %v", err))
+		return
+	}
+
+	if request.Value.Code == "" {
+		sendOK(w)
+		return
+	}
+
+	// check if it is a binary
+	if request.Value.Binary {
+		var decoded []byte
+		decoded, err = base64.StdEncoding.DecodeString(request.Value.Code)
+		if err != nil {
+			sendError(w, http.StatusBadRequest, "cannot decode the request: "+err.Error())
+			return
+		}
+		// extract the replacement, stopping and then starting the action
+		err = extractAction(&decoded, false)
+	} else {
+		buf := []byte(request.Value.Code)
+		err = extractAction(&buf, true)
+	}
+	if err != nil {
+		sendError(w, http.StatusBadRequest, "invalid action: "+err.Error())
+		return
+	}
+
+	// stop and start
+	err = StartLatestAction()
+	if err != nil {
+		sendError(w, http.StatusBadRequest, "cannot start action: "+err.Error())
+		return
+	}
+	sendOK(w)
+}
diff --git a/openwhisk/runHandler.go b/openwhisk/runHandler.go
new file mode 100644
index 0000000..4aee1a6
--- /dev/null
+++ b/openwhisk/runHandler.go
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package openwhisk
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"strings"
+)
+
+// Params are the parameteres sent to the action
+type Params struct {
+	Value json.RawMessage `json:"value"`
+}
+
+// ErrResponse is the response when there are errors
+type ErrResponse struct {
+	Error string `json:"error"`
+}
+
+func sendError(w http.ResponseWriter, code int, cause string) {
+	errResponse := ErrResponse{Error: cause}
+	b, err := json.Marshal(errResponse)
+	if err != nil {
+		fmt.Println("error marshalling error response:", err)
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.WriteHeader(code)
+	w.Write(b)
+	w.Write([]byte("\n"))
+}
+
+func runHandler(w http.ResponseWriter, r *http.Request) {
+
+	// parse the request
+	params := Params{}
+	body, err := ioutil.ReadAll(r.Body)
+	defer r.Body.Close()
+	if err != nil {
+		sendError(w, http.StatusBadRequest, fmt.Sprintf("Error reading request body: %v", err))
+		return
+	}
+
+	// decode request parameters
+	err = json.Unmarshal(body, &params)
+	if err != nil {
+		sendError(w, http.StatusBadRequest, fmt.Sprintf("Error unmarshaling request: %v", err))
+		return
+	}
+
+	// check if you have an action
+	if theExecutor == nil {
+		sendError(w, http.StatusBadRequest, fmt.Sprintf("no action defined yet"))
+		return
+	}
+
+	// execute the action
+	// and check for early termination
+	theExecutor.io <- string(params.Value)
+	var response string
+	var exited bool
+	select {
+	case response = <-theExecutor.io:
+		exited = false
+	case err = <-theExecutor.exit:
+		exited = true
+	}
+
+	// check for early termination
+	if exited {
+		theExecutor = nil
+		sendError(w, http.StatusBadRequest, fmt.Sprintf("command exited"))
+		return
+	}
+
+	// flush the logs sending the activation message at the end
+	theExecutor.log <- true
+
+	// check response
+	if response == "" {
+		sendError(w, http.StatusBadRequest, fmt.Sprintf("%v", err))
+		return
+	}
+
+	// return the response
+	if !strings.HasSuffix(response, "\n") {
+		response = response + "\n"
+	}
+	log.Print(response)
+	w.Header().Set("Content-Type", "application/json")
+	numBytesWritten, err := w.Write([]byte(response))
+
+	// flush output
+	if f, ok := w.(http.Flusher); ok {
+		f.Flush()
+	}
+
+	// diagnostic when writing problems
+	if err != nil {
+		sendError(w, http.StatusInternalServerError, fmt.Sprintf("Error writing response: %v", err))
+		return
+	}
+	if numBytesWritten != len(response) {
+		sendError(w, http.StatusInternalServerError, fmt.Sprintf("Only wrote %d of %d bytes to response", numBytesWritten, len(response)))
+		return
+	}
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e305871
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,19 @@
+include 'core:goproxy'
+include 'core:go19action'
+
+rootProject.name = 'runtime-golang'
+
+gradle.ext.openwhisk = [
+        version: '1.0.0-SNAPSHOT'
+]
+
+gradle.ext.scala = [
+    version: '2.11.8',
+    compileFlags: ['-feature', '-unchecked', '-deprecation', '-Xfatal-warnings', '-Ywarn-unused-import']
+]
+
+gradle.ext.scalafmt = [
+    version: '1.5.0',
+    config: new File(rootProject.projectDir, '.scalafmt.conf')
+]
+
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 0000000..4499418
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1,4 @@
+action/
+*.json
+*.err
+
diff --git a/test/README.md b/test/README.md
new file mode 100644
index 0000000..f35fa34
--- /dev/null
+++ b/test/README.md
@@ -0,0 +1,8 @@
+# Test
+
+How to run tests:
+
+- install `cram`: `sudo pip install cram`
+- start the tester: `cd test ; ./start.sh`
+- in another terminal `cd test ; cram test.t`
+
diff --git a/test/bin/.gitignore b/test/bin/.gitignore
new file mode 100644
index 0000000..b374f99
--- /dev/null
+++ b/test/bin/.gitignore
@@ -0,0 +1,3 @@
+hello_*
+empty
+hi
diff --git a/test/bin/build.sh b/test/bin/build.sh
new file mode 100755
index 0000000..4ed5b0a
--- /dev/null
+++ b/test/bin/build.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+FILE=${1:?go file}
+OUT=$(basename $FILE)
+BIN=${OUT%%.go}
+ZIP=${BIN}.zip
+go build -i -o bin/$BIN $FILE
+GOOS=linux GOARCH=amd64 go build -o exec $FILE
+zip zip/$ZIP exec
+rm exec
+echo "built bin/$BIN zip/$ZIP"
diff --git a/test/bin/init.sh b/test/bin/init.sh
new file mode 100755
index 0000000..e700bb4
--- /dev/null
+++ b/test/bin/init.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+FILE=${1:?file}
+JSON=${2:-/tmp/json$$}
+if file -i $FILE | grep text/ >/dev/null
+then echo '{"value":{"main":"main","code":'$(cat $FILE | jq -R -s .)'}}' >/tmp/json$$
+else echo '{"value":{"binary":true,"code":"'$(base64 -w 0 $FILE)'"}}' >/tmp/json$$
+fi
+#cat $JSON | jq .
+curl -H "Content-Type: application/json" -XPOST -w "%{http_code}\n" http://localhost:${PORT:-8080}/init -d @$JSON 2>/dev/null
+rm /tmp/json$$ 2>/dev/null
diff --git a/test/bin/post.sh b/test/bin/post.sh
new file mode 100755
index 0000000..6255f39
--- /dev/null
+++ b/test/bin/post.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+FILE=${1:?file}
+curl -H "Content-Type: application/json" -XPOST -w "%{http_code}\n" http://localhost:${PORT:-8080}/init -d @$FILE 2>/dev/null
diff --git a/test/bin/run.sh b/test/bin/run.sh
new file mode 100755
index 0000000..0df821e
--- /dev/null
+++ b/test/bin/run.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+DEFAULT='{"name": "Mike"}'
+JSON=${1:-$DEFAULT}
+DATA='{"value":'$JSON'}'
+curl -H "Content-Type: application/json" -w "%{http_code}\n" -XPOST http://localhost:${PORT:-8080}/run -d "$DATA" 2>/dev/null
+
diff --git a/test/crammer.sh b/test/crammer.sh
new file mode 100644
index 0000000..ca816d9
--- /dev/null
+++ b/test/crammer.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+export T=$PWD
+while read -p "> " line
+do eval ${line#$}
+done
diff --git a/test/etc/hello.js b/test/etc/hello.js
new file mode 100644
index 0000000..a1bf768
--- /dev/null
+++ b/test/etc/hello.js
@@ -0,0 +1,5 @@
+function main(args) {
+  return {
+     "result": "Hello, "+args.name
+  }
+}
diff --git a/test/etc/hello.sh b/test/etc/hello.sh
new file mode 100755
index 0000000..9b7f318
--- /dev/null
+++ b/test/etc/hello.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+while read line
+do
+   name="$(echo $line | jq -r .name)"
+   if test "$name" == ""
+   then exit
+   fi
+   echo "name=$name" 
+   hello="Hello, $name"
+   echo '{"hello":"'$hello'"}' >&3
+done
+
diff --git a/test/src/empty.go b/test/src/empty.go
new file mode 100644
index 0000000..35a2611
--- /dev/null
+++ b/test/src/empty.go
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+func main() {}
diff --git a/test/src/hello/.gitignore b/test/src/hello/.gitignore
new file mode 100644
index 0000000..2d71be7
--- /dev/null
+++ b/test/src/hello/.gitignore
@@ -0,0 +1,8 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
+Package.resolved
+*.json
+.*_history
+.viminfo
diff --git a/test/src/hello/hello.go b/test/src/hello/hello.go
new file mode 100644
index 0000000..b0592d0
--- /dev/null
+++ b/test/src/hello/hello.go
@@ -0,0 +1,30 @@
+package hello
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+)
+
+// Hello receive an event in format
+// { "name": "Mike"}
+// and returns a greeting in format
+// { "greetings": "Hello, Mike"}
+func Hello(event json.RawMessage) (json.RawMessage, error) {
+	// input and output
+	var input struct {
+		Name string
+	}
+	var output struct {
+		Greetings string `json:"greetings"`
+	}
+	// read the input event
+	json.Unmarshal(event, &input)
+	if input.Name != "" {
+		// handle the event
+		output.Greetings = "Hello, " + input.Name
+		log.Println(output.Greetings)
+		return json.Marshal(output)
+	}
+	return nil, fmt.Errorf("no name specified")
+}
diff --git a/test/src/hello/hello_test.go b/test/src/hello/hello_test.go
new file mode 100644
index 0000000..0624cad
--- /dev/null
+++ b/test/src/hello/hello_test.go
@@ -0,0 +1,28 @@
+package hello
+
+import (
+	"fmt"
+)
+
+func ExampleHello() {
+	name := []byte(`{ "name": "Mike"}`)
+	data, _ := Hello(name)
+	fmt.Printf("%s", data)
+	// Output:
+	// {"greetings":"Hello, Mike"}
+}
+
+func ExampleHello_noName() {
+	name := []byte(`{ "noname": "Mike"}`)
+	_, err := Hello(name)
+	fmt.Print(err)
+	// Output:
+	// no name specified
+}
+func ExampleHello_badJson() {
+	name := []byte(`{{`)
+	_, err := Hello(name)
+	fmt.Print(err)
+	// Output:
+	// no name specified
+}
diff --git a/test/src/hello_greeting.go b/test/src/hello_greeting.go
new file mode 100644
index 0000000..3fab67f
--- /dev/null
+++ b/test/src/hello_greeting.go
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package main
+
+import (
+	"log"
+	"os"
+
+	"github.com/sciabarracom/incubator-openwhisk-client-go/whisk"
+	"github.com/sciabarracom/incubator-openwhisk-runtime-go/test/src/hello"
+)
+
+func main() {
+	log.SetPrefix("hello_greeting: ")
+	whisk.StartWithArgs(hello.Hello, os.Args[1:])
+}
diff --git a/test/src/hello_message.go b/test/src/hello_message.go
new file mode 100644
index 0000000..84e17ee
--- /dev/null
+++ b/test/src/hello_message.go
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package main
+
+import (
+	"encoding/json"
+	"log"
+	"os"
+
+	"github.com/sciabarracom/incubator-openwhisk-client-go/whisk"
+)
+
+func sayHello(event json.RawMessage) (json.RawMessage, error) {
+	var obj map[string]interface{}
+	json.Unmarshal(event, &obj)
+	name, ok := obj["name"].(string)
+	if !ok {
+		name = "Stranger"
+	}
+	log.Printf("name=%s\n", name)
+	msg := map[string]string{"message": ("Hello, " + name + "!")}
+	return json.Marshal(msg)
+}
+
+func main() {
+	log.SetPrefix("hello_message: ")
+	whisk.StartWithArgs(sayHello, os.Args[1:])
+}
diff --git a/test/src/hi.go b/test/src/hi.go
new file mode 100644
index 0000000..6a07be8
--- /dev/null
+++ b/test/src/hi.go
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("hi")
+}
diff --git a/test/start.sh b/test/start.sh
new file mode 100755
index 0000000..709f312
--- /dev/null
+++ b/test/start.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+cd "$(dirname $0)"
+bin/build.sh src/hello_greeting.go 
+bin/build.sh src/hello_message.go 
+bin/build.sh src/empty.go
+bin/build.sh src/hi.go
+rm -Rvf action
+if test -n "$1"
+then go run ../main/proxy.go -debug
+else
+  #go build -o ../docker/proxy ../main/proxy.go
+  #docker build -t action-golang-v1.9 ../docker/
+  cd ..
+  ./gradlew distDocker
+  cd test
+  docker run -ti -p 8080:8080  action-golang-v1.9 "$@" 
+fi
diff --git a/test/test.t b/test/test.t
new file mode 100644
index 0000000..a85b86c
--- /dev/null
+++ b/test/test.t
@@ -0,0 +1,93 @@
+  $ export T=$TESTDIR
+
+  $ $T/bin/run.sh 
+  {"error":"no action defined yet"}
+  400
+
+  $ $T/bin/post.sh $T/etc/empty.json
+  {"ok":true}
+  200
+
+  $ $T/bin/init.sh $T/test.t
+  {"error":"cannot start action: command exited"}
+  400
+
+  $ $T/bin/init.sh $T/bin/empty
+  {"error":"cannot start action: command exited"}
+  400
+
+  $ $T/bin/init.sh $T/bin/hi
+  {"error":"cannot start action: command exited"}
+  400
+
+  $ $T/bin/run.sh 
+  {"error":"no action defined yet"}
+  400
+
+  $ $T/bin/init.sh $T/bin/hello_message
+  {"ok":true}
+  200
+
+  $ $T/bin/run.sh 
+  {"message":"Hello, Mike!"}
+  200
+
+  $ $T/bin/init.sh $T/bin/hello_greeting
+  {"ok":true}
+  200
+
+  $ $T/bin/run.sh 
+  {"greetings":"Hello, Mike"}
+  200
+
+  $ $T/bin/init.sh $T/zip/hello_message.zip
+  {"ok":true}
+  200
+
+  $ $T/bin/run.sh 
+  {"message":"Hello, Mike!"}
+  200
+
+  $ $T/bin/init.sh $T/zip/hello_greeting.zip
+  {"ok":true}
+  200
+
+  $ $T/bin/run.sh 
+  {"greetings":"Hello, Mike"}
+  200
+
+  $ $T/bin/init.sh $T/test.t
+  {"error":"cannot start action: command exited"}
+  400
+
+  $ $T/bin/run.sh 
+  {"greetings":"Hello, Mike"}
+  200
+
+  $ $T/bin/init.sh $T/bin/empty
+  {"error":"cannot start action: command exited"}
+  400
+
+  $ $T/bin/run.sh 
+  {"greetings":"Hello, Mike"}
+  200
+
+  $ $T/bin/init.sh $T/bin/hi
+  {"error":"cannot start action: command exited"}
+  400
+
+  $ $T/bin/init.sh $T/etc/hello.sh
+  {"ok":true}
+  200
+
+  $ $T/bin/run.sh
+  {"hello":"Hello, Mike"}
+  200
+
+  $ $T/bin/run.sh '{"name": ""}'
+  {"error":"command exited"}
+  400
+
+  $ $T/bin/run.sh
+  {"error":"no action defined yet"}
+  400
\ No newline at end of file
diff --git a/test/zip/.gitignore b/test/zip/.gitignore
new file mode 100644
index 0000000..c4c4ffc
--- /dev/null
+++ b/test/zip/.gitignore
@@ -0,0 +1 @@
+*.zip


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services