The action-golang-v1.14
runtime can execute actions written in the Go programming language in OpenWhisk, either precompiled binary or compiling sources on the fly.
The source of one action is one or more Go source file. The entry point of the action is a function, placed in the main
package. The obvious name for the default action would be main
, but unfortunately main.main
is the fixed entry point, for a go program, and its signature is main()
(without arguments) so it cannot be used, unless you implement the ActionLoop directly, overwriting the one provided by the runtime, see below.
When deploying an OpenWhisk action you can specify the main
function, and the default value is of course main
.
The rule used by the runtime you use the the capitalized name of the function specified as main. The default is of course main.Main
' if you specify hello
it will be hello.Hello
. It will be also main.Main
or hello.Hello
if you specify the main function as, respectively, Main
and Hello
. The function must have a specific signature, as described next.
NOTE The runtime does not support different packages from main
for the entry point. If you specify hello.main
the runtime will try to use Hello.main
, that will be almost certainly incorrect. You can however have other packages in your sources, as described below.
The expected signature for a main
function is:
func Main(event map[string]interface{}) map[string]interface{}
So a very simple hello world
function would be:
package main
import "log"
// Main is the function implementing the action
func Main(obj map[string]interface{}) map[string]interface{} {
// do your work
name, ok := obj["name"].(string)
if !ok {
name = "world"
}
msg := make(map[string]interface{})
msg["message"] = "Hello, " + name + "!"
// log in stdout or in stderr
log.Printf("name=%s\n", name)
// encode the result back in json
return msg
}
You can also have multiple source files in an action, packages and vendor folders. Check the deployment document for more details how to package and deploy actions.
The actionloop
runtime can execute generic Linux executable in an efficient way. The actions should work reading input line by line, perform its work and produce output also line by line. In more detail it should respect the following protocol.
The protocol can be specified informally as follows.
- Send an acknowledgement after initialization when required. If the environment variable
__OW_WAIT_FOR_ACK
is not empty, write on file descriptor 3 the string{ "ok": true }
. - Read one line from standard input (file descriptor 0).
- Parse the line as a JSON object. Currently the object will be in currently in the format:
{
"value": JSON,
"namespace": String,
"action_name": String,
"api_host": String,
"api_key": String,
"activation_id": String,
"deadline": Number
}
Note however that more values could be provided in future.
Usually this JSON is read and the values are stored in environment variables, converted to upper case the key and and adding the prefix __OW_
.
- The payload of the request is stored in the key
value
. The action should read the fieldvalue
assuming it is a JSON object (note, not an array, nor a string or number) and parse it. - The action can now perform its tasks as appropriate. The action can produce log writing in standard output (file descriptor 1) and standard error (file descriptor 3) . Note that those corresponds to file descriptors 1 and 2.
- The action will receive also file descriptor 3 for returning results. The result of the action must be a single line (without embedding newlines - newlines in strings must be quoted) written in file descriptor 3.
- The action should not exit now, but continue the loop, reading the next line and processing as described before, continuing forever.
The actionloop
image works actually with executable in Linux sense, so also scripts are acceptable.
In the current actionloop image there is bash
and the jq
command, so you can for example implement the actionloop with a shell script like this:
#!/bin/bash
# send an ack if required
if test -n "$__OW_WAIT_FOR_ACK"
then echo '{"ok":true}' >&3
fi
# read input forever line by line
while read line
do
# parse the in input with `jq`
name="$(echo $line | jq -r .name.value)"
# log in stdout
echo msg="hello $name"
# produce the result - note the fd3
echo '{"hello": "'$name'"}' >&3
done
Note here we are just interested in the payload, but in general you may also want to retrieve other fields.
Note the actionloop
image will accept any source and will try to run it (if it is possible), while the action-golang-v1.N
will instead try to compile the sources assuming it is Golang instead.
By default the runtime expects you provide a main function that will serve one request, and will add a default implementation of the ActionLoop protocol when compiling.
You can however overwrite the default protocol and provide your how implementation of the ActionLoop. If you do so, you will have to take care of opening file descriptors, reading input, parse JSON and set environment variables.
To overwrite the default ActionLoop you can do this either sending a single file source action, or a zip action.
If you send a single file, you have to provide your own implementation adding a function func main()
in the main
package.
If you send a zip file, you have to provide your implementation in a file called exec
(without extension .go
!) placed in the top level of the zip file.
If you provide your own main.main()
, the default main
will not be generated.
An example named standalone
is provided.