Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

Add ballerina runtime support #788

Merged
merged 10 commits into from
Jun 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ artifacts/
*.nuget.props
*.nuget.targets

# IDEA Files
.idea/
*.iml
*.ipr
*.iws

# Kubeless specific
bats/
Expand Down
11 changes: 11 additions & 0 deletions docker/runtime/ballerina/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM ballerina/ballerina-platform:0.970.1

USER 1000

WORKDIR /kubeless

ENV FUNC_PROCESS="ballerina run kubeless_run.balx -c kubeless.toml"

ADD proxy /

CMD [ "/proxy" ]
9 changes: 9 additions & 0 deletions docker/runtime/ballerina/Dockerfile.init
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM ballerina/ballerina-platform:0.970.1

USER 1000

# Install controller & Kubeless type definitions
ADD kubeless_run.tpl.bal /ballerina/files/src/
ADD kubeless/kubeless.bal /ballerina/files/kubeless/

ENTRYPOINT [""]
16 changes: 16 additions & 0 deletions docker/runtime/ballerina/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
init-image:
docker build -f Dockerfile.init -t kubeless/ballerina-init:0.970.1 .

runtime-image:
env GOOS=linux GOARCH=amd64 go build $$GOPATH/src/github.com/kubeless/kubeless/pkg/function-proxy/proxy.go
docker build -f Dockerfile -t kubeless/ballerina:0.970.1 .

push-init:
docker push kubeless/ballerina-init:0.970.1

push-runtime:
docker push kubeless/ballerina:0.970.1

# Mandatory jobs
build-all: init-image runtime-image
push-all: push-init push-runtime
8 changes: 8 additions & 0 deletions docker/runtime/ballerina/example/foo.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import kubeless;
import ballerina/io;

public function foo(kubeless:Event event, kubeless:Context context) returns (string|error) {
io:println(event);
io:println(context);
return "bar";
}
15 changes: 15 additions & 0 deletions docker/runtime/ballerina/kubeless/kubeless.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
public type Event{
string data;
string event_id;
string event_type;
string event_time;
string event_namespace;
map extensions;
};

public type Context{
string function_name;
string time_out;
string runtime;
string memory_limit;
};
116 changes: 116 additions & 0 deletions docker/runtime/ballerina/kubeless_run.tpl.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright (c) 2017-2018 Bitnami

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import ballerina/http;
import ballerina/io;
import ballerina/config;
import func;
import kubeless;

// A service endpoint represents a listener
endpoint http:Listener listener {
//Listner port is 8090 redirected by proxy
port: 8090
};

@http:ServiceConfig {
basePath: "/",
compression: "NEVER"
}

service<http:Service> controller bind listener {
//Initialize context from environment variables
kubeless:Context context = {
function_name: config:getAsString("FUNC_HANDLER"),
time_out: config:getAsString("FUNC_TIMEOUT"),
runtime: config:getAsString("FUNC_RUNTIME"),
memory_limit: config:getAsString("FUNC_MEMORY_LIMIT")
};

@http:ResourceConfig {
methods: ["GET", "POST", "DELETE", "PATCH", "PUT"],
path: "/"
}
handler(endpoint caller, http:Request request) {
kubeless:Event event = {};
//Read requests and set them to event
if (request.hasHeader("Content-Length")){
int|error result = <int>request.getHeader("Content-Length");
match result {
int legnth => {
if (legnth > 0){
match request.getPayloadAsString() {
string payload => {
event.data = payload;
}
error e => {
io:println("Error while extracting payload: " + e.message);
}
}
}
}
error e => {
io:println("Error while reading header: " + e.message);
}
}
}
//Read headers and set event info.
if (request.hasHeader("event-id")){
event.event_id = request.getHeader("event-id");
}
if (request.hasHeader("event-type")){
event.event_type = request.getHeader("event-type");
}
if (request.hasHeader("event-time")){
event.event_time = request.getHeader("event-time");
}
if (request.hasHeader("event-namespace")){
event.event_namespace = request.getHeader("event-namespace");
}
// Create object to carry data back to caller
http:Response response = new;

// Invoke function and set the response payload.
string response_payload;
match func:<<FUNCTION>>(event, context) {
string return_value => {
response_payload = return_value;
}
error e => {
response_payload = e.message;
}
}
response.setPayload(response_payload);
// Send a response back to caller
// Errors are ignored with '_'
// -> indicates a synchronous network-bound call
_ = caller->respond(response);
}

// Health check resource
@http:ResourceConfig {
methods: ["GET", "POST"],
path: "/healthz"
}
health(endpoint caller, http:Request request) {

http:Response response = new;
response.statusCode = http:OK_200;

// Send a response back to caller
// Errors are ignored with '_'
// -> indicates a synchronous network-bound call
_ = caller->respond(response);
}
}
49 changes: 48 additions & 1 deletion docs/runtimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ By default Kubeless has support for the following runtimes:
- PHP: For the branch 7.2
- Golang: For the branch 1.10
- .NET: For the branch 2.0
- Ballerina: For the branch 0.970.1

You can see the list of supported runtimes executing:

```console
$ kubeless get-server-config
INFO[0000] Current Server Config:
INFO[0000] Supported Runtimes are: python2.7, python3.4, python3.6, nodejs6, nodejs8, ruby2.4, php7.2, go1.10
INFO[0000] Supported Runtimes are: python2.7, python3.4, python3.6, nodejs6, nodejs8, ruby2.4, php7.2, go1.10, dotnetcore2.0, java1.8, ballerina0.970.1
```

Each runtime is encapsulated in a container image. The reference to these images are injected in the Kubeless configuration. You can find the source code of all runtimes in [`docker/runtime`](https://github.com/kubeless/kubeless/tree/master/docker/runtime).
Expand Down Expand Up @@ -348,6 +349,52 @@ You can deploy them using the command:
kubeless function deploy fibonacci --from-file fibonacci.cs --handler module.handler --dependencies fibonacci.csproj --runtime dotnetcore2.0
```

### Ballerina

#### Example

```ballerina
import kubeless;
import ballerina/io;

public function foo(kubeless:Event event, kubeless:Context context) returns (string|error) {
io:println(event);
io:println(context);
return "Hello Ballerina";
}

```

#### Description

The Ballerina functions should import the package `kubeless`. This [package](../docker/runtime/ballerina/kubeless/kubeless.bal) contains two types `Event` and `Context`.

```console
$ kubeless function deploy foo
--runtime ballerina0.970.1
--from-file foo.bal
--handler foo.foo
```

When using the Ballerina runtime, it is possible to provide a configuration via `kubeless.toml` file. The values in kubeless.toml file are available for the function. The function(.bal file) and conf file should be in the same directory.
The zip file containing both files should be passed to the Kubeless CLI.
```console
foo
├── hellowithconf.bal
└── kubeless.toml

$ zip -r -j foo.zip foo/

$ kubeless function deploy get-ballerina-conf
--runtime ballerina0.970.1
--from-file foo.zip
--handler hellowithconf.foo
```

#### Server implementation

For the Ballerina runtime we start a [Ballerina HTTP server](../docker/runtime/ballerina/kubeless_run.tpl.bal) with two resources, '/' and '/healthz'.

## Use a custom runtime

The Kubeless configuration defines a set of default container images per supported runtime variant.
Expand Down
27 changes: 27 additions & 0 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -798,3 +798,30 @@ get-nodejs-distroless:

get-nodejs-distroless-verify:
kubeless function call get-nodejs-distroless |egrep hello.world

get-ballerina:
kubeless function deploy get-ballerina --runtime ballerina0.970.1 --from-file ballerina/helloget.bal --handler helloget.foo

get-ballerina-verify:
kubeless function call get-ballerina |egrep Hello.World.Ballerina

get-ballerina-custom-port:
kubeless function deploy get-ballerina-custom-port --runtime ballerina0.970.1 --handler helloget.foo --from-file ballerina/helloget.bal --port 8083

get-ballerina-custom-port-verify:
kubectl get svc get-ballerina-custom-port -o yaml | grep 'targetPort: 8083'
kubeless function call get-ballerina-custom-port |egrep Hello.World.Ballerina

get-ballerina-data:
kubeless function deploy get-ballerina-data --runtime ballerina0.970.1 --from-file ballerina/hellowithdata.bal --handler hellowithdata.foo

get-ballerina-data-verify:
kubeless function call get-ballerina-data --data '{"hello":"world"}' |egrep hello

get-ballerina-conf:
zip -r -j ballerina/bar.zip ballerina/hello_with_conf/
kubeless function deploy get-ballerina-conf --runtime ballerina0.970.1 --from-file ballerina/bar.zip --handler hello_with_conf.bar
rm ballerina/bar.zip

get-ballerina-conf-verify:
kubeless function call get-ballerina-conf | egrep john
9 changes: 9 additions & 0 deletions examples/ballerina/hello_with_conf/hello_with_conf.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import kubeless;
import ballerina/io;
import ballerina/config;

public function bar(kubeless:Event event, kubeless:Context context) returns (string|error) {
io:println(event);
io:println(context);
return config:getAsString("hello.userid");
}
2 changes: 2 additions & 0 deletions examples/ballerina/hello_with_conf/kubeless.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[hello]
userid="john@ballerina.com"
5 changes: 5 additions & 0 deletions examples/ballerina/helloget.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import kubeless;

public function foo(kubeless:Event event, kubeless:Context context) returns (string|error) {
return "Hello World Ballerina";
}
8 changes: 8 additions & 0 deletions examples/ballerina/hellowithdata.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import kubeless;
import ballerina/io;

public function foo(kubeless:Event event, kubeless:Context context) returns (string|error) {
io:println(event);
io:println(context);
return <string>event.data;
}
14 changes: 14 additions & 0 deletions kubeless-non-rbac.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,20 @@ local runtime_images ='[
],
"depName": "pom.xml",
"fileNameSuffix": ".java"
},
{
"ID": "ballerina",
"compiled": true,
"versions": [
{
"name": "ballerina0.970.1",
"version": "0.970.1",
"runtimeImage": "kubeless/ballerina@sha256:b1f4f0cb65258d3035ae9eec2ae37a7c0d7e4eac6b150da5fb7933668114a409",
"initImage": "kubeless/ballerina-init@sha256:5acecccb9d05d298606c8514ee16b25bab76e4a2f35eda30601bb7f229e01cfd"
}
],
"depName": "",
"fileNameSuffix": ".bal"
}
]';

Expand Down
13 changes: 13 additions & 0 deletions pkg/langruntime/langruntime.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ func (l *Langruntimes) GetBuildContainer(runtime, depsChecksum string, env []v1.
case strings.Contains(runtime, "java"):
command = appendToCommand(command,
"mv /kubeless/pom.xml /kubeless/function-pom.xml")
case strings.Contains(runtime, "ballerina"):
return v1.Container{}, fmt.Errorf("Ballerina does not require a dependencies file")
}

return v1.Container{
Expand Down Expand Up @@ -311,6 +313,17 @@ func (l *Langruntimes) GetCompilationContainer(runtime, funcName string, install
"mvn package > /dev/termination-log 2>&1 && mvn install > /dev/termination-log 2>&1"
case strings.Contains(runtime, "dotnetcore"):
command = "/app/compile-function.sh " + installVolume.MountPath
case strings.Contains(runtime, "ballerina"):
command = fmt.Sprintf(
"mkdir -p /kubeless/kubeless/ /kubeless/func/ && "+
"cp -r /ballerina/files/kubeless/*.bal /kubeless/kubeless/ && "+
"cp -r /kubeless/*.bal /kubeless/func/ && "+
"touch /kubeless/kubeless.toml && "+
"cp -r /ballerina/files/src/kubeless_run.tpl.bal /kubeless/ && "+
"sed 's/<<FUNCTION>>/%s/g' /kubeless/kubeless_run.tpl.bal > /kubeless/kubeless_run.bal && "+
"rm /kubeless/kubeless_run.tpl.bal && "+
"ballerina build kubeless_run.bal ", funcName)

default:
return v1.Container{}, fmt.Errorf("Not found a valid compilation step for %s", runtime)
}
Expand Down
Loading