rabbah closed pull request #1: Add the base source code for Ruby 2.5 Runtime
URL: https://github.com/apache/incubator-openwhisk-runtime-ruby/pull/1
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..ed2c749
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,72 @@
+# Whisk
+nginx.conf
+whisk.properties
+default.props
+
+.ant-targets-build.xml
+/results/
+/logs/
+/config/custom-config.xml
+results
+*.retry
+
+# Environments
+/ansible/environments/*
+!/ansible/environments/distributed
+!/ansible/environments/docker-machine
+!/ansible/environments/local
+!/ansible/environments/mac
+
+# Eclipse
+bin/
+**/.project
+.settings/
+.classpath
+.cache-main
+.cache-tests
+
+# Linux
+*~
+
+# Mac
+.DS_Store
+
+# Gradle
+.gradle
+build/
+!/tools/build/
+
+# Python
+.ipynb_checkpoints/
+*.pyc
+
+# NodeJS
+node_modules
+
+# Vagrant
+.vagrant*
+
+# IntelliJ
+.idea
+*.class
+*.iml
+out/
+
+# Ansible
+ansible/environments/docker-machine/hosts
+ansible/db_local.ini*
+ansible/tmp/*
+ansible/roles/nginx/files/openwhisk-client*
+ansible/roles/nginx/files/*.csr
+ansible/roles/nginx/files/*cert.pem
+
+# .zip files must be explicited whitelisted
+*.zip
+!tests/dat/actions/blackbox.zip
+!tests/dat/actions/helloSwift.zip
+!tests/dat/actions/python.zip
+!tests/dat/actions/python2_virtualenv.zip
+!tests/dat/actions/python3_virtualenv.zip
+!tests/dat/actions/python_virtualenv_dir.zip
+!tests/dat/actions/python_virtualenv_name.zip
+!tests/dat/actions/zippedaction.zip
\ No newline at end of file
diff --git a/.scalafmt.conf b/.scalafmt.conf
new file mode 100644
index 0000000..7084efe
--- /dev/null
+++ b/.scalafmt.conf
@@ -0,0 +1,9 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
contributor
+# license agreements; and to You under the Apache License, Version 2.0.
+
+style = intellij
+danglingParentheses = false
+maxColumn = 120
+docstrings = JavaDoc
+rewrite.rules = [SortImports]
+project.git = true
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..745c73b
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,41 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
contributor
+# license agreements; and to You under the Apache License, Version 2.0.
+
+sudo: required
+group: deprecated-2017Q3
+language: scala
+scala:
+- 2.11.11
+services:
+ - docker
+before_install:
+ - "./tools/travis/setup.sh"
+install: true
+script:
+ - "./tools/travis/build.sh && ./tools/travis/test.sh"
+deploy:
+ - provider: script
+ script: "./tools/travis/publish.sh openwhisk ${TRAVIS_TAG%@*}
${TRAVIS_TAG##*@}"
+ on:
+ tags: true
+ all_branches: true
+ repo: apache/incubator-openwhisk-runtime-ruby
+ - provider: script
+ script: "./tools/travis/publish.sh openwhisk 2 latest &&
./tools/travis/publish.sh openwhisk 3 latest"
+ on:
+ branch: master
+ repo: apache/incubator-openwhisk-runtime-ruby
+env:
+ global:
+ - secure:
Ac7MlWUpsLwmbcjCigDf+vftiXAEJbf78FVm2QCd4IeFqlRiNbQm3bWRXOOW3Zqh4R4o/wSFpFq0qKPrI9WJjOcxD0GxFboq2iGWTegddfDbENV/IBmjurl6NzYY6dUB7GUSmHYy6tdKnxMhjHzZCn2m4OVWltcMtnOV0G+uYvbnre7vXfb67duzJF+mYpJJ7rC0wGh+N50bYvm0BTtPcQMYJibq6sDTY8Lr1b74QGdM/sODre69CbuiwAA7Ed0hajEcMkv8YnwY0De/PL7hthUyCWSD+i1AhtD+3odA64yX/pAbBzfCcqPoI0Nt5ZmAAUKLJ9pAHCc74dlmYzj9wj1z5TS0nLOE2l++vNvgsA3/c+lqrBv6hc9FmSKx4yfNdFCakVLEfdRJw/45ibIRN5dCW5tDdIx8aciBV8c0CyYgSLRvtVuvw5IX7fbwhHOZ8ftTRzCHS0Nd9zs4x3za+1aucWFQEyrkPtXZIiqeuV7yI4S7yYhGeJy/3KmuN7ljDiTElZb/W9W22ryw/UwCyyZBGnvhomAwInS3E51Jv8GuOU9FVliGzGs2yLuVQ1FjIZ25Au5W4TLTor/jE+dxOlH4u25neqsKBxzG4iyJs3AfJnycD8op7JbTG64OqpVvaB8A4N3P416IxGALSFVv2BWSLY4qWBRVKHbZAHn2tTg=
+ - secure:
fte97QoQReAM0obk/tjxYvF/zhRbcyIFxa1jnFuv1m1YKA/pEWue5uMIDZZa1pynN9rFFgOvR2W34Gm2aHl0wJs18Oqi2Es1cSfDDmuYguEIYZmlfbrjXY2gvitH5+6JA6fc4aMmrgOav+WVbzdzBqy511CWEDsejWflAnFpKJqTnYdLqe8L8FAJfteY/cj5Bvz7G5o8WbH+ag2CopjkEKkx4KXsJ7DadKI+BbI9hkMUvPVnTULaf7Q5z3oG92g1XwjKrfN6Z2y8tQukWg+n5pfVY0yLpWvd/Q/9HyilB0s06EzORzE00DTLnvYNGaVsx99m6H11e0Tn5Sx5S6rJOmJa5T2vTPGmsojpgHm1Lz12AYVxRmWvFbTLg8KDNN9H6uTc8DKPGb/mLkkD5ruWXJWjvpd+m+eRcpJOZ9cvPXWFYvCF0VAPCbK0DakInnmCvZyQ6ZLFgGrtkNt51kkTZGR68d0UW9rwrBS/JaaRdKIbSOHB346trSO2raYIQhaaAtv+tHCOVVAuoVC5Erzw+bojrWtTNQl9FZ2NM2jFaoeiRmi/rB9hqs8iK2BD20EnWfaMRf5XOkBJXYDQ804EnaOAHP/ZO94Ziojotf5SaXSeWG/zsxbT13u+zTdEf3auDofe8exzCpXuOHAQrB0b+Dy2OSKRPwzMHsfto6/OU9w=
+notifications:
+ slack:
+ secure:
ipYx0BX7TwWmG+qeyAllxBxrPn21dJubfEbALn8yTOZfykTWpVtohWrqieAnc6kFQRqz1TAs1wzyjvTcutaIMi9C7FcJrYRIMUMpyID3ca5FeZopr/LBW53XHj/SgZWwFRAnWsjYnSydfZJnUBInI6por8jdVqVgsCMTDsvcXFEoHdVj9M93aB7y8pgqZhfYkQOPojktDu7nMxjsjKJN7dT+g/1kVWtS8DQQCXHTPP44YquxOCgAFn+CZjes9Jm+vz5lk5Azksi48yiHwFiLYZo5gcnwr2n2IspjBs6db10rkCe693vIpoW4T3KujMIKwVXrgUl4pDME1nN/loEBHm0DjKoeOD13DHqITMTPX4sR94TCGf0lMyaag/JOETdDgC9hAbh4aSx1cj4hGXiuGrNMbYblMBsjvujqFp2nraw/DE93pHGRY9lbBZIjEVhj/jBmOv1I3yRdXHj4VybZc4JGnfQVGz/RYDOEmKGoLoVWFp+hGAHDqVos9ZvEBByMAAmgVD2/D5cBER3oskp/JJjKmFLk614FcNh4D7/+bYAFhxbCgnoLHH5GeSo0q73O3JSi5pZggEIN9xis7SVQgPl+MUDTe5N3X3J3bQc7cMYGLzh0VNSyYaHuW2P02vWBNE/FAkL8UGrfGsXnDyGRRxo8fYHTXpIjhIkE7tY5eZQ=
+ on_pull_requests: false
+ on_success: change
+ on_failure: always
+ webhooks:
+ urls:
+ # travis2slack webhook to enable DMs on openwhisk-team.slack.com to PR
authors with TravisCI results
+ secure:
"jhiMGpQ6kJFWjjsO68RmgD2Lga7jgNE+EKwND0dMOvzf5llMLFDKcY5J3tgtrqYaslQdXeuYeru/9qJrTTjFEu+vz3iCwoJ/eme+D0TtTIFGlPr7oa9tZlWrkPM/0zFLq7KjJauIIX2+6qrGVrNJJ6ENfr4U8Ir8q51oLIk44bsCeB8EmkahPOlNG6kcNqgpxHWKYUdUIg3B0GxqCKida/76dXDTRHCV2dZuT2bXz2oSJYog/lybomsjQIUZj0+HqxecgWTzag3Y6rTpK+m+vywazHP91hE+oU4e7YrxCH6v9+ukoWaljFqO5ZEKXcpx6tzx8Q0FvoTP8vGOO9b/t1loVcA8OxSJDrtOAztfoz/u0HJN6vnVt+maqnrYAD1F4pxA63JA6/+a7firmtADP7A/WQMZg6RgEkGUr+amFn303dTvgjDDkZ4oH8MAr0EPsneGUA2MZgB3i1MEcnCrYzT7KpYmDmFLoFhS9OX8f1H3zi5DLZZbZ1jbW/Ay4BgvjdoC8vmhAsDfVvyY9P240+nQ9NrnjaAUMD4XI/6JAKekfoxvsnc9W8gKBGTNfzi55AVe7HbzB/wCd58c2CV3Ev3RRwKQpH67jLBROpg2ocRQr0BUeHmfOT7NV4BCqdw1eVkZWBw4oxVaCHelDdICwgPn696W5t/1UVl4tLTt1rk="
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cb4547d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,191 @@
+
+ Apache License
+ Version 2.0, January 2004
+ https://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
+
+ Copyright 2015 Kei Sawada
+
+ 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
+
+ https://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/README.md b/README.md
index cb2a042..7c0bc4c 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,98 @@
-# incubator-openwhisk-runtime-ruby
+<!--
+#
+# 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.
+#
+-->
+
+# Apache OpenWhisk runtimes for Ruby
+[](https://travis-ci.org/apache/incubator-openwhisk-runtime-ruby)
+
+### Give it a try today
+To use as a docker action
+```
+wsk action update myAction my_action.rb --docker openwhisk/action-ruby-v2.5
+```
+This works on any deployment of Apache OpenWhisk
+
+### To use on deployment that contains the rutime as a kind
+To use as a kind action
+```
+wsk action update myAction my_action.rb --kind ruby:2.5
+```
+
+### Local development
+```
+./gradlew core:ruby2.5Action:distDocker
+```
+This will produce the image `whisk/action-ruby-v2.5`
+
+Build and Push image
+```
+docker login
+./gradlew core:ruby2.5Action:distDocker -PdockerImagePrefix=$prefix-user
-PdockerRegistry=docker.io
+```
+
+Deploy OpenWhisk using ansible environment that contains the kind `ruby:2.5`
+Assuming you have OpenWhisk already deploy localy and `OPENWHISK_HOME`
pointing to root directory of OpenWhisk core repository.
+
+Set `ROOTDIR` to the root directory of this repository.
+
+Redeploy OpenWhisk
+```
+cd $OPENWHISK_HOME/ansible
+ANSIBLE_CMD="ansible-playbook -i ${ROOTDIR}/ansible/environments/local"
+$ANSIBLE_CMD setup.yml
+$ANSIBLE_CMD couchdb.yml
+$ANSIBLE_CMD initdb.yml
+$ANSIBLE_CMD wipe.yml
+$ANSIBLE_CMD openwhisk.yml
+```
+
+Or you can use `wskdev` and create a soft link to the target ansible
environment, for example:
+```
+ln -s ${ROOTDIR}/ansible/environments/local
${OPENWHISK_HOME}/ansible/environments/local-ruby
+wskdev fresh -t local-ruby
+```
+
+To use as docker action push to your own dockerhub account
+```
+docker tag whisk/ruby2.5Action $user_prefix/action-ruby-v2.5
+docker push $user_prefix/action-ruby-v2.5
+```
+Then create the action using your the image from dockerhub
+```
+wsk action update myAction my_action.rb --docker $user_prefix/action-ruby-v2.5
+```
+The `$user_prefix` is usually your dockerhub user id.
+
+### Testing
+Install dependencies from the root directory on $OPENWHISK_HOME repository
+```
+./gradlew install
+```
+
+Using gradle to run all tests
+```
+./gradlew :tests:test
+```
+Using gradle to run some tests
+```
+./gradlew :tests:test --tests *ActionContainerTests*
+```
+Using IntelliJ:
+- Import project as gradle project.
+- Make sure working directory is root of the project/repo
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..300a1ce
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,13 @@
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath "cz.alenkacz:gradle-scalafmt:${gradle.scalafmt.version}"
+ }
+}
+
+subprojects {
+ apply plugin: 'scalafmt'
+ scalafmt.configFilePath = gradle.scalafmt.config
+}
diff --git a/core/ruby2.5Action/Dockerfile b/core/ruby2.5Action/Dockerfile
new file mode 100644
index 0000000..4fd4ff0
--- /dev/null
+++ b/core/ruby2.5Action/Dockerfile
@@ -0,0 +1,24 @@
+FROM ruby:2.5
+
+# install dependencies
+RUN gem install \
+ bundler \
+ rubyzip \
+ rack \
+ puma \
+ rake `#optional` \
+ mechanize `#optional` \
+ activesupport `#optional` \
+ jwt `#optional`
+
+# create src directory to store action files
+RUN mkdir -p /action/src
+ADD rackapp /action/rackapp/
+COPY config.ru /action
+
+# run webserver
+RUN cd /action
+EXPOSE 8080
+ENV BUNDLE_GEMFILE /action/src/Gemfile
+ENV RUBYOPT -EUTF-8
+CMD [ "puma", "/action/config.ru", "-b", "tcp://0.0.0.0:8080" ]
diff --git a/core/ruby2.5Action/Gemfile b/core/ruby2.5Action/Gemfile
new file mode 100644
index 0000000..7bd43ed
--- /dev/null
+++ b/core/ruby2.5Action/Gemfile
@@ -0,0 +1,4 @@
+source 'https://rubygems.org'
+
+gem 'rack'
+gem 'rubyzip'
diff --git a/core/ruby2.5Action/build.gradle b/core/ruby2.5Action/build.gradle
new file mode 100644
index 0000000..5b36d4f
--- /dev/null
+++ b/core/ruby2.5Action/build.gradle
@@ -0,0 +1,2 @@
+ext.dockerImageName = 'action-ruby-v2.5'
+apply from: '../../gradle/docker.gradle'
diff --git a/core/ruby2.5Action/config.ru b/core/ruby2.5Action/config.ru
new file mode 100644
index 0000000..93c460d
--- /dev/null
+++ b/core/ruby2.5Action/config.ru
@@ -0,0 +1,25 @@
+#!/usr/bin/ruby
+
+require 'rubygems'
+require 'json'
+require 'zip'
+require 'base64'
+require "#{__dir__}/rackapp/middleware/post_method_validation.rb"
+require "#{__dir__}/rackapp/middleware/sentinel_handler.rb"
+require "#{__dir__}/rackapp/init.rb"
+require "#{__dir__}/rackapp/run.rb"
+
+rackapp = Rack::Builder.app do
+ use HTTPPostMethodValidation
+ use SentinelHandler
+
+ map '/init' do
+ run InitApp.new
+ end
+
+ map "/run" do
+ run RunApp.new
+ end
+end
+
+run rackapp
\ No newline at end of file
diff --git a/core/ruby2.5Action/rackapp/filepath.rb
b/core/ruby2.5Action/rackapp/filepath.rb
new file mode 100644
index 0000000..d660b15
--- /dev/null
+++ b/core/ruby2.5Action/rackapp/filepath.rb
@@ -0,0 +1,16 @@
+#!/usr/bin/ruby
+
+module Filepath
+ ROOT = '/action/'
+
+ CONFIG = ROOT + '.wsk.config'
+ TMP_ZIP = ROOT + 'action.zip'
+ PARAM = ROOT + '.wsk.param'
+ RESULT = ROOT + '.wsk.result'
+ OUT = ROOT + '.wsk.stdout'
+ ERR = ROOT + '.wsk.stderr'
+
+ PROGRAM_DIR = ROOT + 'src/'
+ RACKAPP_DIR = ROOT + 'rackapp/'
+ ENTRYPOINT = PROGRAM_DIR + 'main.rb'
+end
diff --git a/core/ruby2.5Action/rackapp/init.rb
b/core/ruby2.5Action/rackapp/init.rb
new file mode 100644
index 0000000..4b9dcf4
--- /dev/null
+++ b/core/ruby2.5Action/rackapp/init.rb
@@ -0,0 +1,94 @@
+require "#{__dir__}/response/success.rb"
+require "#{__dir__}/response/error.rb"
+require "#{__dir__}/filepath.rb"
+
+class InitApp
+ include Filepath
+
+ def call(env)
+ # Make sure that this action is not initialised more than once
+ if File.exist? CONFIG then
+ puts "Error: Cannot initialize the action more than once."
+ return ErrorResponse.new 'Cannot initialize the action more than once.',
403
+ end
+
+ # Expect JSON data input
+ body = Rack::Request.new(env).body.read
+ data = JSON.parse(body)['value'] || {}
+
+ # Is the input data empty?
+ if data == {} then
+ return ErrorResponse.new 'Missing main/no code to execute.', 500
+ end
+
+ name = data['name'] || '' # action name
+ main = data['main'] || '' # function to call
+ code = data['code'] || '' # source code to run
+ binary = data['binary'] || false # code is binary?
+
+ # Are name/main/code variables instance of String?
+ if ![name, main, code].map{|e| e.is_a? String }.inject{|a,b| a && b } then
+ return ErrorResponse.new 'Invalid Parameters: failed to handle the
request', 500
+ end
+
+ env = {'BUNDLE_GEMFILE' => PROGRAM_DIR + 'Gemfile'}
+ if binary then
+ File.write TMP_ZIP, Base64.decode64(code)
+ if !unzip(TMP_ZIP, PROGRAM_DIR) then
+ return ErrorResponse.new 'Invalid Binary: failed to open zip file.
Please make sure you have finishied $bundle package successfully.', 500
+ end
+ # Try to resolve dependencies
+ if File.exist?(PROGRAM_DIR + 'Gemfile') then
+ if !File.directory?(PROGRAM_DIR + 'vendor/cache') then
+ return ErrorResponse.new 'Invalid Binary: vendor/cache folder is not
found. Please make sure you have used valid zip binary.', 200
+ end
+ if !system(env, "bundle install --local 2> #{ERR} 1> #{OUT}") then
+ return ErrorResponse.new "Invalid Binary: failed to resolve
dependencies / #{File.read(OUT)} / #{File.read(ERR)}", 500
+ end
+ else
+ File.write env['BUNDLE_GEMFILE'], '' # For better performance, better
to remove Gemfile and remove "bundle exec" redundant call when binary=false. To
be improved in future.
+ end
+ if !File.exist?(ENTRYPOINT) then
+ return ErrorResponse.new 'Invalid Ruby Code: zipped actions must
contain main.rb at the root.', 500
+ end
+ else
+ # Save the code for future use
+ File.write ENTRYPOINT, code
+ File.write env['BUNDLE_GEMFILE'], '' # For better performance, better
to remove Gemfile and remove "bundle exec" redundant call when binary=false. To
be improved in future.
+ end
+
+ # Check if the ENTRYPOINT code is valid or not
+ if !valid_code?(ENTRYPOINT) then
+ return ErrorResponse.new 'Invalid Ruby Code: failed to parse the input
code', 500
+ end
+
+ # Check if the method exists as expected
+ if !system(env, "bundle exec ruby -r #{ENTRYPOINT} -e \"method(:#{main}) ?
true : raise(Exception.new('Error'))\" 2> #{ERR} 1> #{OUT}") then
+ return ErrorResponse.new "Invalid Ruby Code: method checking failed /
#{File.read(OUT)} / #{File.read(ERR)}", 500
+ end
+
+ # Save config parameters to filesystem so that later /run can use this
+ File.write CONFIG, {:main=>main, :name=>name}.to_json
+
+ # Proceed with the next step
+ SuccessResponse.new({'OK'=>true})
+ end
+
+ private
+ def valid_code?(path)
+ system("ruby -e 'RubyVM::InstructionSequence.compile_file(\"#{path}\")'
2> #{ERR} 1> #{OUT}")
+ rescue
+ false
+ end
+
+ def unzip(zipfile_path, destination_folder)
+ Zip::File.open(zipfile_path) do |zip|
+ zip.each do |file|
+ zip.extract(file, destination_folder + file.name)
+ end
+ end
+ true
+ rescue
+ false
+ end
+end
diff --git a/core/ruby2.5Action/rackapp/middleware/base.rb
b/core/ruby2.5Action/rackapp/middleware/base.rb
new file mode 100644
index 0000000..d362d66
--- /dev/null
+++ b/core/ruby2.5Action/rackapp/middleware/base.rb
@@ -0,0 +1,5 @@
+class MiddlewareBase
+ def initialize(app)
+ @app = app
+ end
+end
\ No newline at end of file
diff --git a/core/ruby2.5Action/rackapp/middleware/post_method_validation.rb
b/core/ruby2.5Action/rackapp/middleware/post_method_validation.rb
new file mode 100644
index 0000000..8de8dba
--- /dev/null
+++ b/core/ruby2.5Action/rackapp/middleware/post_method_validation.rb
@@ -0,0 +1,11 @@
+require "#{__dir__}/base.rb"
+
+class HTTPPostMethodValidation < MiddlewareBase
+ def call(env)
+ if Rack::Request.new(env).request_method == 'POST' then
+ @app.call(env)
+ else
+ Rack::Response.new 'Something went wrong with the request', 500
+ end
+ end
+end
\ No newline at end of file
diff --git a/core/ruby2.5Action/rackapp/middleware/sentinel_handler.rb
b/core/ruby2.5Action/rackapp/middleware/sentinel_handler.rb
new file mode 100644
index 0000000..2161bde
--- /dev/null
+++ b/core/ruby2.5Action/rackapp/middleware/sentinel_handler.rb
@@ -0,0 +1,13 @@
+require "#{__dir__}/base.rb"
+
+class SentinelHandler < MiddlewareBase
+ def call(env)
+ response = @app.call(env)
+ if !(env['REQUEST_PATH'] == '/init' &&
[200,403].include?(response.status)) then
+ puts response.body if response.status!=200
+ puts "XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX"
+ warn "XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX"
+ end
+ response
+ end
+end
\ No newline at end of file
diff --git a/core/ruby2.5Action/rackapp/response/error.rb
b/core/ruby2.5Action/rackapp/response/error.rb
new file mode 100644
index 0000000..57d9584
--- /dev/null
+++ b/core/ruby2.5Action/rackapp/response/error.rb
@@ -0,0 +1,5 @@
+class ErrorResponse < Rack::Response
+ def initialize(body = [], status = 500, header = {})
+ super({:error=>body}.to_json, status, header.merge({'Content-Type' =>
'application/json'}))
+ end
+end
\ No newline at end of file
diff --git a/core/ruby2.5Action/rackapp/response/success.rb
b/core/ruby2.5Action/rackapp/response/success.rb
new file mode 100644
index 0000000..9d3efb2
--- /dev/null
+++ b/core/ruby2.5Action/rackapp/response/success.rb
@@ -0,0 +1,5 @@
+class SuccessResponse < Rack::Response
+ def initialize(body = [], status = 200, header = {})
+ super body.to_json, status, header.merge({'Content-Type' =>
'application/json'})
+ end
+end
\ No newline at end of file
diff --git a/core/ruby2.5Action/rackapp/run.rb
b/core/ruby2.5Action/rackapp/run.rb
new file mode 100644
index 0000000..c4699d2
--- /dev/null
+++ b/core/ruby2.5Action/rackapp/run.rb
@@ -0,0 +1,48 @@
+require "#{__dir__}/response/success.rb"
+require "#{__dir__}/response/error.rb"
+require "#{__dir__}/filepath.rb"
+
+class RunApp
+ include Filepath
+
+ def call(env)
+ if !File.exist? ENTRYPOINT then
+ return ErrorResponse.new 'Invalid Action: no action file found', 500
+ end
+
+ # Set environment variables
+ body = Rack::Request.new(env).body.read
+ data = JSON.parse(body) || {}
+ env = {'BUNDLE_GEMFILE' => PROGRAM_DIR + 'Gemfile'}
+ ['api_key', 'namespace', 'action_name', 'activation_id',
'deadline'].each{|e|
+ env["__OW_#{e.upcase}"] = data[e] if data[e] && data[e].is_a?(String)
+ }
+
+ # Save parameter values to file in order to let runner.rb read this later
+ File.write PARAM, data['value'].to_json
+
+ # Execute the action with given parameters
+ if system(env, "bundle exec ruby -r #{ENTRYPOINT} #{RACKAPP_DIR}runner.rb
| tee #{OUT}") then
+ if File.exist? RESULT then
+ result = File.read(RESULT)
+ if valid_json?(result) then
+ SuccessResponse.new(JSON.parse(result))
+ else
+ warn "Result must be an array but has type '#{result.class.to_s}':
#{result}"
+ ErrorResponse.new 'The action did not return a dictionary.', 502
+ end
+ else
+ ErrorResponse.new 'Invalid Action: An error occurred running the
action', 502
+ end
+ else
+ ErrorResponse.new "Invalid Action: the execution was not successful. /
#{File.read(OUT)}}", 502
+ end
+ end
+
+ private
+ def valid_json?(json)
+ JSON.parse(json).class == Hash
+ rescue
+ false
+ end
+end
\ No newline at end of file
diff --git a/core/ruby2.5Action/rackapp/runner.rb
b/core/ruby2.5Action/rackapp/runner.rb
new file mode 100644
index 0000000..5f607da
--- /dev/null
+++ b/core/ruby2.5Action/rackapp/runner.rb
@@ -0,0 +1,9 @@
+#!/usr/bin/ruby
+
+require 'json'
+require "#{__dir__}/filepath.rb"
+
+include Filepath
+config = JSON.parse(File.read(CONFIG))
+param = JSON.parse(File.read(PARAM))
+File.write RESULT, method(config["main"]).call(param).to_json
diff --git a/gradle/README.md b/gradle/README.md
new file mode 100644
index 0000000..14842f1
--- /dev/null
+++ b/gradle/README.md
@@ -0,0 +1,64 @@
+<!--
+#
+# 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..d7cc33f
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,8 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
contributor
+# license agreements; and to You under the Apache License, Version 2.0.
+
+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 100755
index 0000000..f955316
--- /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/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..92db886
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,19 @@
+include 'tests'
+
+include 'core:ruby2.5Action'
+
+rootProject.name = 'runtime-ruby'
+
+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/tests/.pydevproject b/tests/.pydevproject
new file mode 100644
index 0000000..40e9f40
--- /dev/null
+++ b/tests/.pydevproject
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?eclipse-pydev version="1.0"?><pydev_project>
+<pydev_property
name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python
2.7</pydev_property>
+</pydev_project>
diff --git a/tests/build.gradle b/tests/build.gradle
new file mode 100644
index 0000000..1ae5a4b
--- /dev/null
+++ b/tests/build.gradle
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'scala'
+apply plugin: 'eclipse'
+compileTestScala.options.encoding = 'UTF-8'
+
+repositories {
+ mavenCentral()
+ mavenLocal()
+}
+
+tasks.withType(Test) {
+ testLogging {
+ events "passed", "skipped", "failed"
+ showStandardStreams = true
+ exceptionFormat = 'full'
+ }
+ outputs.upToDateWhen { false } // force tests to run every time
+}
+
+dependencies {
+ compile "org.scala-lang:scala-library:${gradle.scala.version}"
+ compile
"org.apache.openwhisk:openwhisk-tests:${gradle.openwhisk.version}:tests"
+ compile
"org.apache.openwhisk:openwhisk-tests:${gradle.openwhisk.version}:test-sources"
+}
+
+tasks.withType(ScalaCompile) {
+ scalaCompileOptions.additionalParameters = gradle.scala.compileFlags
+}
diff --git a/tests/src/test/resources/application.conf
b/tests/src/test/resources/application.conf
new file mode 100644
index 0000000..759f063
--- /dev/null
+++ b/tests/src/test/resources/application.conf
@@ -0,0 +1,8 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
contributor
+# license agreements; and to You under the Apache License, Version 2.0.
+
+whisk.spi {
+ SimpleSpi = whisk.spi.SimpleSpiImpl
+ MissingSpi = whisk.spi.MissingImpl
+ MissingModule = missing.module
+}
diff --git
a/tests/src/test/scala/actionContainers/Ruby25ActionContainerTests.scala
b/tests/src/test/scala/actionContainers/Ruby25ActionContainerTests.scala
new file mode 100644
index 0000000..1090c14
--- /dev/null
+++ b/tests/src/test/scala/actionContainers/Ruby25ActionContainerTests.scala
@@ -0,0 +1,411 @@
+/*
+ * 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
+import common.WskActorSystem
+import actionContainers.{ActionContainer, BasicActionRunnerTests}
+import actionContainers.ActionContainer.withContainer
+import actionContainers.ResourceHelpers.ZipBuilder
+import spray.json._
+
+@RunWith(classOf[JUnitRunner])
+class Ruby25ActionContainerTests extends BasicActionRunnerTests with
WskActorSystem {
+ // note: "out" will not be empty as the Webrick outputs a message during the
boot and after the boot
+ val enforceEmptyOutputStream = false
+
+ lazy val ruby25ContainerImageName = "action-ruby-v2.5"
+
+ override def withActionContainer(env: Map[String, String] = Map.empty)(code:
ActionContainer => Unit) = {
+ withContainer(ruby25ContainerImageName, env)(code)
+ }
+
+ def withRuby25Container(code: ActionContainer => Unit) =
withActionContainer()(code)
+
+ behavior of ruby25ContainerImageName
+
+ override val testNoSourceOrExec = TestConfig("")
+
+ override val testEcho = {
+ TestConfig("""
+ |def main(args)
+ | puts 'hello stdout'
+ | warn 'hello stderr'
+ | args
+ |end
+ """.stripMargin)
+ }
+
+ override val testNotReturningJson = {
+ TestConfig(
+ """
+ |def main(args)
+ | "not a json object"
+ |end
+ """.stripMargin,
+ enforceEmptyOutputStream = enforceEmptyOutputStream,
+ enforceEmptyErrorStream = false)
+ }
+
+ override val testInitCannotBeCalledMoreThanOnce = {
+ TestConfig(
+ """
+ |def main(args)
+ | args
+ |end
+ """.stripMargin,
+ enforceEmptyOutputStream = enforceEmptyOutputStream)
+ }
+
+ override val testEntryPointOtherThanMain = {
+ TestConfig(
+ """
+ |def niam(args)
+ | args
+ |end
+ """.stripMargin,
+ main = "niam",
+ enforceEmptyOutputStream = enforceEmptyOutputStream)
+ }
+
+ override val testUnicode = {
+ TestConfig("""
+ |def main(args)
+ | str = args['delimiter'] + " ☃ " + args['delimiter']
+ | print str + "\n"
+ | {"winter" => str}
+ |end
+ """.stripMargin.trim)
+ }
+
+ override val testEnv = {
+ TestConfig(
+ """
+ |def main(args)
+ | {
+ | "env" => ENV,
+ | "api_host" => ENV['__OW_API_HOST'],
+ | "api_key" => ENV['__OW_API_KEY'],
+ | "namespace" => ENV['__OW_NAMESPACE'],
+ | "action_name" => ENV['__OW_ACTION_NAME'],
+ | "activation_id" => ENV['__OW_ACTIVATION_ID'],
+ | "deadline" => ENV['__OW_DEADLINE']
+ | }
+ |end
+ """.stripMargin.trim,
+ enforceEmptyOutputStream = enforceEmptyOutputStream)
+ }
+
+ override val testLargeInput = {
+ TestConfig("""
+ |def main(args)
+ | args
+ |end
+ """.stripMargin)
+ }
+
+ it should "fail to initialize with bad code" in {
+ val (out, err) = withRuby25Container { c =>
+ val code = """
+ | 10 PRINT "Hello world!"
+ | 20 GOTO 10
+ """.stripMargin
+
+ val (initCode, error) = c.init(initPayload(code))
+ initCode should not be (200)
+ error shouldBe a[Some[_]]
+ error.get shouldBe a[JsObject]
+ error.get.fields("error").toString should include("failed to parse the
input code")
+ }
+
+ // Somewhere, the logs should mention an error occurred.
+ checkStreams(out, err, {
+ case (o, e) =>
+ (o + e).toLowerCase should include("invalid")
+ (o + e).toLowerCase should include("parse")
+ })
+ }
+
+ it should "return some error on action error" in {
+ val (out, err) = withRuby25Container { c =>
+ val code = """
+ | def main(args)
+ | raise Exception.new("nooooo")
+ | end
+ """.stripMargin
+
+ val (initCode, _) = c.init(initPayload(code))
+ initCode should be(200)
+
+ val (runCode, runRes) = c.run(runPayload(JsObject()))
+ runCode should not be (200)
+
+ runRes shouldBe defined
+ runRes.get.fields.get("error") shouldBe defined
+ // runRes.get.fields("error").toString.toLowerCase should include("nooooo")
+ }
+
+ // Somewhere, the logs should be the error text
+ checkStreams(out, err, {
+ case (o, e) =>
+ (o + e).toLowerCase should include("nooooo")
+ })
+
+ }
+
+ it should "support application errors" in {
+ withRuby25Container { c =>
+ val code = """
+ | def main(args)
+ | { "error" => "sorry" }
+ | end
+ """.stripMargin;
+
+ val (initCode, error) = c.init(initPayload(code))
+ initCode should be(200)
+
+ val (runCode, runRes) = c.run(runPayload(JsObject()))
+ runCode should be(200) // action writer returning an error is OK
+
+ runRes shouldBe defined
+ runRes.get.fields.get("error") shouldBe defined
+ runRes.get.fields("error").toString.toLowerCase should include("sorry")
+ }
+ }
+
+ it should "fail gracefully when an action has a TypeError exception" in {
+ val (out, err) = withRuby25Container { c =>
+ val code = """
+ | def main(args)
+ | eval "class ENV\nend"
+ | { "hello" => "world" }
+ | end
+ """.stripMargin;
+
+ val (initCode, _) = c.init(initPayload(code))
+ initCode should be(200)
+
+ val (runCode, runRes) = c.run(runPayload(JsObject()))
+ runCode should be(502)
+
+ runRes shouldBe defined
+ runRes.get.fields.get("error") shouldBe defined
+ runRes.get.fields("error").toString should include("An error occurred
running the action")
+ }
+
+ // Somewhere, the logs should be the error text
+ checkStreams(out, err, {
+ case (o, e) =>
+ (o + e).toLowerCase should include("typeerror")
+ })
+ }
+
+ it should "support the documentation examples (1)" in {
+ val (out, err) = withRuby25Container { c =>
+ val code = """
+ | def main(params)
+ | if (params['payload'] == 0) then
+ | return {}
+ | elsif params['payload'] == 1 then
+ | return {'payload' => 'Hello, World!'} # indicates normal
completion
+ | elsif params['payload'] == 2 then
+ | return {'error' => 'payload must be 0 or 1'} #
indicates abnormal completion
+ | end
+ | end
+ """.stripMargin
+
+ c.init(initPayload(code))._1 should be(200)
+
+ val (c1, r1) = c.run(runPayload(JsObject("payload" -> JsNumber(0))))
+ val (c2, r2) = c.run(runPayload(JsObject("payload" -> JsNumber(1))))
+ val (c3, r3) = c.run(runPayload(JsObject("payload" -> JsNumber(2))))
+
+ c1 should be(200)
+ r1 should be(Some(JsObject()))
+
+ c2 should be(200)
+ r2 should be(Some(JsObject("payload" -> JsString("Hello, World!"))))
+
+ c3 should be(200) // application error, not container or system
+ r3.get.fields.get("error") shouldBe Some(JsString("payload must be 0 or
1"))
+ }
+ }
+
+ it should "have mechanize and activesupport gems available" in {
+ // GIVEN that it should "error when requiring a non-existent package" (see
test above for this)
+ val (out, err) = withRuby25Container { c =>
+ val code = """
+ | require 'mechanize'
+ | require 'active_support'
+ | def main(args)
+ | Mechanize.class
+ | ActiveSupport.class
+ | {}
+ | end
+ """.stripMargin
+
+ val (initCode, _) = c.init(initPayload(code))
+
+ initCode should be(200)
+
+ // WHEN I run an action that calls a Guzzle & a Uuid method
+ val (runCode, out) = c.run(runPayload(JsObject()))
+
+ // THEN it should pass only when these packages are available
+ runCode should be(200)
+ }
+ }
+
+ it should "support large-ish actions" in {
+ val thought = " I took the one less traveled by, and that has made all the
difference."
+ val assignment = " x = \"" + thought + "\";\n"
+
+ val code = """
+ | def main(args)
+ | x = "hello"
+ """.stripMargin + (assignment * 7000) + """
+ | x = "world"
+ | { "message" => x }
+ | end
+ """.stripMargin
+
+ // Lest someone should make it too easy.
+ code.length should be >= 500000
+
+ val (out, err) = withRuby25Container { c =>
+ c.init(initPayload(code))._1 should be(200)
+
+ val (runCode, runRes) = c.run(runPayload(JsObject()))
+
+ runCode should be(200)
+ runRes.get.fields.get("message") shouldBe defined
+ runRes.get.fields.get("message") shouldBe Some(JsString("world"))
+ }
+ }
+
+ val exampleOutputDotRuby: String = """
+ | def output(data)
+ | {'result' => data}
+ | end
+ """.stripMargin
+
+ it should "support zip-encoded packages" in {
+ val srcs = Seq(
+ Seq("output.rb") -> exampleOutputDotRuby,
+ Seq("main.rb") -> """
+ | require __dir__ + '/output.rb'
+ | def main(args)
+ | name = args['name'] || 'stranger'
+ | output(name)
+ | end
+ """.stripMargin)
+
+ val code = ZipBuilder.mkBase64Zip(srcs)
+
+ val (out, err) = withRuby25Container { c =>
+ c.init(initPayload(code))._1 should be(200)
+
+ val (runCode, runRes) = c.run(runPayload(JsObject()))
+
+ runCode should be(200)
+ runRes.get.fields.get("result") shouldBe defined
+ runRes.get.fields.get("result") shouldBe Some(JsString("stranger"))
+ }
+ }
+
+ it should "fail gracefully on invalid zip files" in {
+ // Some text-file encoded to base64.
+ val code = "Q2VjaSBuJ2VzdCBwYXMgdW4gemlwLgo="
+
+ val (out, err) = withRuby25Container { c =>
+ val (initCode, error) = c.init(initPayload(code))
+ initCode should not be (200)
+ error shouldBe a[Some[_]]
+ error.get shouldBe a[JsObject]
+ error.get.fields("error").toString should include("failed to open zip
file")
+ }
+
+ // Somewhere, the logs should mention the failure
+ checkStreams(out, err, {
+ case (o, e) =>
+ (o + e).toLowerCase should include("error")
+ (o + e).toLowerCase should include("failed to open zip file")
+ })
+ }
+
+ it should "fail gracefully on valid zip files that are not actions" in {
+ val srcs = Seq(Seq("hello") -> """
+ | Hello world!
+ """.stripMargin)
+
+ val code = ZipBuilder.mkBase64Zip(srcs)
+
+ val (out, err) = withRuby25Container { c =>
+ c.init(initPayload(code))._1 should not be (200)
+ }
+
+ checkStreams(out, err, {
+ case (o, e) =>
+ (o + e).toLowerCase should include("error")
+ (o + e).toLowerCase should include("zipped actions must contain
main.rb at the root.")
+ })
+ }
+
+ it should "fail gracefully on valid zip files with invalid code in main.rb"
in {
+ val (out, err) = withRuby25Container { c =>
+ val srcs = Seq(Seq("main.rb") -> """
+ | 10 PRINT "Hello world!"
+ | 20 GOTO 10
+ """.stripMargin)
+
+ val code = ZipBuilder.mkBase64Zip(srcs)
+
+ val (initCode, error) = c.init(initPayload(code))
+ initCode should not be (200)
+ error shouldBe a[Some[_]]
+ error.get shouldBe a[JsObject]
+ error.get.fields("error").toString should include("failed to parse the
input code")
+ }
+
+ // Somewhere, the logs should mention an error occurred.
+ checkStreams(out, err, {
+ case (o, e) =>
+ (o + e).toLowerCase should include("invalid")
+ (o + e).toLowerCase should include("parse")
+ })
+ }
+
+ it should "support zipped actions using non-default entry point" in {
+ val srcs = Seq(Seq("main.rb") -> """
+ | def niam(args)
+ | { :result => "it works" }
+ | end
+ """.stripMargin)
+
+ val code = ZipBuilder.mkBase64Zip(srcs)
+
+ withRuby25Container { c =>
+ c.init(initPayload(code, main = "niam"))._1 should be(200)
+
+ val (runCode, runRes) = c.run(runPayload(JsObject()))
+ runRes.get.fields.get("result") shouldBe Some(JsString("it works"))
+ }
+ }
+
+}
diff --git a/tools/travis/build.sh b/tools/travis/build.sh
new file mode 100755
index 0000000..e3aa931
--- /dev/null
+++ b/tools/travis/build.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+#
+# 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.
+#
+
+set -ex
+
+# Build script for Travis-CI.
+
+SCRIPTDIR=$(cd $(dirname "$0") && pwd)
+ROOTDIR="$SCRIPTDIR/../.."
+WHISKDIR="$ROOTDIR/../openwhisk"
+UTILDIR="$ROOTDIR/../incubator-openwhisk-utilities"
+
+export OPENWHISK_HOME=$WHISKDIR
+
+# run scancode using the ASF Release configuration
+cd $UTILDIR
+scancode/scanCode.py --config scancode/ASF-Release.cfg $ROOTDIR
+
+# Build OpenWhisk deps before we run tests
+cd $WHISKDIR
+TERM=dumb ./gradlew install
+# Mock file (works around bug upstream)
+echo "openwhisk.home=$WHISKDIR" > whisk.properties
+echo "vcap.services.file=" >> whisk.properties
+
+# Build runtime
+cd $ROOTDIR
+TERM=dumb ./gradlew distDocker
diff --git a/tools/travis/publish.sh b/tools/travis/publish.sh
new file mode 100644
index 0000000..78398f9
--- /dev/null
+++ b/tools/travis/publish.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+#
+# 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.
+#
+
+set -eux
+
+# Build script for Travis-CI.
+
+SCRIPTDIR=$(cd $(dirname "$0") && pwd)
+ROOTDIR="$SCRIPTDIR/../.."
+WHISKDIR="$ROOTDIR/../openwhisk"
+
+export OPENWHISK_HOME=$WHISKDIR
+
+IMAGE_PREFIX=$1
+RUNTIME=$2
+IMAGE_TAG=$3
+
+if [[ ! -z ${DOCKER_USER} ]] && [[ ! -z ${DOCKER_PASSWORD} ]]; then
+docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}"
+fi
+
+if [[ ! -z ${RUNTIME} ]]; then
+TERM=dumb ./gradlew \
+:core:${RUNTIME}:distDocker \
+-PdockerRegistry=docker.io \
+-PdockerImagePrefix=${IMAGE_PREFIX} \
+-PdockerImageTag=${IMAGE_TAG}
+fi
diff --git a/tools/travis/setup.sh b/tools/travis/setup.sh
new file mode 100755
index 0000000..3424cdb
--- /dev/null
+++ b/tools/travis/setup.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+#
+# 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.
+#
+
+set -e
+
+# Build script for Travis-CI.
+
+SCRIPTDIR=$(cd $(dirname "$0") && pwd)
+ROOTDIR="$SCRIPTDIR/../.."
+HOMEDIR="$SCRIPTDIR/../../../"
+
+# clone OpenWhisk utilities repo. in order to run scanCode
+cd $HOMEDIR
+git clone https://github.com/apache/incubator-openwhisk-utilities.git
+
+# clone main openwhisk repo. for testing purposes
+git clone --depth=1 https://github.com/apache/incubator-openwhisk.git openwhisk
+cd openwhisk
+./tools/travis/setup.sh
diff --git a/tools/travis/test.sh b/tools/travis/test.sh
new file mode 100755
index 0000000..a5b249d
--- /dev/null
+++ b/tools/travis/test.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# 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.
+#
+
+set -ex
+
+# Build script for Travis-CI.
+
+SCRIPTDIR=$(cd $(dirname "$0") && pwd)
+ROOTDIR="$SCRIPTDIR/../.."
+WHISKDIR="$ROOTDIR/../openwhisk"
+
+export OPENWHISK_HOME=$WHISKDIR
+cd ${ROOTDIR}
+TERM=dumb ./gradlew :tests:checkScalafmtAll
+TERM=dumb ./gradlew :tests:test
----------------------------------------------------------------
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:
[email protected]
With regards,
Apache Git Services