├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── README.md └── docker.gradle ├── NOTICE.txt ├── core ├── ruby2.5Action │ ├── Gemfile │ ├── build.gradle │ ├── rackapp │ │ ├── middleware │ │ │ ├── base.rb │ │ │ ├── post_method_validation.rb │ │ │ └── sentinel_handler.rb │ │ ├── response │ │ │ ├── success.rb │ │ │ └── error.rb │ │ ├── runner.rb │ │ ├── filepath.rb │ │ ├── run.rb │ │ └── init.rb │ ├── config.ru │ ├── CHANGELOG.md │ └── Dockerfile └── ruby2.6ActionLoop │ ├── build.gradle │ ├── CHANGELOG.txt │ ├── lib │ └── launcher.rb │ ├── Dockerfile │ └── bin │ └── compile ├── .scalafmt.conf ├── tests ├── src │ └── test │ │ ├── resources │ │ ├── application.conf │ │ ├── without_dir_entries │ │ │ ├── main.rb │ │ │ └── bundle │ │ │ │ └── bundler │ │ │ │ └── setup.rb │ │ └── build.sh │ │ └── scala │ │ └── actionContainers │ │ ├── Ruby26ActionLoopContainerTests.scala │ │ └── Ruby25ActionContainerTests.scala └── build.gradle ├── .gitignore ├── settings.gradle ├── .asf.yaml ├── gradlew.bat ├── CONTRIBUTING.md ├── README.md ├── .github └── workflows │ └── ci.yaml ├── gradlew └── LICENSE.txt /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apache/openwhisk-runtime-ruby/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Apache OpenWhisk Runtime Ruby 2 | Copyright 2016-2021 The Apache Software Foundation 3 | 4 | This product includes software developed at 5 | The Apache Software Foundation (http://www.apache.org/). 6 | -------------------------------------------------------------------------------- /core/ruby2.5Action/Gemfile: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | source 'https://rubygems.org' 19 | 20 | gem 'rack' ~> 2.0.0 21 | gem 'rubyzip' 22 | -------------------------------------------------------------------------------- /core/ruby2.5Action/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | ext.dockerImageName = 'action-ruby-v2.5' 19 | apply from: '../../gradle/docker.gradle' 20 | -------------------------------------------------------------------------------- /core/ruby2.5Action/rackapp/middleware/base.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | class MiddlewareBase 19 | def initialize(app) 20 | @app = app 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /core/ruby2.6ActionLoop/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | ext.dockerImageName = 'actionloop-ruby-v2.6' 19 | apply from: '../../gradle/docker.gradle' 20 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | style = intellij 19 | danglingParentheses = false 20 | maxColumn = 120 21 | docstrings = JavaDoc 22 | rewrite.rules = [SortImports] 23 | project.git = true 24 | -------------------------------------------------------------------------------- /tests/src/test/resources/application.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | whisk.spi { 19 | SimpleSpi = whisk.spi.SimpleSpiImpl 20 | MissingSpi = whisk.spi.MissingImpl 21 | MissingModule = missing.module 22 | } 23 | -------------------------------------------------------------------------------- /tests/src/test/resources/without_dir_entries/main.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | def main(params = {}) 19 | name = params["name"] || "stranger" 20 | greeting = "Hello #{name}!" 21 | { greeting: greeting } 22 | end 23 | -------------------------------------------------------------------------------- /core/ruby2.5Action/rackapp/response/success.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | class SuccessResponse < Rack::Response 19 | def initialize(body = [], status = 200, header = {}) 20 | super body.to_json, status, header.merge({'Content-Type' => 'application/json'}) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | distributionBase=GRADLE_USER_HOME 18 | distributionPath=wrapper/dists 19 | zipStoreBase=GRADLE_USER_HOME 20 | zipStorePath=wrapper/dists 21 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-bin.zip 22 | -------------------------------------------------------------------------------- /core/ruby2.5Action/rackapp/response/error.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | class ErrorResponse < Rack::Response 19 | def initialize(body = [], status = 500, header = {}) 20 | super({:error=>body}.to_json, status, header.merge({'Content-Type' => 'application/json'})) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Whisk 2 | nginx.conf 3 | whisk.properties 4 | default.props 5 | 6 | .ant-targets-build.xml 7 | /results/ 8 | /logs/ 9 | /config/custom-config.xml 10 | results 11 | *.retry 12 | 13 | # Environments 14 | /ansible/environments/* 15 | !/ansible/environments/distributed 16 | !/ansible/environments/docker-machine 17 | !/ansible/environments/local 18 | !/ansible/environments/mac 19 | 20 | # Eclipse 21 | bin/ 22 | **/.project 23 | .settings/ 24 | .classpath 25 | .cache-main 26 | .cache-tests 27 | 28 | # Linux 29 | *~ 30 | 31 | # Mac 32 | .DS_Store 33 | 34 | # Gradle 35 | .gradle 36 | build/ 37 | !/tools/build/ 38 | 39 | # Python 40 | .ipynb_checkpoints/ 41 | *.pyc 42 | 43 | # NodeJS 44 | node_modules 45 | 46 | # Vagrant 47 | .vagrant* 48 | 49 | # IntelliJ 50 | .idea 51 | *.class 52 | *.iml 53 | out/ 54 | 55 | # Ansible 56 | ansible/environments/docker-machine/hosts 57 | ansible/db_local.ini* 58 | ansible/tmp/* 59 | ansible/roles/nginx/files/openwhisk-client* 60 | ansible/roles/nginx/files/*.csr 61 | ansible/roles/nginx/files/*cert.pem 62 | 63 | # Generated by tests:buildArtifacts 64 | .built 65 | *.zip 66 | -------------------------------------------------------------------------------- /tests/src/test/resources/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Licensed to the Apache Software Foundation (ASF) under one or more 4 | # contributor license agreements. See the NOTICE file distributed with 5 | # this work for additional information regarding copyright ownership. 6 | # The ASF licenses this file to You under the Apache License, Version 2.0 7 | # (the "License"); you may not use this file except in compliance with 8 | # the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | set -e 20 | 21 | if [ -f ".built" ]; then 22 | echo "Test zip artifacts already built, skipping" 23 | exit 0 24 | fi 25 | 26 | (cd without_dir_entries && zip ../without_dir_entries.zip -r -D .) 27 | 28 | touch .built 29 | -------------------------------------------------------------------------------- /core/ruby2.5Action/rackapp/runner.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # 4 | # Licensed to the Apache Software Foundation (ASF) under one or more 5 | # contributor license agreements. See the NOTICE file distributed with 6 | # this work for additional information regarding copyright ownership. 7 | # The ASF licenses this file to You under the Apache License, Version 2.0 8 | # (the "License"); you may not use this file except in compliance with 9 | # the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'json' 21 | require "#{__dir__}/filepath.rb" 22 | 23 | include Filepath 24 | config = JSON.parse(File.read(CONFIG)) 25 | param = JSON.parse(File.read(PARAM)) 26 | File.write RESULT, method(config["main"]).call(param).to_json 27 | -------------------------------------------------------------------------------- /core/ruby2.5Action/rackapp/middleware/post_method_validation.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | require "#{__dir__}/base.rb" 19 | 20 | class HTTPPostMethodValidation < MiddlewareBase 21 | def call(env) 22 | if Rack::Request.new(env).request_method == 'POST' then 23 | @app.call(env) 24 | else 25 | Rack::Response.new 'Something went wrong with the request', 500 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /tests/src/test/resources/without_dir_entries/bundle/bundler/setup.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | require 'rbconfig' 19 | # ruby 1.8.7 doesn't define RUBY_ENGINE 20 | ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby' 21 | ruby_version = RbConfig::CONFIG["ruby_version"] 22 | path = File.expand_path('..', __FILE__) 23 | $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/betterlorem-0.1.2/lib" 24 | $:.unshift "#{path}/" 25 | -------------------------------------------------------------------------------- /core/ruby2.5Action/rackapp/filepath.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # 4 | # Licensed to the Apache Software Foundation (ASF) under one or more 5 | # contributor license agreements. See the NOTICE file distributed with 6 | # this work for additional information regarding copyright ownership. 7 | # The ASF licenses this file to You under the Apache License, Version 2.0 8 | # (the "License"); you may not use this file except in compliance with 9 | # the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | module Filepath 21 | ROOT = '/action/' 22 | 23 | CONFIG = ROOT + '.wsk.config' 24 | TMP_ZIP = ROOT + 'action.zip' 25 | PARAM = ROOT + '.wsk.param' 26 | RESULT = ROOT + '.wsk.result' 27 | OUT = ROOT + '.wsk.stdout' 28 | ERR = ROOT + '.wsk.stderr' 29 | 30 | PROGRAM_DIR = ROOT + 'src/' 31 | RACKAPP_DIR = ROOT + 'rackapp/' 32 | ENTRYPOINT = PROGRAM_DIR + 'main.rb' 33 | end 34 | -------------------------------------------------------------------------------- /core/ruby2.5Action/rackapp/middleware/sentinel_handler.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | require "#{__dir__}/base.rb" 19 | 20 | class SentinelHandler < MiddlewareBase 21 | def call(env) 22 | response = @app.call(env) 23 | if !(env['REQUEST_PATH'] == '/init' && [200,403].include?(response.status)) then 24 | puts response.body if response.status!=200 25 | puts "XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX" 26 | warn "XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX" 27 | STDOUT.flush 28 | STDERR.flush 29 | end 30 | response 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /core/ruby2.6ActionLoop/CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Ruby 2.6 OpenWhisk Runtime Container 21 | 22 | ## Next Release 23 | - Golang Action loop updatetd to golang 1.20 24 | 25 | ## 1.17.0 26 | - Build actionloop from 1.16@1.18.0 (#63) 27 | - Resolve akka versions explicitly. (#62, #61) 28 | 29 | ## 1.16.0 30 | - Use 1.17.0 release of openwhisk-runtime-go (#52) 31 | - Install latest security fixes with every image build. 32 | 33 | ## 1.15.0 34 | - Build proxy using golang 1.15 and openwhisk-runtime-go 1.16.0 (#48) 35 | 36 | ## 1.14.0 37 | - Initial Release 38 | -------------------------------------------------------------------------------- /core/ruby2.5Action/config.ru: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | # 4 | # Licensed to the Apache Software Foundation (ASF) under one or more 5 | # contributor license agreements. See the NOTICE file distributed with 6 | # this work for additional information regarding copyright ownership. 7 | # The ASF licenses this file to You under the Apache License, Version 2.0 8 | # (the "License"); you may not use this file except in compliance with 9 | # the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'rubygems' 21 | require 'json' 22 | require 'zip' 23 | require 'base64' 24 | require "#{__dir__}/rackapp/middleware/post_method_validation.rb" 25 | require "#{__dir__}/rackapp/middleware/sentinel_handler.rb" 26 | require "#{__dir__}/rackapp/init.rb" 27 | require "#{__dir__}/rackapp/run.rb" 28 | 29 | rackapp = Rack::Builder.app do 30 | use HTTPPostMethodValidation 31 | use SentinelHandler 32 | 33 | map '/init' do 34 | run InitApp.new 35 | end 36 | 37 | map "/run" do 38 | run RunApp.new 39 | end 40 | end 41 | 42 | run rackapp 43 | -------------------------------------------------------------------------------- /core/ruby2.5Action/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Ruby 2.5 OpenWhisk Runtime Container 21 | 22 | ## 1.17.0 23 | - Build actionloop from 1.16@1.18.0 (#63) 24 | - Resolve akka versions explicitly. (#62, #61) 25 | 26 | ## 1.16.0 27 | - Use 1.17.0 release of openwhisk-runtime-go (#52) 28 | - Install the latest security fixes with every image build. 29 | 30 | ## 1.15.0 31 | - Build proxy using golang 1.15 and openwhisk-runtime-go 1.16.0 (#48) 32 | 33 | ## 1.14.0 34 | - Support for __OW_ACTION_VERSION (openwhisk/4761) 35 | 36 | ## 1.13.0-incubating 37 | - Support zip files without dir entries (#15) 38 | 39 | ## 1.0.0 40 | Changes: 41 | - Initial release 42 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | plugins { 19 | id "com.gradle.enterprise" version "3.10.2" 20 | } 21 | 22 | gradleEnterprise { 23 | buildScan { 24 | termsOfServiceUrl = 'https://gradle.com/terms-of-service' 25 | termsOfServiceAgree = 'yes' 26 | publishAlwaysIf(System.getenv('CI') != null) 27 | } 28 | } 29 | 30 | include 'tests' 31 | 32 | include 'core:ruby2.5Action' 33 | include 'core:ruby2.6ActionLoop' 34 | 35 | rootProject.name = 'runtime-ruby' 36 | 37 | gradle.ext.openwhisk = [ 38 | version: '1.0.1-SNAPSHOT' 39 | ] 40 | 41 | gradle.ext.scala = [ 42 | version: '2.12.7', 43 | depVersion : '2.12', 44 | compileFlags: ['-feature', '-unchecked', '-deprecation', '-Xfatal-warnings', '-Ywarn-unused-import'] 45 | ] 46 | 47 | gradle.ext.scalafmt = [ 48 | version: '1.5.1', 49 | config: new File(rootProject.projectDir, '.scalafmt.conf') 50 | ] 51 | 52 | 53 | gradle.ext.akka = [version : '2.6.12'] 54 | gradle.ext.akka_http = [version : '10.2.4'] 55 | -------------------------------------------------------------------------------- /core/ruby2.5Action/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | FROM ruby:2.5 18 | 19 | # install dependencies 20 | RUN gem update --system 3.2.3 21 | RUN gem install \ 22 | bundler \ 23 | rubyzip \ 24 | rack:'~>2.0.0' \ 25 | puma \ 26 | rake `#optional` \ 27 | mechanize `#optional` \ 28 | activesupport:'~>6.1.4.4' \ 29 | jwt `#optional` 30 | 31 | 32 | RUN \ 33 | apt-get -y update \ 34 | # Upgrade installed packages to get latest security fixes if the base image does not contain them already. 35 | && apt-get upgrade -y --no-install-recommends \ 36 | && apt-get install -y zip \ 37 | # Cleanup apt data, we do not need them later on. 38 | && rm -rf /var/lib/apt/lists/* \ 39 | # create src directory to store action files 40 | && mkdir -p /action/src 41 | 42 | ADD rackapp /action/rackapp/ 43 | COPY config.ru /action 44 | 45 | # run webserver 46 | RUN cd /action 47 | EXPOSE 8080 48 | ENV BUNDLE_GEMFILE /action/src/Gemfile 49 | ENV RUBYOPT -EUTF-8 50 | CMD [ "puma", "/action/config.ru", "-b", "tcp://0.0.0.0:8080" ] 51 | -------------------------------------------------------------------------------- /.asf.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | github: 19 | description: "Apache OpenWhisk Runtime Ruby supports Apache OpenWhisk functions written in Ruby" 20 | homepage: https://openwhisk.apache.org/ 21 | labels: 22 | - openwhisk 23 | - apache 24 | - serverless 25 | - faas 26 | - functions-as-a-service 27 | - cloud 28 | - serverless-architectures 29 | - serverless-functions 30 | - docker 31 | - functions 32 | - openwhisk-runtime 33 | - ruby 34 | protected_branches: 35 | master: 36 | required_status_checks: 37 | strict: false 38 | required_pull_request_reviews: 39 | required_approving_review_count: 1 40 | required_signatures: false 41 | enabled_merge_buttons: 42 | merge: false 43 | squash: true 44 | rebase: true 45 | features: 46 | issues: true 47 | 48 | notifications: 49 | commits: commits@openwhisk.apache.org 50 | issues_status: issues@openwhisk.apache.org 51 | issues_comment: issues@openwhisk.apache.org 52 | pullrequests_status: issues@openwhisk.apache.org 53 | pullrequests_comment: issues@openwhisk.apache.org 54 | -------------------------------------------------------------------------------- /core/ruby2.6ActionLoop/lib/launcher.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | require "logger" 18 | require "json" 19 | 20 | # requiring user's action code 21 | require "./main__" 22 | 23 | # open our file descriptor, this allows us to talk to the go-proxy parent process 24 | # code gets executed via file descriptor #3 25 | #out = File.for_fd(3) 26 | out = IO.new(3) 27 | 28 | # run this until process gets killed 29 | while true 30 | # JSON arguments get passed via STDIN 31 | line = STDIN.gets() 32 | break unless line 33 | 34 | # parse JSON arguments that come in via the value parameter 35 | args = JSON.parse(line) 36 | payload = {} 37 | args.each do |key, value| 38 | if key == "value" 39 | payload = value 40 | else 41 | # set environment variables for other keys 42 | ENV["__OW_#{key.upcase}"] = value 43 | end 44 | end 45 | # execute the user's action code 46 | res = {} 47 | begin 48 | res = main(payload) 49 | rescue Exception => e 50 | puts "exception: #{e}" 51 | res ["error"] = "#{e}" 52 | end 53 | 54 | STDOUT.flush() 55 | STDERR.flush() 56 | out.puts(res.to_json) 57 | out.flush() 58 | end 59 | -------------------------------------------------------------------------------- /core/ruby2.6ActionLoop/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # build go proxy from source 19 | FROM golang:1.22 AS builder_source 20 | ARG GO_PROXY_GITHUB_USER=apache 21 | ARG GO_PROXY_GITHUB_BRANCH=master 22 | RUN git clone --branch ${GO_PROXY_GITHUB_BRANCH} \ 23 | https://github.com/${GO_PROXY_GITHUB_USER}/openwhisk-runtime-go /src ;\ 24 | cd /src ; env GO111MODULE=on CGO_ENABLED=0 go build main/proxy.go && \ 25 | mv proxy /bin/proxy 26 | 27 | # or build it from a release 28 | FROM golang:1.22 AS builder_release 29 | ARG GO_PROXY_RELEASE_VERSION=1.22@1.24.0 30 | RUN curl -sL \ 31 | https://github.com/apache/openwhisk-runtime-go/archive/{$GO_PROXY_RELEASE_VERSION}.tar.gz\ 32 | | tar xzf -\ 33 | && cd openwhisk-runtime-go-*/main\ 34 | && GO111MODULE=on CGO_ENABLED=0 go build -o /bin/proxy 35 | 36 | FROM ruby:2.6 37 | 38 | # select the builder to use 39 | ARG GO_PROXY_BUILD_FROM=release 40 | 41 | RUN \ 42 | apt-get -y update \ 43 | # Upgrade installed packages to get latest security fixes if the base image does not contain them already. 44 | && apt-get upgrade -y --no-install-recommends \ 45 | && apt-get install -y zip \ 46 | # Cleanup apt data, we do not need them later on. 47 | && rm -rf /var/lib/apt/lists/* \ 48 | # Create required directories 49 | && mkdir -p /proxy/bin /proxy/lib /proxy/action 50 | 51 | WORKDIR /proxy 52 | COPY --from=builder_source /bin/proxy /bin/proxy_source 53 | COPY --from=builder_release /bin/proxy /bin/proxy_release 54 | RUN mv /bin/proxy_${GO_PROXY_BUILD_FROM} /bin/proxy 55 | ADD lib/launcher.rb /proxy/lib/launcher.rb 56 | ADD bin/compile /proxy/bin/compile 57 | ENV OW_COMPILER=/proxy/bin/compile 58 | ENTRYPOINT ["/bin/proxy"] 59 | -------------------------------------------------------------------------------- /core/ruby2.5Action/rackapp/run.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | require "#{__dir__}/response/success.rb" 19 | require "#{__dir__}/response/error.rb" 20 | require "#{__dir__}/filepath.rb" 21 | 22 | class RunApp 23 | include Filepath 24 | 25 | def call(env) 26 | if !File.exist? ENTRYPOINT then 27 | return ErrorResponse.new 'Invalid Action: no action file found', 500 28 | end 29 | 30 | # Set environment variables 31 | body = Rack::Request.new(env).body.read 32 | data = JSON.parse(body) || {} 33 | env = {'BUNDLE_GEMFILE' => PROGRAM_DIR + 'Gemfile'} 34 | data.each do |key, value| 35 | if key != 'value' 36 | env["__OW_#{key.upcase}"] = value if value && value.is_a?(String) 37 | end 38 | end 39 | 40 | # Save parameter values to file in order to let runner.rb read this later 41 | File.write PARAM, data['value'].to_json 42 | 43 | # Execute the action with given parameters 44 | if system(env, "bundle exec ruby -r #{ENTRYPOINT} #{RACKAPP_DIR}runner.rb | tee #{OUT}") then 45 | if File.exist? RESULT then 46 | result = File.read(RESULT) 47 | if valid_json_or_array(result) then 48 | SuccessResponse.new(JSON.parse(result)) 49 | else 50 | warn "Result must be an array but has type '#{result.class.to_s}': #{result}" 51 | ErrorResponse.new 'The action did not return a dictionary or array.', 502 52 | end 53 | else 54 | ErrorResponse.new 'Invalid Action: An error occurred running the action', 502 55 | end 56 | else 57 | ErrorResponse.new "Invalid Action: the execution was not successful. / #{File.read(OUT)}}", 502 58 | end 59 | end 60 | 61 | private 62 | def valid_json_or_array(json) 63 | JSON.parse(json).class == Hash or JSON.parse(json).class == Array 64 | rescue 65 | false 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /tests/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | apply plugin: 'scala' 19 | apply plugin: 'eclipse' 20 | compileTestScala.options.encoding = 'UTF-8' 21 | 22 | repositories { 23 | mavenCentral() 24 | mavenLocal() 25 | } 26 | 27 | tasks.withType(Test) { 28 | testLogging { 29 | events "passed", "skipped", "failed" 30 | showStandardStreams = true 31 | exceptionFormat = 'full' 32 | } 33 | outputs.upToDateWhen { false } // force tests to run every time 34 | } 35 | 36 | dependencies { 37 | implementation "junit:junit:4.11" 38 | implementation "org.scala-lang:scala-library:${gradle.scala.version}" 39 | implementation "org.scalatest:scalatest_${gradle.scala.depVersion}:3.0.8" 40 | implementation "org.apache.openwhisk:openwhisk-common:${gradle.openwhisk.version}" 41 | implementation "org.apache.openwhisk:openwhisk-tests:${gradle.openwhisk.version}:tests" 42 | implementation "org.apache.openwhisk:openwhisk-tests:${gradle.openwhisk.version}:test-sources" 43 | implementation group: 'com.typesafe.akka', name: "akka-http2-support_${gradle.scala.depVersion}", version: "${gradle.akka_http.version}" 44 | implementation group: 'com.typesafe.akka', name: "akka-http-xml_${gradle.scala.depVersion}", version: "${gradle.akka_http.version}" 45 | implementation group: 'com.typesafe.akka', name: "akka-discovery_${gradle.scala.depVersion}", version: "${gradle.akka.version}" 46 | implementation group: 'com.typesafe.akka', name: "akka-protobuf_${gradle.scala.depVersion}", version: "${gradle.akka.version}" 47 | implementation group: 'com.typesafe.akka', name: "akka-remote_${gradle.scala.depVersion}", version: "${gradle.akka.version}" 48 | implementation group: 'com.typesafe.akka', name: "akka-cluster_${gradle.scala.depVersion}", version: "${gradle.akka.version}" 49 | } 50 | 51 | tasks.withType(ScalaCompile) { 52 | scalaCompileOptions.additionalParameters = gradle.scala.compileFlags 53 | } 54 | 55 | task buildArtifacts(type:Exec) { 56 | workingDir 'src/test/resources' 57 | commandLine './build.sh' 58 | } 59 | 60 | tasks.withType(Test) { 61 | dependsOn buildArtifacts 62 | } 63 | 64 | testClasses.dependsOn(buildArtifacts) 65 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | @rem Execute Gradle 73 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 74 | 75 | :end 76 | @rem End local scope for the variables with windows NT shell 77 | if "%ERRORLEVEL%"=="0" goto mainEnd 78 | 79 | :fail 80 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 81 | rem the _cmd.exe /c_ return code! 82 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 83 | exit /b 1 84 | 85 | :mainEnd 86 | if "%OS%"=="Windows_NT" endlocal 87 | 88 | :omega 89 | -------------------------------------------------------------------------------- /core/ruby2.6ActionLoop/bin/compile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Python Action Builder 3 | # 4 | # Licensed to the Apache Software Foundation (ASF) under one or more 5 | # contributor license agreements. See the NOTICE file distributed with 6 | # this work for additional information regarding copyright ownership. 7 | # The ASF licenses this file to You under the Apache License, Version 2.0 8 | # (the "License"); you may not use this file except in compliance with 9 | # the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | """ 20 | 21 | from __future__ import print_function 22 | import os, sys, codecs, shutil 23 | from os.path import abspath, exists, dirname 24 | 25 | # write a file creating intermediate directories 26 | def write_file(file, body, executable=False): 27 | os.makedirs(dirname(file), mode=0o755, exist_ok=True) 28 | with open(file, mode="w", encoding="utf-8") as f: 29 | f.write(body) 30 | if executable: 31 | os.chmod(file, 0o755) 32 | 33 | # copy a file eventually replacing a substring 34 | def copy_replace(src, dst, match=None, replacement=""): 35 | with codecs.open(src, 'r', 'utf-8') as s: 36 | body = s.read() 37 | if match: 38 | body = body.replace(match, replacement) 39 | write_file(dst, body) 40 | 41 | # assemble sources 42 | def sources(launcher, main, src_dir): 43 | # move exec in the right place if exists 44 | src_file = "%s/exec" % src_dir 45 | if exists(src_file): 46 | copy_replace(src_file, "%s/main__.rb" % src_dir) 47 | 48 | # move main.rb in the right place if it exists 49 | src_file = "%s/main.rb" % src_dir 50 | if exists(src_file): 51 | copy_replace(src_file, "%s/main__.rb" % src_dir) 52 | 53 | # write the boilerplate in a temp dir 54 | copy_replace(launcher, "%s/exec__.rb" % src_dir, 55 | "res = main(payload)", 56 | "res = %s(payload)" % main ) 57 | 58 | # compile sources 59 | def build(src_dir, tgt_dir): 60 | # in general, compile your program into an executable format 61 | # for scripting languages, move sources and create a launcher 62 | # move away the action dir and replace with the new 63 | shutil.rmtree(tgt_dir) 64 | shutil.move(src_dir, tgt_dir) 65 | write_file("%s/exec" % tgt_dir, """#!/bin/sh 66 | cd "$(dirname $0)" 67 | exec /usr/local/bin/ruby exec__.rb 68 | """) 69 | 70 | if __name__ == '__main__': 71 | if len(sys.argv) < 4: 72 | sys.stdout.write("usage: \n") 73 | sys.stdout.flush() 74 | sys.exit(1) 75 | launcher = "%s/lib/launcher.rb" % dirname(dirname(sys.argv[0])) 76 | sources(launcher, sys.argv[1], abspath(sys.argv[2])) 77 | build(abspath(sys.argv[2]), abspath(sys.argv[3])) 78 | sys.stdout.flush() 79 | sys.stderr.flush() 80 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 19 | [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) 20 | 21 | # Contributing to Apache OpenWhisk 22 | 23 | Anyone can contribute to the OpenWhisk project, and we welcome your contributions. 24 | 25 | There are multiple ways to contribute: report bugs, improve the docs, and 26 | contribute code, but you must follow these prerequisites and guidelines: 27 | 28 | - [Contributor License Agreement](#contributor-license-agreement) 29 | - [Raising issues](#raising-issues) 30 | - [Coding Standards](#coding-standards) 31 | 32 | ### Contributor License Agreement 33 | 34 | All contributors must sign and submit an Apache CLA (Contributor License Agreement). 35 | 36 | Instructions on how to do this can be found here: 37 | [http://www.apache.org/licenses/#clas](http://www.apache.org/licenses/#clas) 38 | 39 | Once submitted, you will receive a confirmation email from the Apache Software Foundation (ASF) and be added to 40 | the following list: http://people.apache.org/unlistedclas.html. 41 | 42 | Project committers will use this list to verify pull requests (PRs) come from contributors that have signed a CLA. 43 | 44 | We look forward to your contributions! 45 | 46 | ## Raising issues 47 | 48 | Please raise any bug reports or enhancement requests on the respective project repository's GitHub issue tracker. Be sure to search the 49 | list to see if your issue has already been raised. 50 | 51 | A good bug report is one that make it easy for us to understand what you were trying to do and what went wrong. 52 | Provide as much context as possible, so we can try to recreate the issue. 53 | 54 | A good enhancement request comes with an explanation of what you are trying to do and how that enhancement would help you. 55 | 56 | ### Discussion 57 | 58 | Please use the project's developer email list to engage our community: 59 | [dev@openwhisk.apache.org](dev@openwhisk.apache.org) 60 | 61 | In addition, we provide a "dev" Slack team channel for conversations at: 62 | https://openwhisk-team.slack.com/messages/dev/ 63 | 64 | ### Coding standards 65 | 66 | Please ensure you follow the coding standards used throughout the existing 67 | code base. Some basic rules include: 68 | 69 | - all files must have the Apache license in the header. 70 | - all PRs must have passing builds for all operating systems. 71 | - 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. 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Apache OpenWhisk runtimes for Ruby 21 | [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) 22 | [![Continuous Integration](https://github.com/apache/openwhisk-runtime-ruby/actions/workflows/ci.yaml/badge.svg)](https://github.com/apache/openwhisk-runtime-ruby/actions/workflows/ci.yaml) 23 | 24 | ### Give it a try today 25 | A very simple `hello world` function would be: 26 | 27 | ```ruby 28 | def main(args) 29 | name = args["name"] || "stranger" 30 | greeting = "Hello #{name}!" 31 | puts greeting 32 | { "greeting" => greeting } 33 | end 34 | ``` 35 | 36 | For the return result, not only support `dictionary` but also support `array` 37 | 38 | So a very simple `hello array` function would be: 39 | 40 | ```ruby 41 | def main(args) 42 | nums = Array["a","b"] 43 | nums 44 | end 45 | ``` 46 | 47 | And support array result for sequence action as well, the first action's array result can be used as next action's input parameter. 48 | 49 | So the function can be 50 | 51 | ```ruby 52 | def main(args) 53 | args 54 | end 55 | ``` 56 | 57 | To use as a docker action 58 | ``` 59 | wsk action update myAction my_action.rb --docker openwhisk/action-ruby-v2.5 60 | ``` 61 | This works on any deployment of Apache OpenWhisk 62 | 63 | ### To use on deployment that contains the runtime as a kind 64 | To use as a kind action 65 | ``` 66 | wsk action update myAction my_action.rb --kind ruby:2.5 67 | ``` 68 | 69 | ### Local development 70 | ``` 71 | ./gradlew core:ruby2.5Action:distDocker 72 | ``` 73 | This will produce the image `whisk/action-ruby-v2.5` 74 | 75 | Build and Push image 76 | ``` 77 | docker login 78 | ./gradlew core:ruby2.5Action:distDocker -PdockerImagePrefix=$prefix-user -PdockerRegistry=docker.io 79 | ``` 80 | 81 | Deploy OpenWhisk using ansible environment that contains the kind `ruby:2.5` 82 | Assuming you have OpenWhisk already deploy locally and `OPENWHISK_HOME` pointing to root directory of OpenWhisk core repository. 83 | 84 | Set `ROOTDIR` to the root directory of this repository. 85 | 86 | Redeploy OpenWhisk 87 | ``` 88 | cd $OPENWHISK_HOME/ansible 89 | ANSIBLE_CMD="ansible-playbook -i ${ROOTDIR}/ansible/environments/local" 90 | $ANSIBLE_CMD setup.yml 91 | $ANSIBLE_CMD couchdb.yml 92 | $ANSIBLE_CMD initdb.yml 93 | $ANSIBLE_CMD wipe.yml 94 | $ANSIBLE_CMD openwhisk.yml 95 | ``` 96 | 97 | Or you can use `wskdev` and create a soft link to the target ansible environment, for example: 98 | ``` 99 | ln -s ${ROOTDIR}/ansible/environments/local ${OPENWHISK_HOME}/ansible/environments/local-ruby 100 | wskdev fresh -t local-ruby 101 | ``` 102 | 103 | To use as docker action push to your own dockerhub account 104 | ``` 105 | docker tag whisk/ruby2.5Action $user_prefix/action-ruby-v2.5 106 | docker push $user_prefix/action-ruby-v2.5 107 | ``` 108 | Then create the action using your image from Docker Hub. 109 | ``` 110 | wsk action update myAction my_action.rb --docker $user_prefix/action-ruby-v2.5 111 | ``` 112 | The `$user_prefix` is usually your dockerhub user id. 113 | 114 | ### Testing 115 | Install dependencies from the root directory on $OPENWHISK_HOME repository 116 | ``` 117 | ./gradlew install 118 | ``` 119 | 120 | Using gradle to run all tests 121 | ``` 122 | ./gradlew :tests:test 123 | ``` 124 | Using gradle to run some tests 125 | ``` 126 | ./gradlew :tests:test --tests *ActionContainerTests* 127 | ``` 128 | Using IntelliJ: 129 | - Import project as gradle project. 130 | - Make sure the working directory is root of the project/repo. 131 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | # 18 | 19 | name: Continuous Integration 20 | 21 | on: 22 | push: 23 | branches: [ master ] 24 | tags: [ '*' ] 25 | pull_request: 26 | branches: [ master ] 27 | types: [ opened, synchronize, reopened ] 28 | schedule: 29 | - cron: '30 1 * * 1,3,5' 30 | workflow_dispatch: 31 | 32 | permissions: read-all 33 | 34 | jobs: 35 | ci: 36 | runs-on: ubuntu-22.04 37 | env: 38 | PUSH_NIGHTLY: ${{ (github.event_name == 'push' || github.event_name == 'schedule') && github.ref == 'refs/heads/master' }} 39 | PUSH_RELEASE: ${{ github.event_name == 'push' && github.ref_type == 'tag' }} 40 | steps: 41 | # Checkout just this repo and run scanCode before we do anything else 42 | - name: Checkout runtime repo 43 | uses: actions/checkout@v4 44 | with: 45 | path: runtime 46 | - name: Scan Code 47 | uses: apache/openwhisk-utilities/scancode@master 48 | 49 | # Install core OpenWhisk artifacts needed to build/test anything else 50 | - name: Checkout OpenWhisk core repo 51 | uses: actions/checkout@v4 52 | with: 53 | repository: apache/openwhisk 54 | path: core 55 | - name: Setup Java 56 | uses: actions/setup-java@v4 57 | with: 58 | distribution: 'temurin' 59 | java-version: '11' 60 | - name: Compile and Install Core OpenWhisk 61 | working-directory: core 62 | run: | 63 | ./gradlew :tests:compileTestScala 64 | ./gradlew install 65 | 66 | # Build this repository 67 | - name: Build Runtime 68 | working-directory: runtime 69 | run: | 70 | ./gradlew distDocker 71 | 72 | # Test this repository 73 | - name: Test Runtime 74 | working-directory: runtime 75 | run: | 76 | ./gradlew :tests:checkScalafmtAll 77 | ./gradlew :tests:test 78 | 79 | # Conditionally publish runtime images to DockerHub 80 | # Important: naming convention for release tags is runtime@version 81 | - name: Docker Login 82 | if: ${{ env.PUSH_NIGHTLY == 'true' || env.PUSH_RELEASE == 'true' }} 83 | uses: docker/login-action@v3 84 | with: 85 | username: ${{ secrets.DOCKERHUB_USER_OPENWHISK }} 86 | password: ${{ secrets.DOCKERHUB_TOKEN_OPENWHISK }} 87 | - name: Push Nightly Images 88 | if: ${{ env.PUSH_NIGHTLY == 'true' }} 89 | working-directory: runtime 90 | run: | 91 | SHORT_COMMIT=$(git rev-parse --short "$GITHUB_SHA") 92 | ./gradlew :core:ruby2.5Action:distDocker -PdockerRegistry=docker.io -PdockerImagePrefix=openwhisk -PdockerImageTag=nightly 93 | ./gradlew :core:ruby2.5Action:distDocker -PdockerRegistry=docker.io -PdockerImagePrefix=openwhisk -PdockerImageTag=$SHORT_COMMIT 94 | ./gradlew :core:ruby2.6ActionLoop:distDocker -PdockerRegistry=docker.io -PdockerImagePrefix=openwhisk -PdockerImageTag=nightly 95 | ./gradlew :core:ruby2.6ActionLoop:distDocker -PdockerRegistry=docker.io -PdockerImagePrefix=openwhisk -PdockerImageTag=$SHORT_COMMIT 96 | - name: Push Release Images 97 | if: ${{ env.PUSH_RELEASE == 'true' }} 98 | working-directory: runtime 99 | run: | 100 | RUNTIME_VERSION=${GITHUB_REF_NAME%@*} 101 | IMAGE_TAG=${GITHUB_REF_NAME##*@} 102 | if [ ${RUNTIME_VERSION} == "2.5" ]; then 103 | RUNTIME="ruby2.5Action" 104 | elif [ ${RUNTIME_VERSION} == "2.6" ]; then 105 | RUNTIME="ruby2.6ActionLoop" 106 | fi 107 | ./gradlew :core:$RUNTIME:distDocker -PdockerRegistry=docker.io -PdockerImagePrefix=openwhisk -PdockerImageTag=$IMAGE_TAG 108 | -------------------------------------------------------------------------------- /tests/src/test/scala/actionContainers/Ruby26ActionLoopContainerTests.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package runtime.actionContainers 18 | 19 | import actionContainers.ActionContainer.withContainer 20 | import actionContainers.{ActionContainer, BasicActionRunnerTests} 21 | import common.WskActorSystem 22 | import org.junit.runner.RunWith 23 | import org.scalatest.junit.JUnitRunner 24 | import spray.json.{JsArray, JsObject, JsString} 25 | 26 | @RunWith(classOf[JUnitRunner]) 27 | class Ruby26ActionLoopContainerTests extends BasicActionRunnerTests with WskActorSystem { 28 | 29 | val image = "actionloop-ruby-v2.6" 30 | 31 | override def withActionContainer(env: Map[String, String] = Map.empty)(code: ActionContainer => Unit) = { 32 | withContainer(image, env)(code) 33 | } 34 | 35 | def withActionLoopContainer(code: ActionContainer => Unit) = 36 | withContainer(image)(code) 37 | 38 | behavior of image 39 | 40 | override val testNoSourceOrExec = TestConfig("") 41 | 42 | override val testNotReturningJson = 43 | TestConfig("""|def main(args) 44 | | "not a json object" 45 | |end 46 | |""".stripMargin) 47 | 48 | override val testEcho = TestConfig("""|def main(args) 49 | | puts 'hello stdout' 50 | | warn 'hello stderr' 51 | | args 52 | |end 53 | |""".stripMargin) 54 | 55 | override val testUnicode = TestConfig("""|def main(args) 56 | | str = args['delimiter'] + " ☃ " + args['delimiter'] 57 | | print str + "\n" 58 | | {"winter" => str} 59 | |end 60 | |""".stripMargin) 61 | 62 | override val testEnv = TestConfig( 63 | """|def main(args) 64 | | { 65 | | "api_host" => ENV['__OW_API_HOST'], 66 | | "api_key" => ENV['__OW_API_KEY'], 67 | | "namespace" => ENV['__OW_NAMESPACE'], 68 | | "action_name" => ENV['__OW_ACTION_NAME'], 69 | | "action_version" => ENV['__OW_ACTION_VERSION'], 70 | | "activation_id" => ENV['__OW_ACTIVATION_ID'], 71 | | "deadline" => ENV['__OW_DEADLINE'] 72 | | } 73 | |end 74 | |""".stripMargin, 75 | enforceEmptyOutputStream = false) 76 | 77 | override val testInitCannotBeCalledMoreThanOnce = TestConfig(s"""|def main(args) 78 | | args 79 | |end 80 | |""".stripMargin) 81 | 82 | override val testEntryPointOtherThanMain = TestConfig( 83 | s"""|def niam(args) 84 | | args 85 | |end 86 | |""".stripMargin, 87 | main = "niam") 88 | 89 | override val testLargeInput = TestConfig(s"""|def main(args) 90 | | args 91 | |end 92 | |""".stripMargin) 93 | 94 | it should "support return array result" in { 95 | val (out, err) = withActionLoopContainer { c => 96 | val code = """ 97 | | def main(args) 98 | | nums = Array["a","b"] 99 | | nums 100 | | end 101 | """.stripMargin 102 | 103 | val (initCode, _) = c.init(initPayload(code)) 104 | 105 | initCode should be(200) 106 | 107 | val (runCode, runRes) = c.runForJsArray(runPayload(JsObject())) 108 | runCode should be(200) 109 | runRes shouldBe Some(JsArray(JsString("a"), JsString("b"))) 110 | } 111 | } 112 | 113 | it should "support array as input param" in { 114 | val (out, err) = withActionLoopContainer { c => 115 | val code = """ 116 | | def main(args) 117 | | args 118 | | end 119 | """.stripMargin 120 | 121 | val (initCode, _) = c.init(initPayload(code)) 122 | 123 | initCode should be(200) 124 | 125 | val (runCode, runRes) = c.runForJsArray(runPayload(JsArray(JsString("a"), JsString("b")))) 126 | runCode should be(200) 127 | runRes shouldBe Some(JsArray(JsString("a"), JsString("b"))) 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /gradle/README.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Gradle 21 | 22 | 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. 23 | 24 | ## Usage 25 | 26 | 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). 27 | 28 | ### Build 29 | 30 | To build all Docker images use `gradle distDocker` at the top level project, to build a specific component use `gradle :core:controller:distDocker`. 31 | 32 | Project level options that can be used on `distDocker`: 33 | 34 | - `dockerImageName` (*required*): The name of the image to build (e.g. whisk/controller) 35 | - `dockerHost` (*optional*): The docker host to run commands on, default behaviour is docker's own `DOCKER_HOST` environment variable 36 | - `dockerRegistry` (*optional*): The registry to push to 37 | - `dockerImageTag` (*optional*, default 'latest'): The tag for the image 38 | - `dockerTimeout` (*optional*, default 240): Timeout for docker operations in seconds 39 | - `dockerRetries` (*optional*, default 3): How many times to retry docker operations 40 | - `dockerBinary` (*optional*, default `docker`): The binary to execute docker commands 41 | 42 | ### Test 43 | 44 | 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`. 45 | 46 | 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. 47 | 48 | ## Build your own `build.gradle` 49 | 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. 50 | 51 | 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`. 52 | 53 | 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 54 | 55 | ``` 56 | ext.dockerImageName = 'openwwhisk/{IMAGENAME}' 57 | apply from: 'path/to/docker.gradle' 58 | ``` 59 | 60 | 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: 61 | 62 | ``` 63 | distDocker.dependsOn ':common:scala:distDocker', 'distTar' 64 | ``` 65 | -------------------------------------------------------------------------------- /core/ruby2.5Action/rackapp/init.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | require "#{__dir__}/response/success.rb" 19 | require "#{__dir__}/response/error.rb" 20 | require "#{__dir__}/filepath.rb" 21 | 22 | class InitApp 23 | include Filepath 24 | 25 | def call(env) 26 | # Make sure that this action is not initialised more than once 27 | if File.exist? CONFIG then 28 | puts "Error: Cannot initialize the action more than once." 29 | STDOUT.flush 30 | return ErrorResponse.new 'Cannot initialize the action more than once.', 403 31 | end 32 | 33 | # Expect JSON data input 34 | body = Rack::Request.new(env).body.read 35 | data = JSON.parse(body)['value'] || {} 36 | 37 | # Is the input data empty? 38 | if data == {} then 39 | return ErrorResponse.new 'Missing main/no code to execute.', 500 40 | end 41 | 42 | name = data['name'] || '' # action name 43 | main = data['main'] || '' # function to call 44 | code = data['code'] || '' # source code to run 45 | binary = data['binary'] || false # code is binary? 46 | 47 | # Are name/main/code variables instance of String? 48 | if ![name, main, code].map{|e| e.is_a? String }.inject{|a,b| a && b } then 49 | return ErrorResponse.new 'Invalid Parameters: failed to handle the request', 500 50 | end 51 | 52 | env = {'BUNDLE_GEMFILE' => PROGRAM_DIR + 'Gemfile'} 53 | if binary then 54 | File.write TMP_ZIP, Base64.decode64(code) 55 | if !unzip(TMP_ZIP, PROGRAM_DIR) then 56 | return ErrorResponse.new 'Invalid Binary: failed to open zip file. Please make sure you have finished $bundle package successfully.', 500 57 | end 58 | # Try to resolve dependencies 59 | if File.exist?(PROGRAM_DIR + 'Gemfile') then 60 | if !File.directory?(PROGRAM_DIR + 'vendor/cache') then 61 | return ErrorResponse.new 'Invalid Binary: vendor/cache folder is not found. Please make sure you have used valid zip binary.', 200 62 | end 63 | if !system(env, "bundle install --local 2> #{ERR} 1> #{OUT}") then 64 | return ErrorResponse.new "Invalid Binary: failed to resolve dependencies / #{File.read(OUT)} / #{File.read(ERR)}", 500 65 | end 66 | else 67 | 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. 68 | end 69 | if !File.exist?(ENTRYPOINT) then 70 | return ErrorResponse.new 'Invalid Ruby Code: zipped actions must contain main.rb at the root.', 500 71 | end 72 | else 73 | # Save the code for future use 74 | File.write ENTRYPOINT, code 75 | 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. 76 | end 77 | 78 | # Check if the ENTRYPOINT code is valid or not 79 | if !valid_code?(ENTRYPOINT) then 80 | return ErrorResponse.new 'Invalid Ruby Code: failed to parse the input code', 500 81 | end 82 | 83 | # Check if the method exists as expected 84 | if !system(env, "bundle exec ruby -r #{ENTRYPOINT} -e \"method(:#{main}) ? true : raise(Exception.new('Error'))\" 2> #{ERR} 1> #{OUT}") then 85 | return ErrorResponse.new "Invalid Ruby Code: method checking failed / #{File.read(OUT)} / #{File.read(ERR)}", 500 86 | end 87 | 88 | # Save config parameters to filesystem so that later /run can use this 89 | File.write CONFIG, {:main=>main, :name=>name}.to_json 90 | 91 | # Proceed with the next step 92 | SuccessResponse.new({'OK'=>true}) 93 | end 94 | 95 | private 96 | def valid_code?(path) 97 | system("ruby -e 'RubyVM::InstructionSequence.compile_file(\"#{path}\")' 2> #{ERR} 1> #{OUT}") 98 | rescue 99 | false 100 | end 101 | 102 | def unzip(zipfile_path, destination_folder) 103 | Zip::File.open(zipfile_path) do |zip| 104 | zip.each do |file| 105 | f_path = destination_folder + file.name 106 | FileUtils.mkdir_p(File.dirname(f_path)) 107 | zip.extract(file, f_path) 108 | end 109 | end 110 | true 111 | rescue 112 | false 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /gradle/docker.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import groovy.time.* 19 | 20 | /** 21 | * Utility to build docker images based in gradle projects 22 | * 23 | * This extends gradle's 'application' plugin logic with a 'distDocker' task which builds 24 | * a docker image from the Dockerfile of the project that applies this file. The image 25 | * is automatically tagged and pushed if a tag and/or a registry is given. 26 | * 27 | * Parameters that can be set on project level: 28 | * - dockerImageName (required): The name of the image to build (e.g. controller) 29 | * - dockerRegistry (optional): The registry to push to 30 | * - dockerImageTag (optional, default 'latest'): The tag for the image 31 | * - dockerImagePrefix (optional, default 'whisk'): The prefix for the image, 32 | * 'controller' becomes 'whisk/controller' per default 33 | * - dockerTimeout (optional, default 840): Timeout for docker operations in seconds 34 | * - dockerRetries (optional, default 3): How many times to retry docker operations 35 | * - dockerBinary (optional, default 'docker'): The binary to execute docker commands 36 | * - dockerBuildArgs (options, default ''): Project specific custom docker build arguments 37 | * - dockerHost (optional): The docker host to run commands on, default behaviour is 38 | * docker's own DOCKER_HOST environment variable 39 | */ 40 | 41 | ext { 42 | dockerRegistry = project.hasProperty('dockerRegistry') ? dockerRegistry + '/' : '' 43 | dockerImageTag = project.hasProperty('dockerImageTag') ? dockerImageTag : 'latest' 44 | dockerImagePrefix = project.hasProperty('dockerImagePrefix') ? dockerImagePrefix : 'whisk' 45 | dockerTimeout = project.hasProperty('dockerTimeout') ? dockerTimeout.toInteger() : 840 46 | dockerRetries = project.hasProperty('dockerRetries') ? dockerRetries.toInteger() : 3 47 | dockerBinary = project.hasProperty('dockerBinary') ? [dockerBinary] : ['docker'] 48 | dockerBuildArg = ['build'] 49 | } 50 | ext.dockerTaggedImageName = dockerRegistry + dockerImagePrefix + '/' + dockerImageName + ':' + dockerImageTag 51 | 52 | if(project.hasProperty('dockerHost')) { 53 | dockerBinary += ['--host', project.dockerHost] 54 | } 55 | 56 | if(project.hasProperty('dockerBuildArgs')) { 57 | dockerBuildArgs.split(' ').each { arg -> 58 | dockerBuildArg += ['--build-arg', arg] 59 | } 60 | } 61 | 62 | task distDocker { 63 | doLast { 64 | def start = new Date() 65 | def cmd = dockerBinary + dockerBuildArg + ['-t', dockerImageName, project.buildscript.sourceFile.getParentFile().getAbsolutePath()] 66 | retry(cmd, dockerRetries, dockerTimeout) 67 | println("Building '${dockerImageName}' took ${TimeCategory.minus(new Date(), start)}") 68 | } 69 | } 70 | task tagImage { 71 | doLast { 72 | def versionString = (dockerBinary + ['-v']).execute().text 73 | def matched = (versionString =~ /(\d+)\.(\d+)\.(\d+)/) 74 | 75 | def major = matched[0][1] as int 76 | def minor = matched[0][2] as int 77 | 78 | def dockerCmd = ['tag'] 79 | if(major == 1 && minor < 12) { 80 | dockerCmd += ['-f'] 81 | } 82 | retry(dockerBinary + dockerCmd + [dockerImageName, dockerTaggedImageName], dockerRetries, dockerTimeout) 83 | } 84 | } 85 | 86 | task pushImage { 87 | doLast { 88 | def cmd = dockerBinary + ['push', dockerTaggedImageName] 89 | retry(cmd, dockerRetries, dockerTimeout) 90 | } 91 | } 92 | pushImage.dependsOn tagImage 93 | pushImage.onlyIf { dockerRegistry != '' } 94 | distDocker.finalizedBy pushImage 95 | 96 | def retry(cmd, retries, timeout) { 97 | println("${new Date()}: Executing '${cmd.join(" ")}'") 98 | def proc = cmd.execute() 99 | proc.consumeProcessOutput(System.out, System.err) 100 | proc.waitForOrKill(timeout * 1000) 101 | if(proc.exitValue() != 0) { 102 | def message = "${new Date()}: Command '${cmd.join(" ")}' failed with exitCode ${proc.exitValue()}" 103 | if(proc.exitValue() == 143) { // 143 means the process was killed (SIGTERM signal) 104 | message = "${new Date()}: Command '${cmd.join(" ")}' was killed after ${timeout} seconds" 105 | } 106 | 107 | if(retries > 1) { 108 | println("${message}, ${retries-1} retries left, retrying...") 109 | retry(cmd, retries-1, timeout) 110 | } 111 | else { 112 | println("${message}, no more retries left, aborting...") 113 | throw new GradleException(message) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MYSY switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | ======================================================================== 205 | Apache License 2.0 206 | ======================================================================== 207 | 208 | This product bundles the files gradlew and gradlew.bat from Gradle v5.5 209 | which are distributed under the Apache License, Version 2.0. 210 | For details see ./gradlew and ./gradlew.bat. 211 | -------------------------------------------------------------------------------- /tests/src/test/scala/actionContainers/Ruby25ActionContainerTests.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package runtime.actionContainers 19 | 20 | import org.junit.runner.RunWith 21 | import org.scalatest.junit.JUnitRunner 22 | import common.WskActorSystem 23 | import actionContainers.{ActionContainer, BasicActionRunnerTests} 24 | import actionContainers.ActionContainer.withContainer 25 | import actionContainers.ResourceHelpers.ZipBuilder 26 | import actionContainers.ResourceHelpers 27 | import java.nio.file.FileSystems; 28 | import spray.json._ 29 | 30 | @RunWith(classOf[JUnitRunner]) 31 | class Ruby25ActionContainerTests extends BasicActionRunnerTests with WskActorSystem { 32 | // note: "out" will not be empty as the Webrick outputs a message during the boot and after the boot 33 | val enforceEmptyOutputStream = false 34 | 35 | lazy val ruby25ContainerImageName = "action-ruby-v2.5" 36 | 37 | override def withActionContainer(env: Map[String, String] = Map.empty)(code: ActionContainer => Unit) = { 38 | withContainer(ruby25ContainerImageName, env)(code) 39 | } 40 | 41 | def withRuby25Container(code: ActionContainer => Unit) = withActionContainer()(code) 42 | 43 | behavior of ruby25ContainerImageName 44 | 45 | override val testNoSourceOrExec = TestConfig("") 46 | 47 | override val testEcho = { 48 | TestConfig(""" 49 | |def main(args) 50 | | puts 'hello stdout' 51 | | warn 'hello stderr' 52 | | args 53 | |end 54 | """.stripMargin) 55 | } 56 | 57 | override val testNotReturningJson = { 58 | TestConfig( 59 | """ 60 | |def main(args) 61 | | "not a json object" 62 | |end 63 | """.stripMargin, 64 | enforceEmptyOutputStream = enforceEmptyOutputStream, 65 | enforceEmptyErrorStream = false) 66 | } 67 | 68 | override val testInitCannotBeCalledMoreThanOnce = { 69 | TestConfig( 70 | """ 71 | |def main(args) 72 | | args 73 | |end 74 | """.stripMargin, 75 | enforceEmptyOutputStream = enforceEmptyOutputStream) 76 | } 77 | 78 | override val testEntryPointOtherThanMain = { 79 | TestConfig( 80 | """ 81 | |def niam(args) 82 | | args 83 | |end 84 | """.stripMargin, 85 | main = "niam", 86 | enforceEmptyOutputStream = enforceEmptyOutputStream) 87 | } 88 | 89 | override val testUnicode = { 90 | TestConfig(""" 91 | |def main(args) 92 | | str = args['delimiter'] + " ☃ " + args['delimiter'] 93 | | print str + "\n" 94 | | {"winter" => str} 95 | |end 96 | """.stripMargin.trim) 97 | } 98 | 99 | override val testEnv = { 100 | TestConfig( 101 | """ 102 | |def main(args) 103 | | { 104 | | "env" => ENV, 105 | | "api_host" => ENV['__OW_API_HOST'], 106 | | "api_key" => ENV['__OW_API_KEY'], 107 | | "namespace" => ENV['__OW_NAMESPACE'], 108 | | "action_name" => ENV['__OW_ACTION_NAME'], 109 | | "action_version" => ENV['__OW_ACTION_VERSION'], 110 | | "activation_id" => ENV['__OW_ACTIVATION_ID'], 111 | | "deadline" => ENV['__OW_DEADLINE'] 112 | | } 113 | |end 114 | """.stripMargin.trim, 115 | enforceEmptyOutputStream = enforceEmptyOutputStream) 116 | } 117 | 118 | override val testLargeInput = { 119 | TestConfig(""" 120 | |def main(args) 121 | | args 122 | |end 123 | """.stripMargin) 124 | } 125 | 126 | it should "fail to initialize with bad code" in { 127 | val (out, err) = withRuby25Container { c => 128 | val code = """ 129 | | 10 PRINT "Hello world!" 130 | | 20 GOTO 10 131 | """.stripMargin 132 | 133 | val (initCode, error) = c.init(initPayload(code)) 134 | initCode should not be (200) 135 | error shouldBe a[Some[_]] 136 | error.get shouldBe a[JsObject] 137 | error.get.fields("error").toString should include("failed to parse the input code") 138 | } 139 | 140 | // Somewhere, the logs should mention an error occurred. 141 | checkStreams(out, err, { 142 | case (o, e) => 143 | (o + e).toLowerCase should include("invalid") 144 | (o + e).toLowerCase should include("parse") 145 | }) 146 | } 147 | 148 | it should "return some error on action error" in { 149 | val (out, err) = withRuby25Container { c => 150 | val code = """ 151 | | def main(args) 152 | | raise Exception.new("nooooo") 153 | | end 154 | """.stripMargin 155 | 156 | val (initCode, _) = c.init(initPayload(code)) 157 | initCode should be(200) 158 | 159 | val (runCode, runRes) = c.run(runPayload(JsObject())) 160 | runCode should not be (200) 161 | 162 | runRes shouldBe defined 163 | runRes.get.fields.get("error") shouldBe defined 164 | // runRes.get.fields("error").toString.toLowerCase should include("nooooo") 165 | } 166 | 167 | // Somewhere, the logs should be the error text 168 | checkStreams(out, err, { 169 | case (o, e) => 170 | (o + e).toLowerCase should include("nooooo") 171 | }) 172 | 173 | } 174 | 175 | it should "support application errors" in { 176 | withRuby25Container { c => 177 | val code = """ 178 | | def main(args) 179 | | { "error" => "sorry" } 180 | | end 181 | """.stripMargin; 182 | 183 | val (initCode, error) = c.init(initPayload(code)) 184 | initCode should be(200) 185 | 186 | val (runCode, runRes) = c.run(runPayload(JsObject())) 187 | runCode should be(200) // action writer returning an error is OK 188 | 189 | runRes shouldBe defined 190 | runRes.get.fields.get("error") shouldBe defined 191 | runRes.get.fields("error").toString.toLowerCase should include("sorry") 192 | } 193 | } 194 | 195 | it should "fail gracefully when an action has a TypeError exception" in { 196 | val (out, err) = withRuby25Container { c => 197 | val code = """ 198 | | def main(args) 199 | | eval "class ENV\nend" 200 | | { "hello" => "world" } 201 | | end 202 | """.stripMargin; 203 | 204 | val (initCode, _) = c.init(initPayload(code)) 205 | initCode should be(200) 206 | 207 | val (runCode, runRes) = c.run(runPayload(JsObject())) 208 | runCode should be(502) 209 | 210 | runRes shouldBe defined 211 | runRes.get.fields.get("error") shouldBe defined 212 | runRes.get.fields("error").toString should include("An error occurred running the action") 213 | } 214 | 215 | // Somewhere, the logs should be the error text 216 | checkStreams(out, err, { 217 | case (o, e) => 218 | (o + e).toLowerCase should include("typeerror") 219 | }) 220 | } 221 | 222 | it should "support the documentation examples (1)" in { 223 | val (out, err) = withRuby25Container { c => 224 | val code = """ 225 | | def main(params) 226 | | if (params['payload'] == 0) then 227 | | return {} 228 | | elsif params['payload'] == 1 then 229 | | return {'payload' => 'Hello, World!'} # indicates normal completion 230 | | elsif params['payload'] == 2 then 231 | | return {'error' => 'payload must be 0 or 1'} # indicates abnormal completion 232 | | end 233 | | end 234 | """.stripMargin 235 | 236 | c.init(initPayload(code))._1 should be(200) 237 | 238 | val (c1, r1) = c.run(runPayload(JsObject("payload" -> JsNumber(0)))) 239 | val (c2, r2) = c.run(runPayload(JsObject("payload" -> JsNumber(1)))) 240 | val (c3, r3) = c.run(runPayload(JsObject("payload" -> JsNumber(2)))) 241 | 242 | c1 should be(200) 243 | r1 should be(Some(JsObject())) 244 | 245 | c2 should be(200) 246 | r2 should be(Some(JsObject("payload" -> JsString("Hello, World!")))) 247 | 248 | c3 should be(200) // application error, not container or system 249 | r3.get.fields.get("error") shouldBe Some(JsString("payload must be 0 or 1")) 250 | } 251 | } 252 | 253 | it should "have mechanize and activesupport gems available" in { 254 | // GIVEN that it should "error when requiring a non-existent package" (see test above for this) 255 | val (out, err) = withRuby25Container { c => 256 | val code = """ 257 | | require 'mechanize' 258 | | require 'active_support' 259 | | def main(args) 260 | | Mechanize.class 261 | | ActiveSupport.class 262 | | {} 263 | | end 264 | """.stripMargin 265 | 266 | val (initCode, _) = c.init(initPayload(code)) 267 | 268 | initCode should be(200) 269 | 270 | // WHEN I run an action that calls a Guzzle & a Uuid method 271 | val (runCode, out) = c.run(runPayload(JsObject())) 272 | 273 | // THEN it should pass only when these packages are available 274 | runCode should be(200) 275 | } 276 | } 277 | 278 | it should "support large-ish actions" in { 279 | val thought = " I took the one less traveled by, and that has made all the difference." 280 | val assignment = " x = \"" + thought + "\";\n" 281 | 282 | val code = """ 283 | | def main(args) 284 | | x = "hello" 285 | """.stripMargin + (assignment * 7000) + """ 286 | | x = "world" 287 | | { "message" => x } 288 | | end 289 | """.stripMargin 290 | 291 | // Lest someone should make it too easy. 292 | code.length should be >= 500000 293 | 294 | val (out, err) = withRuby25Container { c => 295 | c.init(initPayload(code))._1 should be(200) 296 | 297 | val (runCode, runRes) = c.run(runPayload(JsObject())) 298 | 299 | runCode should be(200) 300 | runRes.get.fields.get("message") shouldBe defined 301 | runRes.get.fields.get("message") shouldBe Some(JsString("world")) 302 | } 303 | } 304 | 305 | val exampleOutputDotRuby: String = """ 306 | | def output(data) 307 | | {'result' => data} 308 | | end 309 | """.stripMargin 310 | 311 | it should "support zip-encoded packages" in { 312 | val srcs = Seq( 313 | Seq("output.rb") -> exampleOutputDotRuby, 314 | Seq("main.rb") -> """ 315 | | require __dir__ + '/output.rb' 316 | | def main(args) 317 | | name = args['name'] || 'stranger' 318 | | output(name) 319 | | end 320 | """.stripMargin) 321 | 322 | val code = ZipBuilder.mkBase64Zip(srcs) 323 | 324 | val (out, err) = withRuby25Container { c => 325 | c.init(initPayload(code))._1 should be(200) 326 | 327 | val (runCode, runRes) = c.run(runPayload(JsObject())) 328 | 329 | runCode should be(200) 330 | runRes.get.fields.get("result") shouldBe defined 331 | runRes.get.fields.get("result") shouldBe Some(JsString("stranger")) 332 | } 333 | } 334 | 335 | it should "support zip-encoded packages without directory entries" in { 336 | val path = FileSystems.getDefault().getPath("src", "test", "resources", "without_dir_entries.zip"); 337 | val code = ResourceHelpers.readAsBase64(path) 338 | 339 | val (out, err) = withRuby25Container { c => 340 | c.init(initPayload(code))._1 should be(200) 341 | 342 | val (runCode, runRes) = c.run(runPayload(JsObject())) 343 | 344 | runCode should be(200) 345 | runRes.get.fields.get("greeting") shouldBe defined 346 | runRes.get.fields.get("greeting") shouldBe Some(JsString("Hello stranger!")) 347 | } 348 | } 349 | 350 | it should "fail gracefully on invalid zip files" in { 351 | // Some text-file encoded to base64. 352 | val code = "Q2VjaSBuJ2VzdCBwYXMgdW4gemlwLgo=" 353 | 354 | val (out, err) = withRuby25Container { c => 355 | val (initCode, error) = c.init(initPayload(code)) 356 | initCode should not be (200) 357 | error shouldBe a[Some[_]] 358 | error.get shouldBe a[JsObject] 359 | error.get.fields("error").toString should include("failed to open zip file") 360 | } 361 | 362 | // Somewhere, the logs should mention the failure 363 | checkStreams(out, err, { 364 | case (o, e) => 365 | (o + e).toLowerCase should include("error") 366 | (o + e).toLowerCase should include("failed to open zip file") 367 | }) 368 | } 369 | 370 | it should "fail gracefully on valid zip files that are not actions" in { 371 | val srcs = Seq(Seq("hello") -> """ 372 | | Hello world! 373 | """.stripMargin) 374 | 375 | val code = ZipBuilder.mkBase64Zip(srcs) 376 | 377 | val (out, err) = withRuby25Container { c => 378 | c.init(initPayload(code))._1 should not be (200) 379 | } 380 | 381 | checkStreams(out, err, { 382 | case (o, e) => 383 | (o + e).toLowerCase should include("error") 384 | (o + e).toLowerCase should include("zipped actions must contain main.rb at the root.") 385 | }) 386 | } 387 | 388 | it should "fail gracefully on valid zip files with invalid code in main.rb" in { 389 | val (out, err) = withRuby25Container { c => 390 | val srcs = Seq(Seq("main.rb") -> """ 391 | | 10 PRINT "Hello world!" 392 | | 20 GOTO 10 393 | """.stripMargin) 394 | 395 | val code = ZipBuilder.mkBase64Zip(srcs) 396 | 397 | val (initCode, error) = c.init(initPayload(code)) 398 | initCode should not be (200) 399 | error shouldBe a[Some[_]] 400 | error.get shouldBe a[JsObject] 401 | error.get.fields("error").toString should include("failed to parse the input code") 402 | } 403 | 404 | // Somewhere, the logs should mention an error occurred. 405 | checkStreams(out, err, { 406 | case (o, e) => 407 | (o + e).toLowerCase should include("invalid") 408 | (o + e).toLowerCase should include("parse") 409 | }) 410 | } 411 | 412 | it should "support zipped actions using non-default entry point" in { 413 | val srcs = Seq(Seq("main.rb") -> """ 414 | | def niam(args) 415 | | { :result => "it works" } 416 | | end 417 | """.stripMargin) 418 | 419 | val code = ZipBuilder.mkBase64Zip(srcs) 420 | 421 | withRuby25Container { c => 422 | c.init(initPayload(code, main = "niam"))._1 should be(200) 423 | 424 | val (runCode, runRes) = c.run(runPayload(JsObject())) 425 | runRes.get.fields.get("result") shouldBe Some(JsString("it works")) 426 | } 427 | } 428 | 429 | it should "support return array result" in { 430 | val (out, err) = withRuby25Container { c => 431 | val code = """ 432 | | def main(args) 433 | | nums = Array["a","b"] 434 | | nums 435 | | end 436 | """.stripMargin 437 | 438 | val (initCode, _) = c.init(initPayload(code)) 439 | 440 | initCode should be(200) 441 | 442 | val (runCode, runRes) = c.runForJsArray(runPayload(JsObject())) 443 | runCode should be(200) 444 | runRes shouldBe Some(JsArray(JsString("a"), JsString("b"))) 445 | } 446 | } 447 | 448 | it should "support array as input param" in { 449 | val (out, err) = withRuby25Container { c => 450 | val code = """ 451 | | def main(args) 452 | | args 453 | | end 454 | """.stripMargin 455 | 456 | val (initCode, _) = c.init(initPayload(code)) 457 | 458 | initCode should be(200) 459 | 460 | val (runCode, runRes) = c.runForJsArray(runPayload(JsArray(JsString("a"), JsString("b")))) 461 | runCode should be(200) 462 | runRes shouldBe Some(JsArray(JsString("a"), JsString("b"))) 463 | } 464 | } 465 | } 466 | --------------------------------------------------------------------------------