Add Python 3.10 runtime based on Python 3.9 runtime. (#128)


diff --git a/.travis.yml b/.travis.yml
index 3df9c76..0827243 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -45,7 +45,7 @@
       all_branches: true
       repo: apache/openwhisk-runtime-python
   - provider: script
-    script: "./tools/travis/publish.sh openwhisk 3 nightly && ./tools/travis/publish.sh openwhisk 3-ai nightly && ./tools/travis/publish.sh openwhisk 39 nightly"
+    script: "./tools/travis/publish.sh openwhisk 3 nightly && ./tools/travis/publish.sh openwhisk 3-ai nightly && ./tools/travis/publish.sh openwhisk 39 nightly && ./tools/travis/publish.sh openwhisk 310 nightly"
     on:
       branch: master
       repo: apache/openwhisk-runtime-python
diff --git a/README.md b/README.md
index 5b8d678..a85e69c 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@
 
 - Python 3.7 (python:3.7 & openwhisk/action-python-v3.7)
 - Python 3.9 (python:3.9 & openwhisk/action-python-v3.9)
+- Python 3.10 (python:3.10 & openwhisk/action-python-v3.10)
 - Python 3.6 AI (python:3.6 & openwhisk/action-python-v3.6-ai)
 
 This README documents the build, customization and testing of these runtime images.
@@ -152,6 +153,6 @@
 zip -j -r myaction | docker run -i action-python-v3.7 -compile main > myaction.zip
 ```
 
-You may use `v3.9` or `v3.6-ai` as well according to your Python version needs.
+You may use `v3.10`, `v3.9` or `v3.6-ai` as well according to your Python version needs.
 
 The resulting action includes a virtualenv already built for you and that is fast to deploy and start as all the dependencies are already resolved. Note that there is a limit on the size of the zip file and this approach will not work for installing large libraries like Pandas or Numpy, instead use the provide "v.3.6-ai"  runtime instead which provides these libraries already for you.
diff --git a/core/python310Action/Dockerfile b/core/python310Action/Dockerfile
new file mode 100644
index 0000000..e777425
--- /dev/null
+++ b/core/python310Action/Dockerfile
@@ -0,0 +1,70 @@
+#
+# 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.
+#
+
+# build go proxy from source
+FROM golang:1.18 AS builder_source
+ARG GO_PROXY_GITHUB_USER=apache
+ARG GO_PROXY_GITHUB_BRANCH=master
+RUN git clone --branch ${GO_PROXY_GITHUB_BRANCH} \
+   https://github.com/${GO_PROXY_GITHUB_USER}/openwhisk-runtime-go /src ;\
+   cd /src ; env GO111MODULE=on CGO_ENABLED=0 go build main/proxy.go && \
+   mv proxy /bin/proxy
+
+# or build it from a release
+FROM golang:1.18 AS builder_release
+ARG GO_PROXY_RELEASE_VERSION=1.18@1.19.0
+RUN curl -sL \
+  https://github.com/apache/openwhisk-runtime-go/archive/{$GO_PROXY_RELEASE_VERSION}.tar.gz\
+  | tar xzf -\
+  && cd openwhisk-runtime-go-*/main\
+  && GO111MODULE=on go build -o /bin/proxy
+
+FROM python:3.10-buster
+
+# select the builder to use
+ARG GO_PROXY_BUILD_FROM=release
+
+# install zip
+RUN apt-get update && apt-get install -y zip \
+    && rm -rf /var/lib/apt/lists/*
+
+# Install common modules for python
+COPY requirements_common.txt requirements_common.txt
+COPY requirements.txt requirements.txt
+RUN pip3 install --upgrade pip six wheel &&\
+    pip3 install --no-cache-dir -r requirements.txt
+
+RUN mkdir -p /action
+WORKDIR /
+
+COPY --from=builder_source /bin/proxy /bin/proxy_source
+COPY --from=builder_release /bin/proxy /bin/proxy_release
+RUN mv /bin/proxy_${GO_PROXY_BUILD_FROM} /bin/proxy
+
+ADD bin/compile /bin/compile
+ADD lib/launcher.py /lib/launcher.py
+
+# log initialization errors
+ENV OW_LOG_INIT_ERROR=1
+# the launcher must wait for an ack
+ENV OW_WAIT_FOR_ACK=1
+# using the runtime name to identify the execution environment
+ENV OW_EXECUTION_ENV=openwhisk/action-python-v3.10
+# compiler script
+ENV OW_COMPILER=/bin/compile
+
+ENTRYPOINT ["/bin/proxy"]
diff --git a/core/python310Action/build.gradle b/core/python310Action/build.gradle
new file mode 100644
index 0000000..123b98c
--- /dev/null
+++ b/core/python310Action/build.gradle
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+ext.dockerImageName = 'action-python-v3.10'
+apply from: '../../gradle/docker.gradle'
+
+distDocker.dependsOn 'copyLib'
+distDocker.dependsOn 'copyBin'
+distDocker.dependsOn 'copyReqrCommon'
+distDocker.finalizedBy('cleanup')
+
+task copyLib(type: Copy) {
+    from '../python3Action/lib'
+    into './lib'
+}
+
+task copyBin(type: Copy) {
+    from '../python3Action/bin'
+    into './bin'
+}
+
+task copyReqrCommon(type: Copy) {
+    from '../requirements_common.txt'
+    into './'
+}
+
+task cleanup(type: Delete) {
+    delete 'bin'
+    delete 'lib'
+    delete 'requirements_common.txt'
+}
diff --git a/core/python310Action/requirements.txt b/core/python310Action/requirements.txt
new file mode 100644
index 0000000..569a25f
--- /dev/null
+++ b/core/python310Action/requirements.txt
@@ -0,0 +1,2 @@
+# default packages available for action-python-v3.10
+-r requirements_common.txt
diff --git a/settings.gradle b/settings.gradle
index 82b9a9a..5cdc665 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -20,6 +20,7 @@
 include 'core:python3Action'
 include 'core:python36AiAction'
 include 'core:python39Action'
+include 'core:python310Action'
 
 rootProject.name = 'runtime-python'
 
diff --git a/tests/src/test/resources/build.sh b/tests/src/test/resources/build.sh
index bbc4616..b3c72db 100755
--- a/tests/src/test/resources/build.sh
+++ b/tests/src/test/resources/build.sh
@@ -21,7 +21,7 @@
   exit 0
 fi
 
-for i in v3.7 v3.6-ai v3.9
+for i in v3.7 v3.6-ai v3.9 v3.10
 do echo "*** $i ***"
    zip -r -j - python_virtualenv | docker run -i action-python-$i -compile main >python-${i}_virtualenv.zip
    cp python-${i}_virtualenv.zip python-${i}_virtualenv_invalid_main.zip
diff --git a/tests/src/test/scala/runtime/actionContainers/Python310Tests.scala b/tests/src/test/scala/runtime/actionContainers/Python310Tests.scala
new file mode 100644
index 0000000..4db7721
--- /dev/null
+++ b/tests/src/test/scala/runtime/actionContainers/Python310Tests.scala
@@ -0,0 +1,33 @@
+/*
+ * 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 runtime.actionContainers
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+
+@RunWith(classOf[JUnitRunner])
+class Python310Tests extends Python37Tests {
+
+  override lazy val imageName = "action-python-v3.10"
+
+  override lazy val zipPrefix = "python-v3.10"
+
+  override lazy val errorCodeOnRun = false
+
+  override val testNoSource = TestConfig("", hasCodeStub = false)
+}
diff --git a/tools/travis/publish.sh b/tools/travis/publish.sh
index 12e1b7d..3215103 100755
--- a/tools/travis/publish.sh
+++ b/tools/travis/publish.sh
@@ -36,6 +36,8 @@
   RUNTIME="python36AiAction"
 elif [ ${RUNTIME_VERSION} == "39" ]; then
   RUNTIME="python39Action"
+elif [ ${RUNTIME_VERSION} == "310" ]; then
+  RUNTIME="python310Action"
 fi
 
 if [[ ! -z ${DOCKER_USER} ]] && [[ ! -z ${DOCKER_PASSWORD} ]]; then