Compare commits
101
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4fe007838 | ||
|
|
0fd60eab3b | ||
|
|
ae87c0ea37 | ||
|
|
55651d49e0 | ||
|
|
1f0ca47550 | ||
|
|
e619a70661 | ||
|
|
d248bb89ea | ||
|
|
5fe4b276eb | ||
|
|
8f1b27bd72 | ||
|
|
ce4d3ac42d | ||
|
|
3328014ee6 | ||
|
|
b1e66663bd | ||
|
|
c1dd8a9f35 | ||
|
|
d9b98830cf | ||
|
|
a8e7ec534b | ||
|
|
0207d13b23 | ||
|
|
93ef6fac53 | ||
|
|
c43cde8891 | ||
|
|
62aa9d6d73 | ||
|
|
cde2f26a9d | ||
|
|
75d65adcc0 | ||
|
|
2f6cd6d659 | ||
|
|
990d943f21 | ||
|
|
531f69a037 | ||
|
|
3ea9ad2449 | ||
|
|
bc61396071 | ||
|
|
6185e842b5 | ||
|
|
a8c6347ed0 | ||
|
|
e5f5050ea6 | ||
|
|
03e3a63581 | ||
|
|
747a3d2197 | ||
|
|
9bd21d2380 | ||
|
|
a15c867812 | ||
|
|
e1067ab4e5 | ||
|
|
d2b7dc36ab | ||
|
|
1abe961869 | ||
|
|
01b399187e | ||
|
|
b4dd7a83df | ||
|
|
137d8f7285 | ||
|
|
c9f63e8173 | ||
|
|
a71756762b | ||
|
|
a6eb4c6d31 | ||
|
|
a13015e75a | ||
|
|
1665235c14 | ||
|
|
a3f8016442 | ||
|
|
a390c1baa8 | ||
|
|
b5b10a4a27 | ||
|
|
aa77d8686f | ||
|
|
68c006cd33 | ||
|
|
b561f7d948 | ||
|
|
9bdaf3c74e | ||
|
|
e1cb5f6e09 | ||
|
|
a6869ab746 | ||
|
|
365eabb027 | ||
|
|
ad63ba63c8 | ||
|
|
6af605bfe8 | ||
|
|
4d16a4be08 | ||
|
|
6e5b1cf8db | ||
|
|
ca3bf8e83e | ||
|
|
92cd5334ec | ||
|
|
f4b6558e79 | ||
|
|
3b75a86773 | ||
|
|
8cf382c2b3 | ||
|
|
ab6e2e8021 | ||
|
|
eae5150bff | ||
|
|
d4dc166039 | ||
|
|
41baa7b6bf | ||
|
|
6e4115cb5d | ||
|
|
9e5db82cc9 | ||
|
|
6be861afc8 | ||
|
|
430639a1cf | ||
|
|
c8b5bb2972 | ||
|
|
99d6a95bc1 | ||
|
|
14c55912f5 | ||
|
|
858fe9f62b | ||
|
|
25afe7bd78 | ||
|
|
3420a614bb | ||
|
|
d4d917ad04 | ||
|
|
d0ad377d72 | ||
|
|
4046c811b1 | ||
|
|
8127a1f57f | ||
|
|
20cac31635 | ||
|
|
fd07a8a75e | ||
|
|
0d4d2e32a8 | ||
|
|
436057e15b | ||
|
|
33f7647ea7 | ||
|
|
a1d255f038 | ||
|
|
c9ea73786b | ||
|
|
76d4ea7b04 | ||
|
|
2725da6b1b | ||
|
|
c353e7f230 | ||
|
|
7b58ef389d | ||
|
|
9db35b6ff2 | ||
|
|
1cc1fac151 | ||
|
|
39fb45d8da | ||
|
|
1b04f042cd | ||
|
|
7485dca7d8 | ||
|
|
20baa2ee9d | ||
|
|
2fc9fd9603 | ||
|
|
760e021141 | ||
|
|
f5270d97b1 |
+10
-2
@@ -1,4 +1,12 @@
|
|||||||
*~
|
fission-bundle/fission-bundle
|
||||||
|
|
||||||
# Pycharm IDE
|
# Pycharm IDE
|
||||||
.idea
|
.idea
|
||||||
|
environments/php7/vendor/
|
||||||
|
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*~
|
||||||
|
|
||||||
|
vendor/
|
||||||
|
|||||||
+3
-3
@@ -3,7 +3,7 @@ dist: trusty
|
|||||||
|
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.7
|
- 1.8
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
install:
|
install:
|
||||||
@@ -16,5 +16,5 @@ script:
|
|||||||
- ./fission-bundle/build.sh
|
- ./fission-bundle/build.sh
|
||||||
- hack/verify-gofmt.sh
|
- hack/verify-gofmt.sh
|
||||||
- /tmp/test-etcd/etcd &
|
- /tmp/test-etcd/etcd &
|
||||||
- go test -v -i $(go list ./... | grep -v '/vendor/')
|
- go test -v -i $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go')
|
||||||
- go test -v $(go list ./... | grep -v '/vendor/')
|
- go test -v $(go list ./... | grep -v '/vendor/' | grep -v 'examples/go')
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
Compiling Fission
|
||||||
|
=================
|
||||||
|
|
||||||
|
[You only need to do this if you're making Fission changes; if you're
|
||||||
|
just deploying Fission, use fission.yaml which points to prebuilt
|
||||||
|
images.]
|
||||||
|
|
||||||
|
You'll need the `go` compiler and tools installed, along with the
|
||||||
|
[glide dependency management
|
||||||
|
tool](https://github.com/Masterminds/glide#install). You'll also need
|
||||||
|
docker for building images.
|
||||||
|
|
||||||
|
The server side is compiled as one binary ("fission-bundle") which
|
||||||
|
contains controller, poolmgr and router; it invokes the right one
|
||||||
|
based on command-line arguments.
|
||||||
|
|
||||||
|
To build fission-bundle: clone this repo to
|
||||||
|
`$GOPATH/src/github.com/fission/fission`, then from the top level
|
||||||
|
directory (if you want to build the image with the docker inside
|
||||||
|
minikube, you'll need to set the proper environment variables with
|
||||||
|
`eval $(minikube docker-env)`):
|
||||||
|
|
||||||
|
```
|
||||||
|
# Get dependencies
|
||||||
|
$ glide install
|
||||||
|
|
||||||
|
# Build fission server and an image
|
||||||
|
$ pushd fission-bundle
|
||||||
|
$ ./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
You now need to build the docker image for fission. You can use
|
||||||
|
`push.sh` and push it to a docker hub account. But it's easiest to use
|
||||||
|
minikube and its built-in docker daemon:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ eval $(minikube docker-env)
|
||||||
|
$ docker build -t minikube/fission-bundle .
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, install fission on your kubernetes cluster:
|
||||||
|
|
||||||
|
```
|
||||||
|
# To install, update fission.yaml to point to the image you just made: "minikube/fission-bundle"
|
||||||
|
$ $EDITOR fission.yaml
|
||||||
|
$ kubectl create -f fission.yaml
|
||||||
|
$ kubectl create -f fission-nodeport.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
And if you're changing the CLI too, you can build it with:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Build Fission CLI
|
||||||
|
$ cd fission && go install
|
||||||
|
```
|
||||||
@@ -95,6 +95,19 @@ While a few simple retries are done, there isn't yet a reliable
|
|||||||
message bus between Kubewatcher and the function. Work for this is
|
message bus between Kubewatcher and the function. Work for this is
|
||||||
tracked in issue #64.
|
tracked in issue #64.
|
||||||
|
|
||||||
|
Message Queue Trigger
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
A message queue trigger binds a message queue topic to a function:
|
||||||
|
events from that topic cause the function to be invoked with the
|
||||||
|
message as the body of the request. The trigger may also contain a
|
||||||
|
response topic: if specified, the function's output is sent to this
|
||||||
|
response.
|
||||||
|
|
||||||
|
Here's a diagram of the components:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Environment Container
|
Environment Container
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
@@ -111,3 +124,21 @@ When poolmgr needs to create a service for a function, it calls
|
|||||||
fetcher to fetch the function. Fetcher downloads the function into a
|
fetcher to fetch the function. Fetcher downloads the function into a
|
||||||
volume shared between fetcher and this environment container. Poolmgr
|
volume shared between fetcher and this environment container. Poolmgr
|
||||||
then requests the container to load the function.
|
then requests the container to load the function.
|
||||||
|
|
||||||
|
Logger
|
||||||
|
------
|
||||||
|
|
||||||
|
Logger helps to forward function logs to centralized db service for log
|
||||||
|
persistence. Currently only influxdb is supported to store logs.
|
||||||
|
Following is a diagram describe how log service works:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
1. Pool manager choose a pod from pool to execute user function
|
||||||
|
2. Pool manager makes a HTTP POST to logger helper, once helper receives
|
||||||
|
the request it creates a symlink to container log for fluentd.
|
||||||
|
3. Fluentd reads log from symlink and pipes to influxdb
|
||||||
|
4. `fission function logs ...` retrieve event logs from influxdb with
|
||||||
|
optional log filter
|
||||||
|
5. Pool manager removes function pod from pool
|
||||||
|
6. Pool manager asks logger helper to stop piping logs, logger removes symlink.
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# Fission Roadmap
|
||||||
|
|
||||||
|
## Function features ([area-func](https://github.com/fission/fission/labels/area-func))
|
||||||
|
|
||||||
|
- Secrets, configmaps, env vars
|
||||||
|
- Volumes
|
||||||
|
- Mem requests and limits
|
||||||
|
- Function exec time deadline
|
||||||
|
- Expose a regular K8s Service for a function
|
||||||
|
|
||||||
|
## Fission API ([area-api](https://github.com/fission/fission/labels/area-api))
|
||||||
|
|
||||||
|
- TPR-based controller
|
||||||
|
- API authentication
|
||||||
|
- Aggregated API server
|
||||||
|
|
||||||
|
## Development Workflows ([area-dev](https://github.com/fission/fission/labels/area-dev))
|
||||||
|
|
||||||
|
- Function Versioning
|
||||||
|
- Versioning for a group of functions
|
||||||
|
- Rolling upgrades
|
||||||
|
- for one function
|
||||||
|
- for multiple functions
|
||||||
|
- for functions + other kubernetes deployments
|
||||||
|
- Unit testing
|
||||||
|
|
||||||
|
## API Gateway / Ingress features ([area-ingress](https://github.com/fission/fission/labels/area-ingress))
|
||||||
|
|
||||||
|
- Function authn hooks
|
||||||
|
- K8s Ingress flag
|
||||||
|
- Bundle an ingress controller?
|
||||||
|
|
||||||
|
## Workflows and Function composition ([area-composition](https://github.com/fission/fission/labels/area-composition))
|
||||||
|
|
||||||
|
- Simple Composition - Sync
|
||||||
|
- Simple Composition - Async
|
||||||
|
- Hooks (pre, post, on-error)
|
||||||
|
- Workflows
|
||||||
|
- Testing in the presence of function composition
|
||||||
|
|
||||||
|
## Events ([area-events](https://github.com/fission/fission/labels/area-events))
|
||||||
|
|
||||||
|
- NATS
|
||||||
|
- Kafka
|
||||||
|
- AWS: SNS, SQS
|
||||||
|
- Google PubSub
|
||||||
|
- RabbitMQ
|
||||||
|
- Other event sources
|
||||||
|
|
||||||
|
- Bundle an event queue (NATS streaming, most probably)
|
||||||
|
|
||||||
|
## Operability ([area-ops](https://github.com/fission/fission/labels/area-ops))
|
||||||
|
|
||||||
|
### Fission Install/Upgrade ([area-install](https://github.com/fission/fission/labels/area-install))
|
||||||
|
|
||||||
|
- Helm Installer for Fission
|
||||||
|
- Helm installation for fission functions
|
||||||
|
- CLI installer/upgrader? ("fission upgrade")
|
||||||
|
- Upgrade checker/reminder (like minikube does)
|
||||||
|
- CLI auto-upgrader
|
||||||
|
|
||||||
|
### Function observation ([area-observe](https://github.com/fission/fission/labels/area-observe))
|
||||||
|
|
||||||
|
- Function Logging
|
||||||
|
- Tracing -- Opentracing
|
||||||
|
- Metrics -- Prometheus
|
||||||
|
- Function exception tracking
|
||||||
|
- Logging function load errors
|
||||||
|
- Tracing fission overheads for each function
|
||||||
|
|
||||||
|
## Function Security
|
||||||
|
|
||||||
|
- Function isolation (is authz hook sufficient or do we need something like mutual TLS?)
|
||||||
|
- Function service accounts
|
||||||
|
|
||||||
|
## UX, especially for beginners ([area-ux](https://github.com/fission/fission/labels/area-ux))
|
||||||
|
|
||||||
|
- Fission CLI should include a tutorial
|
||||||
|
- Fission CLI should have a way to drop you into the UI
|
||||||
|
- Eliminate FISSION_URL, just use kube client to find fission url. Also useful to grab credentials.
|
||||||
|
|
||||||
|
## Documentation ([area-doc](https://github.com/fission/fission/labels/area-doc))
|
||||||
|
|
||||||
|
- Installation guide improvements
|
||||||
|
- Troubleshooting guide for common problems
|
||||||
|
- FAQ
|
||||||
|
- Performance overview
|
||||||
|
- Render docs nicely to fission.io
|
||||||
|
|
||||||
|
## Web UI (tracked separately in the fission-ui repo)
|
||||||
|
|
||||||
|
## Performance and Scalability ([area-perf](https://github.com/fission/fission/labels/area-perf))
|
||||||
|
|
||||||
|
- Autoscaling
|
||||||
|
- Cold-start optimization -- optimistically choose from pool, save about ~20msec
|
||||||
|
- Cold-start optimization -- preload funcs in fetcher
|
||||||
|
- Cold-start optimization -- preload libraries in envs (v2) -- mem vs. speed tradeoff
|
||||||
|
|
||||||
|
## Function extensibility ([area-ext](https://github.com/fission/fission/labels/area-ext))
|
||||||
|
|
||||||
|
- Env v2: easy addition of dependencies etc.
|
||||||
|
- Integration with Service Broker
|
||||||
|
|
||||||
|
## Multi-area stuff
|
||||||
|
|
||||||
|
- Execution strategies: cold-start pool vs create-pod-on-cold-start -- one size doesn't fit all, at least with current tech; abstract over execution strategies according to requirements
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# Environment V2 Fission-Environment API
|
||||||
|
|
||||||
|
Fission Environments are the language-specific component of fission.
|
||||||
|
|
||||||
|
They must satisfy the interface in this spec.
|
||||||
|
|
||||||
|
## Meta
|
||||||
|
|
||||||
|
This is version 2.0-alpha of the Fission-Environment API.
|
||||||
|
|
||||||
|
(It's unstable and may change without warning until 2.0-beta.)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Fission V2 Environments consist of:
|
||||||
|
|
||||||
|
* Metadata
|
||||||
|
* A runtime image
|
||||||
|
* A builder image (optional)
|
||||||
|
* Function Interface Specification
|
||||||
|
* User Documentation
|
||||||
|
* Examples
|
||||||
|
|
||||||
|
### Metadata
|
||||||
|
|
||||||
|
See EnvironmentSpec, Runtime, and Builder in types.go.
|
||||||
|
|
||||||
|
## Runtime Image
|
||||||
|
|
||||||
|
An environment runtime image is a docker container image. It must run
|
||||||
|
a server that has two jobs:
|
||||||
|
|
||||||
|
(a) Loading a "function" from a file path on demand
|
||||||
|
(b) Invoking that function on request
|
||||||
|
|
||||||
|
### Function Loading
|
||||||
|
|
||||||
|
The environment must expose a single HTTP endpoint (at the port and
|
||||||
|
URL specified in the metadata) that loads a function. The function
|
||||||
|
load request is a JSON-serialized `FunctionLoadRequest`.
|
||||||
|
|
||||||
|
The function load request contains a filepath to load the function
|
||||||
|
from. Fission does not define in any way the contents of the path; it
|
||||||
|
is completely environment-dependent. It may be a single file, or a
|
||||||
|
directory (in case of deployment packages).
|
||||||
|
|
||||||
|
The load request may contain an EntryPoint. If it does, the loader
|
||||||
|
must interpret this; usually it's the name of a function in a module
|
||||||
|
or package containing multiple functions. If there is no entrypoint,
|
||||||
|
the environment must use a default; again, the value of thsi default
|
||||||
|
is environment-specific.
|
||||||
|
|
||||||
|
The load request may contain a URL. If it does, requests to that URL
|
||||||
|
should be routed to the function. It defaults to "/".
|
||||||
|
|
||||||
|
### Function Invocation
|
||||||
|
|
||||||
|
Functions are invoked on HTTP request to the server. The port for the
|
||||||
|
request on the runtime container is defined in the Runtime metadata,
|
||||||
|
and the URL for the request is specified in the FunctionLoadRequest.
|
||||||
|
|
||||||
|
The interface of the function is environment specific; the environment
|
||||||
|
must come with a spec for this interface.
|
||||||
|
|
||||||
|
## Builder
|
||||||
|
|
||||||
|
The builder is a container image that contains tools to build a
|
||||||
|
function from source. The source may be a single file or a directory
|
||||||
|
of files.
|
||||||
|
|
||||||
|
The builder container is invoked with the specified command, with the
|
||||||
|
following params:
|
||||||
|
|
||||||
|
1. File path of the source
|
||||||
|
2. File path where the output should go
|
||||||
|
3. Other env or function-specific params passed by the user
|
||||||
|
|
||||||
|
The first two parameters are file paths, and all remaining params are
|
||||||
|
environment-specific.
|
||||||
|
|
||||||
|
The output of the builder should be something that the runtime can
|
||||||
|
load and run -- there should be no intermediate steps that need user
|
||||||
|
intervention.
|
||||||
|
|
||||||
|
### Errors
|
||||||
|
|
||||||
|
## Function Interface Spec
|
||||||
|
|
||||||
|
The function interface spec is a document that specifies the interface
|
||||||
|
of functions and their semantics. It must specify:
|
||||||
|
|
||||||
|
* How functions are invoked (sync, async)
|
||||||
|
* How the request context is provided to the function (URL, headers, request type, request body)
|
||||||
|
* Function logging
|
||||||
|
* Semantics of function errors and exceptions
|
||||||
|
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
The docs should contain everything necessary to use the environment:
|
||||||
|
|
||||||
|
* How to add it to a fission cluster
|
||||||
|
* How to write and build functions for this environment (link to the interface spec)
|
||||||
|
* How to modify and rebuild the environment itself
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Suggested examples to provide:
|
||||||
|
|
||||||
|
* A simple "Hello world"
|
||||||
|
* A function that demonstrates use of the request context: url params,
|
||||||
|
request headers, request body
|
||||||
|
* A function that does logging
|
||||||
|
* A multi-file function package
|
||||||
|
* A function with dependencies
|
||||||
|
* Functions with shared code
|
||||||
|
|
||||||
|
## Compatibility with v1
|
||||||
|
|
||||||
|
V1 environment images can be used as v2 environment runtime images.
|
||||||
@@ -37,6 +37,11 @@ container.
|
|||||||
annoying to wait until runtime to see a syntax error that could have
|
annoying to wait until runtime to see a syntax error that could have
|
||||||
been caught on function upload.
|
been caught on function upload.
|
||||||
|
|
||||||
|
* Starting a Pod without knowing the functions has its limitations: we
|
||||||
|
can't set CPU/memory limits, we can't mount volumes (persistent
|
||||||
|
volumes, secrets, configmaps). We also can't change the namespace
|
||||||
|
the Pod is in.
|
||||||
|
|
||||||
* Not great for a large code base
|
* Not great for a large code base
|
||||||
|
|
||||||
* Some people want to operate at the image level but still get the
|
* Some people want to operate at the image level but still get the
|
||||||
@@ -97,71 +102,226 @@ Roughly in order of priority:
|
|||||||
|
|
||||||
### User stories
|
### User stories
|
||||||
|
|
||||||
#### Compiled language
|
#### Environment Creation
|
||||||
|
|
||||||
User writes a function in Go.
|
V1 Environments were just an image. V2 Environments will be a yaml
|
||||||
|
file with the following properties:
|
||||||
|
|
||||||
|
* Run time image (required)
|
||||||
|
* Version (required)
|
||||||
|
* Builder image (optional)
|
||||||
|
* Build invocation command (required if builder image specified)
|
||||||
|
* File name extension(s) (optional)
|
||||||
|
|
||||||
|
The version will be used to distinguish V2 environments from V1.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ fission function create --code blah.go
|
$ cat golang.yaml
|
||||||
|
type: Environment
|
||||||
|
metadata:
|
||||||
|
name: go
|
||||||
|
spec:
|
||||||
|
runtimeImage: fission/go-runtime
|
||||||
|
builderImage: fission/go-builder
|
||||||
|
buildCommand:
|
||||||
|
- "/build.sh"
|
||||||
|
fileExtentions:
|
||||||
|
- go
|
||||||
|
|
||||||
<compilation errors>
|
$ fission env create -f golang.yaml
|
||||||
|
|
||||||
<user edits file>
|
|
||||||
$ $EDITOR blah.go
|
|
||||||
<fixes errors>
|
|
||||||
|
|
||||||
$ fission function update --code blah.go
|
|
||||||
<success>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
(Or perhaps we could have a `fission function check --env x --code y`
|
#### Function creation for compiled languages
|
||||||
which just does compilation, without creating a function object?
|
|
||||||
Useful for integration into IDEs. Basically, just an on-demand
|
User writes a function in a compiled language, for example Go.
|
||||||
builder. Useful when you don't wanna setup anything on your laptop.)
|
|
||||||
|
```
|
||||||
|
$ fission function create --code blah.go
|
||||||
|
|
||||||
|
<compilation errors>
|
||||||
|
|
||||||
|
<user edits file>
|
||||||
|
$ $EDITOR blah.go
|
||||||
|
<fixes errors>
|
||||||
|
|
||||||
|
$ fission function update --code blah.go
|
||||||
|
<success>
|
||||||
|
|
||||||
|
$ fission route ... # routes work as usual
|
||||||
|
```
|
||||||
|
|
||||||
This same user story applies to interpreted languages too, where the
|
This same user story applies to interpreted languages too, where the
|
||||||
"compilation" step can be used to check for syntax errors.
|
"compilation" step can be used to check for syntax errors.
|
||||||
|
|
||||||
|
#### Compiled language, without using fission builds
|
||||||
|
|
||||||
#### Collections of files
|
User compiles their function locally, resulting in a set of one or
|
||||||
|
more binaries. The user packages these up as a zip file, creating a
|
||||||
We should probably have a manifest in YAML/JSON/etc syntax for
|
"deployment package".
|
||||||
specifying a function. We could also use that YAML to let users
|
|
||||||
specify the function's environment, resource requirements, etc.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ fission funcion create -f blah.yaml
|
$ fission function create --deployment-package foo.zip
|
||||||
|
|
||||||
$ cat blah.yaml
|
$ fission route ... # routes work as usual
|
||||||
type: Function
|
|
||||||
metadata:
|
|
||||||
name: ...
|
|
||||||
environment: ...
|
|
||||||
files:
|
|
||||||
- foo.py
|
|
||||||
- bar.py
|
|
||||||
- baz/*.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The yaml file could specify a list of files. The fission client would
|
In this use case, fission is no longer operating at the source
|
||||||
deal with packaging up this set of files and uploading the package.
|
level. Builds are left to the user and fission only sees the
|
||||||
|
deployment package package.
|
||||||
|
|
||||||
|
#### Collections of source files
|
||||||
|
|
||||||
|
The user can create a source package -- a set of source files in a
|
||||||
|
zip.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission function create --source-package foo.zip
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
This workflow works similarly to providing a single source file.
|
||||||
|
|
||||||
|
In addition, fission CLI could support automatic creation of source packages, e.g.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission function create --source-files *.js
|
||||||
|
```
|
||||||
|
|
||||||
|
This is purely client-side "syntactic sugar" -- the CLI creates the
|
||||||
|
source package instead of the user having to do it manually. It
|
||||||
|
doesn't change semantics; the source package is still handled as one
|
||||||
|
object.
|
||||||
|
|
||||||
#### Handling Dependencies
|
#### Handling Dependencies
|
||||||
|
|
||||||
A manifest could point at a function's dependency spec. The
|
The source package of a function can contain dependency specs.
|
||||||
Environment's "builder" container could then fetch these deps.
|
|
||||||
|
|
||||||
So a NodeJS function manifest could contain a reference to
|
Fission framework proper does not treat this spec in any special way;
|
||||||
package.json. A "builder" container in the NodeJS environment would
|
it's just another file in the source package. These will be
|
||||||
then run npm on that function.
|
interpreted by the environment builder.
|
||||||
|
|
||||||
``` $ cat func.yaml
|
```
|
||||||
type: Function
|
$ fission function create --source-files *.js --source-files package.json
|
||||||
metadata: ...
|
|
||||||
environment: ...
|
|
||||||
dependencies: package.json
|
|
||||||
|
|
||||||
$ fission function create -f func.yaml
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In this case, the CLI will create a source package containing the JS
|
||||||
|
files and package.json. The NodeJS environment builder will create a
|
||||||
|
deployment package out of these files. The runtime environment will
|
||||||
|
load and run the deployment package.
|
||||||
|
|
||||||
|
#### V1 Compatibility
|
||||||
|
|
||||||
|
V1 Environments will continue to be supported. Existing commands will
|
||||||
|
continue to work. V1 environments won't support newer features like
|
||||||
|
builds, source and deployment packages, etc.
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
|
||||||
|
#### Environment Type
|
||||||
|
|
||||||
|
The environment type has a set of new properties: version, runtime
|
||||||
|
image, builder image, build command, file extension(s).
|
||||||
|
|
||||||
|
#### Function Type
|
||||||
|
|
||||||
|
The function type has new properties: source package, deployment
|
||||||
|
package. The literal code string continues to be supported, but it
|
||||||
|
will have a specified size limit, say 512KB.
|
||||||
|
|
||||||
|
#### Source and Binary Packages
|
||||||
|
|
||||||
|
A package is just a zip file. It's contents are opaque to fission:
|
||||||
|
the meaning of its contents is defined by the environment. Fission's
|
||||||
|
job is to manage the storage and delivery of the package into build
|
||||||
|
and runtime environments.
|
||||||
|
|
||||||
|
#### Storage Service
|
||||||
|
|
||||||
|
The storage service will have an HTTP API to upload and download
|
||||||
|
files. It can store the packages on a persistent volume or as objects
|
||||||
|
in cloud storage services such as S3.
|
||||||
|
|
||||||
|
Storage service has a garbage collection API endpoint. When invoked,
|
||||||
|
it will remove all packages that are not referenced from any function.
|
||||||
|
|
||||||
|
#### Fetcher
|
||||||
|
|
||||||
|
Fetcher gets some new responsibilities:
|
||||||
|
|
||||||
|
1. It must now also handle zip/unzip of packages
|
||||||
|
|
||||||
|
2. It must know how to upload to the storage service (so it's not
|
||||||
|
exactly "fetcher" any more, but...)
|
||||||
|
|
||||||
|
#### Runtime Environment Interface
|
||||||
|
|
||||||
|
The V2 runtime environment interface is very similar to V1
|
||||||
|
environments. Environments must support a dynamic loader and have an
|
||||||
|
HTTP server that forwards requests to the loaded module.
|
||||||
|
|
||||||
|
The differences:
|
||||||
|
|
||||||
|
* V2 runtimes must support loading a deployment package. Fetcher is
|
||||||
|
responsible for unzipping a deployment package, but interpretation
|
||||||
|
of the contents is up to the environment's code. For example, it
|
||||||
|
may have to include the directory where the deployment package is
|
||||||
|
unzipped in its module load path.
|
||||||
|
|
||||||
|
[TODO any other differences?]
|
||||||
|
|
||||||
|
#### Buildmgr
|
||||||
|
|
||||||
|
A new service that will manage builds. Its design is similar to
|
||||||
|
poolmgr, except it is triggered on creation or update of a function,
|
||||||
|
rather than HTTP requests.
|
||||||
|
|
||||||
|
Buildmgr creates a builder deployment+service for each environment.
|
||||||
|
Pods in this deployment run the environment's build container, and
|
||||||
|
fetcher, with a shared volume between the two containers.
|
||||||
|
|
||||||
|
When a function is created or updated with a source package, buildmgr
|
||||||
|
notices this and triggers a build. First, it calls fetcher to
|
||||||
|
download the source package into a shared volume with the build
|
||||||
|
container. It then invokes the builder by running the build
|
||||||
|
invocation command in the build container. Next, it calls fetcher to
|
||||||
|
package up the output of the builder and store the built package into
|
||||||
|
the the storage service.
|
||||||
|
|
||||||
|
Finally, it updates the function object in the controller API with a
|
||||||
|
reference to the built package.
|
||||||
|
|
||||||
|
[We can collapse this workflow into one request into the builder
|
||||||
|
service, which would make it easier to scale up the builder
|
||||||
|
deployment; if we used multiple requests we'd need some sort of
|
||||||
|
affinity rule, but k8s services only support IP based affinity.]
|
||||||
|
|
||||||
|
#### Poolmgr
|
||||||
|
|
||||||
|
Poolmgr remains relatively unchanged. Instead of contructing URLs for
|
||||||
|
function metadata, it uses the deployment package URL in the function
|
||||||
|
object.
|
||||||
|
|
||||||
|
#### CLI
|
||||||
|
|
||||||
|
Client libraries and CLI have to deal with the new properties in
|
||||||
|
functions and environments.
|
||||||
|
|
||||||
|
The CLI will now talk to both storage service and controller. When a
|
||||||
|
function is created, the user can specify the function in one of 3
|
||||||
|
ways:
|
||||||
|
|
||||||
|
1. One source file, same as v1.
|
||||||
|
|
||||||
|
2. A source package (or a set of source files, which is turned into a
|
||||||
|
source package by the CLI)
|
||||||
|
|
||||||
|
3. A deployment package
|
||||||
|
|
||||||
|
If the file is specified as a source file, fission CLI will use the
|
||||||
|
code literal if it's under the size limit; otherwise it should use the
|
||||||
|
storage service. This will allow users to use fission deployments
|
||||||
|
with no storage service, but with a size limit on functions.
|
||||||
|
|
||||||
|
For the case of a source or deployment package, the CLI first does an
|
||||||
|
upload to the storage service, then creates a function object with a
|
||||||
|
reference to the uploaded package.
|
||||||
|
|||||||
+128
-19
@@ -1,12 +1,18 @@
|
|||||||
* [Running Fission on your Cluster](#running-fission-on-your-cluster)
|
|
||||||
* [Setup Kubernetes](#setup-kubernetes)
|
- [Running Fission on your Cluster](#running-fission-on-your-cluster)
|
||||||
* [Mac](#install-and-start-kubernetes-on-osx)
|
* [Setup Kubernetes](#setup-kubernetes)
|
||||||
* [Linux](#or-install-and-start-kubernetes-on-linux)
|
+ [Install and start Kubernetes on OSX:](#install-and-start-kubernetes-on-osx)
|
||||||
* [Verify access to the cluster](#verify-access-to-the-cluster)
|
+ [Or, install and start Kubernetes on Linux:](#or-install-and-start-kubernetes-on-linux)
|
||||||
* [Get and Run Fission: Minikube or Local cluster](#get-and-run-fission-minikube-or-local-cluster)
|
* [Verify access to the cluster](#verify-access-to-the-cluster)
|
||||||
* [Get and Run Fission: GKE or other Cloud](#get-and-run-fission-gke-or-other-cloud)
|
* [Get and Run Fission: Minikube or Local cluster](#get-and-run-fission-minikube-or-local-cluster)
|
||||||
* [Install the client CLI](#install-the-client-cli)
|
* [Get and Run Fission: GKE or other Cloud](#get-and-run-fission-gke-or-other-cloud)
|
||||||
* [Run an example](#run-an-example)
|
* [Get and Run Fission: OpenShift](#get-and-run-fission-openshift)
|
||||||
|
+ [Using Minishift or Local Cluster](#using-minishift-or-local-cluster)
|
||||||
|
+ [Using other clouds](#using-other-clouds)
|
||||||
|
* [Install the client CLI](#install-the-client-cli)
|
||||||
|
* [Run an example](#run-an-example)
|
||||||
|
* [Enable Persistent Function Logs (Optional)](#enable-persistent-function-logs-optional)
|
||||||
|
* [Use the web based Fission-ui (Optional)](#use-the-web-based-fission-ui-optional)
|
||||||
|
|
||||||
## Running Fission on your Cluster
|
## Running Fission on your Cluster
|
||||||
|
|
||||||
@@ -17,14 +23,14 @@ You can install Kubernetes on your laptop with [minikube](https://github.com/kub
|
|||||||
#### Install and start Kubernetes on OSX:
|
#### Install and start Kubernetes on OSX:
|
||||||
```bash
|
```bash
|
||||||
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin
|
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin
|
||||||
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.14.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
|
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.16.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
|
||||||
$ minikube start
|
$ minikube start
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Or, install and start Kubernetes on Linux:
|
#### Or, install and start Kubernetes on Linux:
|
||||||
```bash
|
```bash
|
||||||
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin
|
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin
|
||||||
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.14.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
|
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.16.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
|
||||||
$ minikube start
|
$ minikube start
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -43,8 +49,8 @@ set up services with NodePort. This exposes fission on ports 31313
|
|||||||
and 31314.
|
and 31314.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ kubectl create -f http://fission.io/fission.yaml
|
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-rbac.yaml
|
||||||
$ kubectl create -f http://fission.io/fission-nodeport.yaml
|
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-nodeport.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Set the FISSION_URL and FISSION_ROUTER environment variables.
|
Set the FISSION_URL and FISSION_ROUTER environment variables.
|
||||||
@@ -66,8 +72,8 @@ If you're using GKE or any other cloud provider that supports the
|
|||||||
LoadBalancer service type, use these commands:
|
LoadBalancer service type, use these commands:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ kubectl create -f http://fission.io/fission.yaml
|
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-rbac.yaml
|
||||||
$ kubectl create -f http://fission.io/fission-cloud.yaml
|
$ kubectl create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-cloud.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
Save the external IP addresses of controller and router services in
|
Save the external IP addresses of controller and router services in
|
||||||
@@ -80,20 +86,62 @@ svc```). Then:
|
|||||||
$ export FISSION_ROUTER=$(kubectl --namespace fission get svc router -o=jsonpath='{..ip}')
|
$ export FISSION_ROUTER=$(kubectl --namespace fission get svc router -o=jsonpath='{..ip}')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Get and Run Fission: OpenShift
|
||||||
|
|
||||||
|
If you're using OpenShift, it's possible to run Fission on it! The
|
||||||
|
deployment template needs to be deployed as a user with cluster-admin
|
||||||
|
permissions (like `system:admin`), as it needs to create a
|
||||||
|
`ClusterRole` for deploying function containers from the `fission`
|
||||||
|
namespace/project.
|
||||||
|
|
||||||
|
Identically as with Kubernetes, you need to set the FISSION_URL and FISSION_ROUTER environment variables. If you're using minishift, use these commands:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ export FISSION_URL=http://$(minishift ip):31313¬
|
||||||
|
$ export FISSION_ROUTER=$(minishift ip):31314¬
|
||||||
|
```
|
||||||
|
#### Using Minishift or Local Cluster
|
||||||
|
|
||||||
|
If you're using minishift or no cloud provider, use these commands to set up services with NodePort. This exposes fission on ports 31313 and 31314.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ oc login -u system:admin
|
||||||
|
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-openshift.yaml
|
||||||
|
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-nodeport.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using other clouds
|
||||||
|
|
||||||
|
If you're using any cloud provider that supports the LoadBalancer service type, use these commands:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ oc login -u system:admin
|
||||||
|
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-openshift.yaml
|
||||||
|
$ oc create -f https://github.com/fission/fission/releases/download/nightly20170621/fission-cloud.yaml
|
||||||
|
```
|
||||||
|
After these steps, you should be able to run fission client as with kubernetes.
|
||||||
|
|
||||||
### Install the client CLI
|
### Install the client CLI
|
||||||
|
|
||||||
|
#### Mac OS
|
||||||
|
|
||||||
Get the CLI binary for Mac:
|
Get the CLI binary for Mac:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ curl http://fission.io/mac/fission > fission && chmod +x fission && sudo mv fission /usr/local/bin/
|
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170621/fission-cli-osx && chmod +x fission && sudo mv fission /usr/local/bin/
|
||||||
```
|
```
|
||||||
|
|
||||||
Or Linux:
|
#### Linux
|
||||||
|
|
||||||
```
|
```
|
||||||
$ curl http://fission.io/linux/fission > fission && chmod +x fission && sudo mv fission /usr/local/bin/
|
$ curl -Lo fission https://github.com/fission/fission/releases/download/nightly20170621/fission-cli-linux && chmod +x fission && sudo mv fission /usr/local/bin/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Windows
|
||||||
|
|
||||||
|
For Windows, you can use the linux binary on WSL. Or you can download
|
||||||
|
this windows executable: [fission.exe](https://github.com/fission/fission/releases/download/nightly20170621/fission-cli-windows.exe)
|
||||||
|
|
||||||
### Run an example
|
### Run an example
|
||||||
|
|
||||||
Finally, you're ready to use Fission!
|
Finally, you're ready to use Fission!
|
||||||
@@ -101,7 +149,7 @@ Finally, you're ready to use Fission!
|
|||||||
```
|
```
|
||||||
$ fission env create --name nodejs --image fission/node-env
|
$ fission env create --name nodejs --image fission/node-env
|
||||||
|
|
||||||
$ echo 'module.exports = function(context, callback) { callback(200, "Hello, world!\n"); }' > hello.js
|
$ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js
|
||||||
|
|
||||||
$ fission function create --name hello --env nodejs --code hello.js
|
$ fission function create --name hello --env nodejs --code hello.js
|
||||||
|
|
||||||
@@ -110,3 +158,64 @@ Finally, you're ready to use Fission!
|
|||||||
$ curl http://$FISSION_ROUTER/hello
|
$ curl http://$FISSION_ROUTER/hello
|
||||||
Hello, world!
|
Hello, world!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Enable Persistent Function Logs (Optional)
|
||||||
|
|
||||||
|
Fission uses InfluxDB to store logs and fluentd to forward them from
|
||||||
|
function pods into InfluxDB.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ kubectl create -f fission-logger.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it for the basic setup. You can now use following command to view function logs:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission function logs --name hello
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also list the all the pods that have hosted the function
|
||||||
|
(including ones that aren't alive any more) and view logs for a
|
||||||
|
particular pod:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission function pods --name hello
|
||||||
|
|
||||||
|
$ fission function logs --name hello --pod <pod name>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use the web based Fission-ui (Optional)
|
||||||
|
|
||||||
|
[Fission-ui](https://github.com/fission/fission-ui) is the ui for fission maintained by the community.
|
||||||
|
It allows users to observe and manage fission. It also provides a simple online development environment for serverless functions.
|
||||||
|
|
||||||
|
To setup Fission-ui with fission in k8s is simple:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run this after fission is deployed
|
||||||
|
$ kubectl create -f https://raw.githubusercontent.com/fission/fission-ui/master/docker/fission-ui.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open `http://node-ip:31319` to use Fission-ui.
|
||||||
|
|
||||||
|
For more infomation, please check out [Fission-ui Readme](https://github.com/fission/fission-ui/blob/master/README.md).
|
||||||
|
|
||||||
|
### Install NATS for message-queue based triggers (Optional)
|
||||||
|
|
||||||
|
Fission supports message queue triggers that allow you to invoke
|
||||||
|
functions based on events in a queue. For now, NATS-Streaming is the
|
||||||
|
only supported message queue.
|
||||||
|
|
||||||
|
You can install NATS Streaming on your Kubernetes cluster with:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ kubectl create -f fission-nats.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
You can subscribe to a NATS Streaming queue with a command like this:
|
||||||
|
(See `fission mqtrigger --help` for details)
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission mqtrigger create --name myQueueTrigger --function processEvent --topic "myQueue.request"
|
||||||
|
```
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ Usage
|
|||||||
$ fission env create --name nodejs --image fission/node-env
|
$ fission env create --name nodejs --image fission/node-env
|
||||||
|
|
||||||
# A javascript one-liner that prints "hello world"
|
# A javascript one-liner that prints "hello world"
|
||||||
$ echo 'module.exports = function(context, callback) { callback(200, "Hello, world!\n"); }' > hello.js
|
$ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js
|
||||||
|
|
||||||
# Upload your function code to fission
|
# Upload your function code to fission
|
||||||
$ fission function create --name hello --env nodejs --code hello.js
|
$ fission function create --name hello --env nodejs --code hello.js
|
||||||
@@ -81,148 +81,12 @@ See the [examples](examples) directory for more.
|
|||||||
Running Fission on your Cluster
|
Running Fission on your Cluster
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
### Setup Kubernetes
|
See the [installation guide](INSTALL.md).
|
||||||
|
|
||||||
You can install Kubernetes on your laptop with [minikube](https://github.com/kubernetes/minikube):
|
|
||||||
|
|
||||||
#### Install and start Kubernetes on OSX:
|
|
||||||
```bash
|
|
||||||
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin
|
|
||||||
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.14.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
|
|
||||||
$ minikube start
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Or, install and start Kubernetes on Linux:
|
|
||||||
```bash
|
|
||||||
$ curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin
|
|
||||||
$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.14.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/
|
|
||||||
$ minikube start
|
|
||||||
```
|
|
||||||
|
|
||||||
Or, you can use [Google Container Engine's](https://cloud.google.com/container-engine/) free trial to get a 3 node cluster.
|
|
||||||
|
|
||||||
### Verify access to the cluster
|
|
||||||
|
|
||||||
```
|
|
||||||
$ kubectl version
|
|
||||||
```
|
|
||||||
|
|
||||||
### Get and Run Fission: Minikube or Local cluster
|
|
||||||
|
|
||||||
If you're using minikube or no cloud provider, use these commands to
|
|
||||||
set up services with NodePort. This exposes fission on ports 31313
|
|
||||||
and 31314.
|
|
||||||
|
|
||||||
```
|
|
||||||
$ kubectl create -f http://fission.io/fission.yaml
|
|
||||||
$ kubectl create -f http://fission.io/fission-nodeport.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the FISSION_URL and FISSION_ROUTER environment variables.
|
|
||||||
FISSION_URL is used by the fission CLI to find the server.
|
|
||||||
FISSION_URL should be prefixed with a `http://`. (FISSION_ROUTER is
|
|
||||||
only needed for the examples below to work.)
|
|
||||||
|
|
||||||
If you're using minikube, use these commands:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ export FISSION_URL=http://$(minikube ip):31313
|
|
||||||
$ export FISSION_ROUTER=$(minikube ip):31314
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Get and Run Fission: GKE or other Cloud
|
|
||||||
|
|
||||||
If you're using GKE or any other cloud provider that supports the
|
|
||||||
LoadBalancer service type, use these commands:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ kubectl create -f http://fission.io/fission.yaml
|
|
||||||
$ kubectl create -f http://fission.io/fission-cloud.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
Save the external IP addresses of controller and router services in
|
|
||||||
FISSION_URL and FISSION_ROUTER, respectively. Wait for services to
|
|
||||||
get IP addresses (check this with ```kubectl --namespace fission get
|
|
||||||
svc```). Then:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ export FISSION_URL=http://$(kubectl --namespace fission get svc controller -o=jsonpath='{..ip}')
|
|
||||||
$ export FISSION_ROUTER=$(kubectl --namespace fission get svc router -o=jsonpath='{..ip}')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Install the client CLI
|
|
||||||
|
|
||||||
Get the CLI binary for Mac:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ curl http://fission.io/mac/fission > fission && chmod +x fission && sudo mv fission /usr/local/bin/
|
|
||||||
```
|
|
||||||
|
|
||||||
Or Linux:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ curl http://fission.io/linux/fission > fission && chmod +x fission && sudo mv fission /usr/local/bin/
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run an example
|
|
||||||
|
|
||||||
Finally, you're ready to use Fission!
|
|
||||||
|
|
||||||
```
|
|
||||||
$ fission env create --name nodejs --image fission/node-env
|
|
||||||
|
|
||||||
$ echo 'module.exports = function(context, callback) { callback(200, "Hello, world!\n"); }' > hello.js
|
|
||||||
|
|
||||||
$ fission function create --name hello --env nodejs --code hello.js
|
|
||||||
|
|
||||||
$ fission route create --method GET --url /hello --function hello
|
|
||||||
|
|
||||||
$ curl http://$FISSION_ROUTER/hello
|
|
||||||
Hello, world!
|
|
||||||
```
|
|
||||||
|
|
||||||
Compiling Fission
|
Compiling Fission
|
||||||
=================
|
=================
|
||||||
|
|
||||||
[You only need to do this if you're making Fission changes; if you're
|
See the [compilation guide](Compiling.md).
|
||||||
just deploying Fission, use fission.yaml which points to prebuilt
|
|
||||||
images.]
|
|
||||||
|
|
||||||
You'll need go installed, along with the glide dependecy management
|
|
||||||
tool. You'll also need docker for building images.
|
|
||||||
|
|
||||||
The server side is compiled as one binary ("fission-bundle") which
|
|
||||||
contains controller, poolmgr and router; it invokes the right one
|
|
||||||
based on command-line arguments.
|
|
||||||
|
|
||||||
To build fission-bundle: clone this repo, then from the top level
|
|
||||||
directory:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Get dependencies
|
|
||||||
$ glide install
|
|
||||||
|
|
||||||
# Build fission server and an image
|
|
||||||
$ pushd fission-bundle
|
|
||||||
$ ./build.sh
|
|
||||||
|
|
||||||
# Edit push.sh to point to your registry
|
|
||||||
$ $EDITOR push.sh
|
|
||||||
$ ./push.sh
|
|
||||||
$ popd
|
|
||||||
|
|
||||||
# To install, update fission.yaml to point to your compiled image
|
|
||||||
$ $EDITOR fission.yaml
|
|
||||||
$ kubectl create -f fission.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're changing the CLI:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Build Fission CLI
|
|
||||||
$ cd fission && go install
|
|
||||||
```
|
|
||||||
|
|
||||||
Status
|
Status
|
||||||
======
|
======
|
||||||
|
|||||||
Vendored
+2
-2
@@ -17,10 +17,10 @@ limitations under the License.
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fission/fission"
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Vendored
+5
-3
@@ -16,9 +16,11 @@ limitations under the License.
|
|||||||
|
|
||||||
package cache
|
package cache
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
import "log"
|
"log"
|
||||||
import "time"
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
func checkErr(err error) {
|
func checkErr(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
name: fission
|
||||||
|
version: 0.1.0
|
||||||
|
description: A Helm chart for Fission. Fission is a fast serverless framework for Kubernetes.
|
||||||
|
keywords:
|
||||||
|
- fission
|
||||||
|
- serverless
|
||||||
|
home: http://fission.io/
|
||||||
|
maintainers:
|
||||||
|
- name: Sanket Sudake
|
||||||
|
email: sanketsudake@gmail.com
|
||||||
|
engine: gotpl
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Fission
|
||||||
|
|
||||||
|
[Fission](http://fission.io/) is a framework for serverless functions on Kubernetes.
|
||||||
|
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Kubernetes 1.4+ with Beta APIs enabled
|
||||||
|
|
||||||
|
|
||||||
|
## Installing the chart
|
||||||
|
|
||||||
|
To install the chart with the release name `my-release`,
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ helm install --name my-release ./fission
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uninstalling the chart
|
||||||
|
|
||||||
|
To uninstall/delete chart,
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ helm delete my-release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The following table lists the configurable parameters of the Fission chart and their default values.
|
||||||
|
|
||||||
|
| Parameter | Description | Default |
|
||||||
|
| ------------------- | ------------------------------------------ | ------------------------ |
|
||||||
|
| `serviceType` | Type of service to use | `LoadBalancer`. |
|
||||||
|
| `image` | Fission image | `fission/fission-bundle` |
|
||||||
|
| `imageTag` | Fission image tag | `alpha20170124` |
|
||||||
|
| `controllerPort` | Fission Controller Service Port | `31313` |
|
||||||
|
| `routerPort` | Fission Router Service Port | `31314` |
|
||||||
|
| `functionNamespace` | Namespace for Fission functions | `fission-function` |
|
||||||
|
|
||||||
|
|
||||||
|
Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example,
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ helm install --name my-release --set image=custom/fission-bundle,imageTag=v1 fission
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're using minikube, set serviceType to NodePort:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ helm install --name my-release --set serviceType=NodePort fission
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also set parameters with a yaml file (see values.yaml for
|
||||||
|
what it should look like):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ helm install --name my-release -f values.yaml fission
|
||||||
|
```
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
1. Install the client CLI.
|
||||||
|
|
||||||
|
Mac:
|
||||||
|
$ curl http://fission.io/mac/fission > fission && chmod +x fission && sudo mv fission /usr/local/bin/
|
||||||
|
|
||||||
|
Linux:
|
||||||
|
$ curl http://fission.io/linux/fission > fission && chmod +x fission && sudo mv fission /usr/local/bin/
|
||||||
|
|
||||||
|
2. Set the FISSION_URL and FISSION_ROUTER environment variables.
|
||||||
|
FISSION_URL is used by the fission CLI to find the server.
|
||||||
|
FISSION_URL should be prefixed with a http://. (FISSION_ROUTER is only needed for the examples below to work.)
|
||||||
|
|
||||||
|
{{- if contains "NodePort" .Values.serviceType }}
|
||||||
|
$ export FISSION_URL=http://$(minikube ip):{{ .Values.controllerPort }}
|
||||||
|
$ export FISSION_ROUTER=$(minikube ip):{{ .Values.routerPort }}
|
||||||
|
|
||||||
|
{{- else if contains "LoadBalancer" .Values.serviceType }}
|
||||||
|
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||||
|
You can watch the status of by running 'kubectl --namespace fission get -w svc' and 'kubectl --namespace fission get svc -w router'
|
||||||
|
|
||||||
|
$ export FISSION_URL=http://$(kubectl --namespace fission get svc controller -o=jsonpath='{..ip}')
|
||||||
|
$ export FISSION_ROUTER=$(kubectl --namespace fission get svc router -o=jsonpath='{..ip}')
|
||||||
|
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
3. Finally, you're ready to use Fission!
|
||||||
|
|
||||||
|
$ fission env create --name nodejs --image fission/node-env
|
||||||
|
|
||||||
|
$ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js
|
||||||
|
|
||||||
|
$ fission function create --name hello --env nodejs --code hello.js
|
||||||
|
|
||||||
|
$ fission route create --method GET --url /hello --function hello
|
||||||
|
|
||||||
|
$ curl http://$FISSION_ROUTER/hello
|
||||||
|
Hello, world!
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{{/* vim: set filetype=mustache: */}}
|
||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 24 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
We truncate at 24 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
|
*/}}
|
||||||
|
{{- define "fullname" -}}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 24 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: {{ .Values.functionNamespace }}
|
||||||
|
labels:
|
||||||
|
name: fission-function
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: controller
|
||||||
|
labels:
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: controller
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: controller
|
||||||
|
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
|
||||||
|
command: ["/fission-bundle"]
|
||||||
|
args: ["--controllerPort", "8888", "--filepath", "/filestore"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: router
|
||||||
|
labels:
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: router
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: router
|
||||||
|
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
|
||||||
|
command: ["/fission-bundle"]
|
||||||
|
args: ["--routerPort", "8888"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: poolmgr
|
||||||
|
labels:
|
||||||
|
svc: poolmgr
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8888
|
||||||
|
selector:
|
||||||
|
svc: poolmgr
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: poolmgr
|
||||||
|
labels:
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: poolmgr
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: poolmgr
|
||||||
|
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
|
||||||
|
command: ["/fission-bundle"]
|
||||||
|
args: ["--poolmgrPort", "8888", "--namespace", "{{ .Values.functionNamespace }}"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: kubewatcher
|
||||||
|
labels:
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: kubewatcher
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: kubewatcher
|
||||||
|
image: "{{ .Values.image }}:{{ .Values.imageTag }}"
|
||||||
|
command: ["/fission-bundle"]
|
||||||
|
args: ["--kubewatcher"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: etcd
|
||||||
|
labels:
|
||||||
|
svc: etcd
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 2379
|
||||||
|
targetPort: 2379
|
||||||
|
selector:
|
||||||
|
svc: etcd
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: etcd
|
||||||
|
labels:
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: etcd
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: etcd
|
||||||
|
image: quay.io/coreos/etcd
|
||||||
|
env:
|
||||||
|
- name: ETCD_LISTEN_CLIENT_URLS
|
||||||
|
value: http://0.0.0.0:2379
|
||||||
|
- name: ETCD_ADVERTISE_CLIENT_URLS
|
||||||
|
value: http://etcd:2379
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: router
|
||||||
|
labels:
|
||||||
|
svc: router
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.serviceType }}
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8888
|
||||||
|
nodePort: {{ .Values.routerPort }}
|
||||||
|
selector:
|
||||||
|
svc: router
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: controller
|
||||||
|
labels:
|
||||||
|
svc: controller
|
||||||
|
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.serviceType }}
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8888
|
||||||
|
nodePort: {{ .Values.controllerPort }}
|
||||||
|
selector:
|
||||||
|
svc: controller
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#
|
||||||
|
# Fission chart configuration
|
||||||
|
#
|
||||||
|
|
||||||
|
## Kubernetes configuration
|
||||||
|
## For minikube, set this to NodePort, elsewhere use LoadBalancer.
|
||||||
|
serviceType: LoadBalancer
|
||||||
|
|
||||||
|
## Fission image repostiroy
|
||||||
|
image: fission/fission-bundle
|
||||||
|
|
||||||
|
## Fission image version
|
||||||
|
imageTag: alpha20170124
|
||||||
|
|
||||||
|
## Port at which Fission controller service should be exposed
|
||||||
|
controllerPort: 31313
|
||||||
|
|
||||||
|
## Port at which Fission router service should be exposed
|
||||||
|
routerPort: 31314
|
||||||
|
|
||||||
|
## Namespace in which to run fission functions (this is different from
|
||||||
|
## the release namespace)
|
||||||
|
functionNamespace: fission-function
|
||||||
+58
-11
@@ -21,32 +21,47 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/fission/fission"
|
"github.com/fission/fission"
|
||||||
|
"github.com/fission/fission/fission/logdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type API struct {
|
type (
|
||||||
FunctionStore
|
API struct {
|
||||||
HTTPTriggerStore
|
FunctionStore
|
||||||
EnvironmentStore
|
HTTPTriggerStore
|
||||||
WatchStore
|
TimeTriggerStore
|
||||||
}
|
MessageQueueTriggerStore
|
||||||
|
EnvironmentStore
|
||||||
|
WatchStore
|
||||||
|
}
|
||||||
|
|
||||||
|
logDBConfig struct {
|
||||||
|
httpURL string
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func MakeAPI(rs *ResourceStore) *API {
|
func MakeAPI(rs *ResourceStore) *API {
|
||||||
api := &API{
|
api := &API{
|
||||||
FunctionStore: FunctionStore{ResourceStore: *rs},
|
FunctionStore: FunctionStore{ResourceStore: *rs},
|
||||||
HTTPTriggerStore: HTTPTriggerStore{ResourceStore: *rs},
|
HTTPTriggerStore: HTTPTriggerStore{ResourceStore: *rs},
|
||||||
EnvironmentStore: EnvironmentStore{ResourceStore: *rs},
|
TimeTriggerStore: TimeTriggerStore{ResourceStore: *rs},
|
||||||
WatchStore: WatchStore{ResourceStore: *rs},
|
MessageQueueTriggerStore: MessageQueueTriggerStore{ResourceStore: *rs},
|
||||||
|
EnvironmentStore: EnvironmentStore{ResourceStore: *rs},
|
||||||
|
WatchStore: WatchStore{ResourceStore: *rs},
|
||||||
}
|
}
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) respondWithSuccess(w http.ResponseWriter, resp []byte) {
|
func (api *API) respondWithSuccess(w http.ResponseWriter, resp []byte) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
_, err := w.Write(resp)
|
_, err := w.Write(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// this will probably fail too, but try anyway
|
// this will probably fail too, but try anyway
|
||||||
@@ -61,7 +76,25 @@ func (api *API) respondWithError(w http.ResponseWriter, err error) {
|
|||||||
http.Error(w, msg, code)
|
http.Error(w, msg, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *API) getLogDBConfig(dbType string) logDBConfig {
|
||||||
|
dbType = strings.ToUpper(dbType)
|
||||||
|
// retrieve db auth config from the env
|
||||||
|
url := os.Getenv(fmt.Sprintf("%s_URL", dbType))
|
||||||
|
if url == "" {
|
||||||
|
// set up default database url
|
||||||
|
url = logdb.INFLUXDB_URL
|
||||||
|
}
|
||||||
|
username := os.Getenv(fmt.Sprintf("%s_USERNAME", dbType))
|
||||||
|
password := os.Getenv(fmt.Sprintf("%s_PASSWORD", dbType))
|
||||||
|
return logDBConfig{
|
||||||
|
httpURL: url,
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (api *API) HomeHandler(w http.ResponseWriter, r *http.Request) {
|
func (api *API) HomeHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
fmt.Fprintf(w, "{\"message\": \"Fission API\", \"version\": \"0.1.0\"}\n")
|
fmt.Fprintf(w, "{\"message\": \"Fission API\", \"version\": \"0.1.0\"}\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +126,20 @@ func (api *API) Serve(port int) {
|
|||||||
r.HandleFunc("/v1/watches/{watch}", api.WatchApiUpdate).Methods("PUT")
|
r.HandleFunc("/v1/watches/{watch}", api.WatchApiUpdate).Methods("PUT")
|
||||||
r.HandleFunc("/v1/watches/{watch}", api.WatchApiDelete).Methods("DELETE")
|
r.HandleFunc("/v1/watches/{watch}", api.WatchApiDelete).Methods("DELETE")
|
||||||
|
|
||||||
|
r.HandleFunc("/v1/triggers/time", api.TimeTriggerApiList).Methods("GET")
|
||||||
|
r.HandleFunc("/v1/triggers/time", api.TimeTriggerApiCreate).Methods("POST")
|
||||||
|
r.HandleFunc("/v1/triggers/time/{timeTrigger}", api.TimeTriggerApiGet).Methods("GET")
|
||||||
|
r.HandleFunc("/v1/triggers/time/{timeTrigger}", api.TimeTriggerApiUpdate).Methods("PUT")
|
||||||
|
r.HandleFunc("/v1/triggers/time/{timeTrigger}", api.TimeTriggerApiDelete).Methods("DELETE")
|
||||||
|
|
||||||
|
r.HandleFunc("/v1/triggers/messagequeue", api.MessageQueueTriggerApiList).Methods("GET")
|
||||||
|
r.HandleFunc("/v1/triggers/messagequeue", api.MessageQueueApiCreate).Methods("POST")
|
||||||
|
r.HandleFunc("/v1/triggers/messagequeue/{mqTrigger}", api.MessageQueueApiGet).Methods("GET")
|
||||||
|
r.HandleFunc("/v1/triggers/messagequeue/{mqTrigger}", api.MessageQueueApiUpdate).Methods("PUT")
|
||||||
|
r.HandleFunc("/v1/triggers/messagequeue/{mqTrigger}", api.MessageQueueApiDelete).Methods("DELETE")
|
||||||
|
|
||||||
|
r.HandleFunc("/proxy/{dbType}", api.FunctionLogsApiPost).Methods("POST")
|
||||||
|
|
||||||
address := fmt.Sprintf(":%v", port)
|
address := fmt.Sprintf(":%v", port)
|
||||||
|
|
||||||
log.WithFields(log.Fields{"port": port}).Info("Server started")
|
log.WithFields(log.Fields{"port": port}).Info("Server started")
|
||||||
|
|||||||
+175
-1
@@ -24,8 +24,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
etcdClient "github.com/coreos/etcd/client"
|
etcdClient "github.com/coreos/etcd/client"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/fission/fission"
|
"github.com/fission/fission"
|
||||||
@@ -36,6 +36,27 @@ var g struct {
|
|||||||
client *client.Client
|
client *client.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertNameReuseFails(err error, name string) {
|
||||||
|
assert(err != nil, "recreating "+name+" with same name must fail")
|
||||||
|
fe, ok := err.(fission.Error)
|
||||||
|
assert(ok, "error must be a fission Error")
|
||||||
|
assert(fe.Code == fission.ErrorNameExists, "error must be a name exists error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertNotFoundFails(err error, name string) {
|
||||||
|
assert(err != nil, "requesting a non-existent "+name+" must fail")
|
||||||
|
fe, ok := err.(fission.Error)
|
||||||
|
assert(ok, "error must be a fission Error")
|
||||||
|
assert(fe.Code == fission.ErrorNotFound, "error must be a not found error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertCronSpecFails(err error) {
|
||||||
|
assert(err != nil, "using an invalid cron spec must fail")
|
||||||
|
fe, ok := err.(fission.Error)
|
||||||
|
assert(ok, "error must be a fission Error")
|
||||||
|
assert(fe.Code == fission.ErrorInvalidArgument, "error must be a invalid argument error")
|
||||||
|
}
|
||||||
|
|
||||||
func TestFunctionApi(t *testing.T) {
|
func TestFunctionApi(t *testing.T) {
|
||||||
log.SetFormatter(&log.TextFormatter{DisableColors: true})
|
log.SetFormatter(&log.TextFormatter{DisableColors: true})
|
||||||
|
|
||||||
@@ -50,12 +71,17 @@ func TestFunctionApi(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Code: "code1",
|
Code: "code1",
|
||||||
}
|
}
|
||||||
|
_, err := g.client.FunctionGet(&fission.Metadata{Name: "foo"})
|
||||||
|
assertNotFoundFails(err, "function")
|
||||||
|
|
||||||
m, err := g.client.FunctionCreate(testFunc)
|
m, err := g.client.FunctionCreate(testFunc)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
uid1 := m.Uid
|
uid1 := m.Uid
|
||||||
//log.Printf("Created function %v: %v", m.Name, m.Uid)
|
//log.Printf("Created function %v: %v", m.Name, m.Uid)
|
||||||
|
|
||||||
|
_, err = g.client.FunctionCreate(testFunc)
|
||||||
|
assertNameReuseFails(err, "function")
|
||||||
|
|
||||||
code, err := g.client.FunctionGetRaw(m)
|
code, err := g.client.FunctionGetRaw(m)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
assert(string(code) == testFunc.Code, "code from FunctionGetRaw must match created function")
|
assert(string(code) == testFunc.Code, "code from FunctionGetRaw must match created function")
|
||||||
@@ -101,12 +127,81 @@ func TestFunctionApi(t *testing.T) {
|
|||||||
assert(len(funcs) == 2,
|
assert(len(funcs) == 2,
|
||||||
"created two functions, but didn't find them")
|
"created two functions, but didn't find them")
|
||||||
|
|
||||||
|
funcs_url := g.client.Url + "/v1/functions"
|
||||||
|
resp, err := http.Get(funcs_url)
|
||||||
|
panicIf(err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
assert(resp.StatusCode == 200, "http get status code on /v1/functions")
|
||||||
|
|
||||||
|
var found bool = false
|
||||||
|
for _, b := range resp.Header["Content-Type"] {
|
||||||
|
if b == "application/json; charset=utf-8" {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found, "incorrect response content type")
|
||||||
|
|
||||||
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo"})
|
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo"})
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
err = g.client.FunctionDelete(&fission.Metadata{Name: "bar"})
|
err = g.client.FunctionDelete(&fission.Metadata{Name: "bar"})
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFunctionVersionApi(t *testing.T) {
|
||||||
|
testFunc := &fission.Function{
|
||||||
|
Metadata: fission.Metadata{
|
||||||
|
Name: "foo",
|
||||||
|
Uid: "",
|
||||||
|
},
|
||||||
|
Environment: fission.Metadata{
|
||||||
|
Name: "nodejs",
|
||||||
|
Uid: "xxx",
|
||||||
|
},
|
||||||
|
Code: "code1",
|
||||||
|
}
|
||||||
|
|
||||||
|
testFunc.Code = "code1"
|
||||||
|
m, err := g.client.FunctionCreate(testFunc)
|
||||||
|
panicIf(err)
|
||||||
|
uid1 := m.Uid
|
||||||
|
|
||||||
|
testFunc.Code = "code2"
|
||||||
|
m, err = g.client.FunctionUpdate(testFunc)
|
||||||
|
panicIf(err)
|
||||||
|
uid2 := m.Uid
|
||||||
|
|
||||||
|
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo", Uid: uid1})
|
||||||
|
panicIf(err)
|
||||||
|
|
||||||
|
f, err := g.client.FunctionGet(&fission.Metadata{Name: "foo"})
|
||||||
|
panicIf(err)
|
||||||
|
assert(f.Metadata.Uid == uid2, "deleted version1, but version2 does not exist")
|
||||||
|
|
||||||
|
testFunc.Code = "code3"
|
||||||
|
m, err = g.client.FunctionUpdate(testFunc)
|
||||||
|
panicIf(err)
|
||||||
|
uid3 := m.Uid
|
||||||
|
|
||||||
|
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo", Uid: uid3})
|
||||||
|
panicIf(err)
|
||||||
|
|
||||||
|
f, err = g.client.FunctionGet(&fission.Metadata{Name: "foo"})
|
||||||
|
panicIf(err)
|
||||||
|
assert(f.Metadata.Uid == uid2, "deleted version3, but version2 does not exist")
|
||||||
|
|
||||||
|
testFunc.Code = "code4"
|
||||||
|
m, err = g.client.FunctionUpdate(testFunc)
|
||||||
|
panicIf(err)
|
||||||
|
|
||||||
|
err = g.client.FunctionDelete(&fission.Metadata{Name: "foo"})
|
||||||
|
panicIf(err)
|
||||||
|
|
||||||
|
funcs, err := g.client.FunctionList()
|
||||||
|
panicIf(err)
|
||||||
|
assert(len(funcs) == 0,
|
||||||
|
"created one function with two versions(2 and 4), delete without uid but cannot delete them all")
|
||||||
|
}
|
||||||
|
|
||||||
func TestHTTPTriggerApi(t *testing.T) {
|
func TestHTTPTriggerApi(t *testing.T) {
|
||||||
testTrigger := &fission.HTTPTrigger{
|
testTrigger := &fission.HTTPTrigger{
|
||||||
Metadata: fission.Metadata{
|
Metadata: fission.Metadata{
|
||||||
@@ -119,10 +214,16 @@ func TestHTTPTriggerApi(t *testing.T) {
|
|||||||
Uid: "",
|
Uid: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
_, err := g.client.HTTPTriggerGet(&fission.Metadata{Name: "foo"})
|
||||||
|
assertNotFoundFails(err, "trigger")
|
||||||
|
|
||||||
m, err := g.client.HTTPTriggerCreate(testTrigger)
|
m, err := g.client.HTTPTriggerCreate(testTrigger)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
defer g.client.HTTPTriggerDelete(m)
|
defer g.client.HTTPTriggerDelete(m)
|
||||||
|
|
||||||
|
_, err = g.client.HTTPTriggerCreate(testTrigger)
|
||||||
|
assertNameReuseFails(err, "trigger")
|
||||||
|
|
||||||
tr, err := g.client.HTTPTriggerGet(m)
|
tr, err := g.client.HTTPTriggerGet(m)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
testTrigger.Metadata.Uid = m.Uid
|
testTrigger.Metadata.Uid = m.Uid
|
||||||
@@ -140,6 +241,10 @@ func TestHTTPTriggerApi(t *testing.T) {
|
|||||||
|
|
||||||
testTrigger.Metadata.Name = "yyy"
|
testTrigger.Metadata.Name = "yyy"
|
||||||
m, err = g.client.HTTPTriggerCreate(testTrigger)
|
m, err = g.client.HTTPTriggerCreate(testTrigger)
|
||||||
|
assert(err != nil, "duplicate trigger should not be allowed")
|
||||||
|
|
||||||
|
testTrigger.UrlPattern = "/hi2"
|
||||||
|
m, err = g.client.HTTPTriggerCreate(testTrigger)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
defer g.client.HTTPTriggerDelete(m)
|
defer g.client.HTTPTriggerDelete(m)
|
||||||
|
|
||||||
@@ -156,10 +261,16 @@ func TestEnvironmentApi(t *testing.T) {
|
|||||||
},
|
},
|
||||||
RunContainerImageUrl: "gcr.io/xyz",
|
RunContainerImageUrl: "gcr.io/xyz",
|
||||||
}
|
}
|
||||||
|
_, err := g.client.EnvironmentGet(&fission.Metadata{Name: "foo"})
|
||||||
|
assertNotFoundFails(err, "environment")
|
||||||
|
|
||||||
m, err := g.client.EnvironmentCreate(testEnv)
|
m, err := g.client.EnvironmentCreate(testEnv)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
defer g.client.EnvironmentDelete(m)
|
defer g.client.EnvironmentDelete(m)
|
||||||
|
|
||||||
|
_, err = g.client.EnvironmentCreate(testEnv)
|
||||||
|
assertNameReuseFails(err, "environment")
|
||||||
|
|
||||||
tr, err := g.client.EnvironmentGet(m)
|
tr, err := g.client.EnvironmentGet(m)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
testEnv.Metadata.Uid = m.Uid
|
testEnv.Metadata.Uid = m.Uid
|
||||||
@@ -201,10 +312,16 @@ func TestWatchApi(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Target: "",
|
Target: "",
|
||||||
}
|
}
|
||||||
|
_, err := g.client.WatchGet(&fission.Metadata{Name: "foo"})
|
||||||
|
assertNotFoundFails(err, "watch")
|
||||||
|
|
||||||
m, err := g.client.WatchCreate(testWatch)
|
m, err := g.client.WatchCreate(testWatch)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
defer g.client.WatchDelete(m)
|
defer g.client.WatchDelete(m)
|
||||||
|
|
||||||
|
_, err = g.client.WatchCreate(testWatch)
|
||||||
|
assertNameReuseFails(err, "watch")
|
||||||
|
|
||||||
w, err := g.client.WatchGet(m)
|
w, err := g.client.WatchGet(m)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
testWatch.Metadata.Uid = m.Uid
|
testWatch.Metadata.Uid = m.Uid
|
||||||
@@ -221,6 +338,53 @@ func TestWatchApi(t *testing.T) {
|
|||||||
assert(len(ws) == 2, "created two envs, but didn't find them")
|
assert(len(ws) == 2, "created two envs, but didn't find them")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTimeTriggerApi(t *testing.T) {
|
||||||
|
testTrigger := &fission.TimeTrigger{
|
||||||
|
Metadata: fission.Metadata{
|
||||||
|
Name: "xxx",
|
||||||
|
Uid: "yyy",
|
||||||
|
},
|
||||||
|
Cron: "0 30 * * * *",
|
||||||
|
Function: fission.Metadata{
|
||||||
|
Name: "foo",
|
||||||
|
Uid: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := g.client.TimeTriggerGet(&fission.Metadata{Name: "foo"})
|
||||||
|
assertNotFoundFails(err, "trigger")
|
||||||
|
|
||||||
|
m, err := g.client.TimeTriggerCreate(testTrigger)
|
||||||
|
panicIf(err)
|
||||||
|
defer g.client.TimeTriggerDelete(m)
|
||||||
|
|
||||||
|
_, err = g.client.TimeTriggerCreate(testTrigger)
|
||||||
|
assertNameReuseFails(err, "trigger")
|
||||||
|
|
||||||
|
tr, err := g.client.TimeTriggerGet(m)
|
||||||
|
panicIf(err)
|
||||||
|
testTrigger.Metadata.Uid = m.Uid
|
||||||
|
assert(*testTrigger == *tr, "trigger should match after reading")
|
||||||
|
|
||||||
|
testTrigger.Cron = "@hourly"
|
||||||
|
m2, err := g.client.TimeTriggerUpdate(testTrigger)
|
||||||
|
panicIf(err)
|
||||||
|
|
||||||
|
m.Uid = m2.Uid
|
||||||
|
tr, err = g.client.TimeTriggerGet(m)
|
||||||
|
panicIf(err)
|
||||||
|
testTrigger.Metadata.Uid = m.Uid
|
||||||
|
assert(*testTrigger == *tr, "trigger should match after reading")
|
||||||
|
|
||||||
|
testTrigger.Metadata.Name = "yyy"
|
||||||
|
testTrigger.Cron = "Not valid cron spec"
|
||||||
|
m, err = g.client.TimeTriggerCreate(testTrigger)
|
||||||
|
assertCronSpecFails(err)
|
||||||
|
|
||||||
|
ts, err := g.client.TimeTriggerList()
|
||||||
|
panicIf(err)
|
||||||
|
assert(len(ts) == 1, "created one trigger, but didn't find it")
|
||||||
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -232,6 +396,7 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
ks.Delete(context.Background(), "Function", &etcdClient.DeleteOptions{Recursive: true})
|
ks.Delete(context.Background(), "Function", &etcdClient.DeleteOptions{Recursive: true})
|
||||||
ks.Delete(context.Background(), "HTTPTrigger", &etcdClient.DeleteOptions{Recursive: true})
|
ks.Delete(context.Background(), "HTTPTrigger", &etcdClient.DeleteOptions{Recursive: true})
|
||||||
|
ks.Delete(context.Background(), "TimeTrigger", &etcdClient.DeleteOptions{Recursive: true})
|
||||||
ks.Delete(context.Background(), "Environment", &etcdClient.DeleteOptions{Recursive: true})
|
ks.Delete(context.Background(), "Environment", &etcdClient.DeleteOptions{Recursive: true})
|
||||||
ks.Delete(context.Background(), "Watch", &etcdClient.DeleteOptions{Recursive: true})
|
ks.Delete(context.Background(), "Watch", &etcdClient.DeleteOptions{Recursive: true})
|
||||||
|
|
||||||
@@ -241,6 +406,15 @@ func TestMain(m *testing.M) {
|
|||||||
resp, err := http.Get("http://localhost:8888/")
|
resp, err := http.Get("http://localhost:8888/")
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
assert(resp.StatusCode == 200, "http get status code on root")
|
assert(resp.StatusCode == 200, "http get status code on root")
|
||||||
|
|
||||||
|
var found bool = false
|
||||||
|
for _, b := range resp.Header["Content-Type"] {
|
||||||
|
if b == "application/json; charset=utf-8" {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(found, "incorrect response content type")
|
||||||
|
|
||||||
_, err = ioutil.ReadAll(resp.Body)
|
_, err = ioutil.ReadAll(resp.Body)
|
||||||
panicIf(err)
|
panicIf(err)
|
||||||
|
|
||||||
|
|||||||
+238
-31
@@ -26,8 +26,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
|
|
||||||
"github.com/fission/fission"
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -80,19 +78,15 @@ func (c *Client) url(relativeUrl string) string {
|
|||||||
|
|
||||||
func (c *Client) handleResponse(resp *http.Response) ([]byte, error) {
|
func (c *Client) handleResponse(resp *http.Response) ([]byte, error) {
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
var errCode int
|
return nil, fission.MakeErrorFromHTTP(resp)
|
||||||
switch resp.StatusCode {
|
}
|
||||||
case 403:
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
errCode = fission.ErrorNotAuthorized
|
return body, err
|
||||||
case 404:
|
}
|
||||||
errCode = fission.ErrorNotFound
|
|
||||||
case 400:
|
func (c *Client) handleCreateResponse(resp *http.Response) ([]byte, error) {
|
||||||
errCode = fission.ErrorInvalidArgument
|
if resp.StatusCode != 201 {
|
||||||
default:
|
return nil, fission.MakeErrorFromHTTP(resp)
|
||||||
errCode = fission.ErrorInternal
|
|
||||||
}
|
|
||||||
return nil, fission.MakeError(errCode,
|
|
||||||
fmt.Sprintf("HTTP error %v", resp.StatusCode))
|
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
return body, err
|
return body, err
|
||||||
@@ -114,12 +108,8 @@ func (c *Client) FunctionCreate(f *fission.Function) (*fission.Metadata, error)
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleCreateResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"name": f.Metadata.Name,
|
|
||||||
"err": err,
|
|
||||||
}).Error("Failed to create function")
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +213,7 @@ func (c *Client) FunctionList() ([]fission.Function, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -250,12 +241,8 @@ func (c *Client) HTTPTriggerCreate(t *fission.HTTPTrigger) (*fission.Metadata, e
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleCreateResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"name": t.Metadata.Name,
|
|
||||||
"err": err,
|
|
||||||
}).Error("Failed to create http trigger")
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,6 +321,7 @@ func (c *Client) HTTPTriggerList() ([]fission.HTTPTrigger, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -361,12 +349,8 @@ func (c *Client) EnvironmentCreate(env *fission.Environment) (*fission.Metadata,
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleCreateResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"name": env.Metadata.Name,
|
|
||||||
"err": err,
|
|
||||||
}).Error("Failed to create environment")
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,6 +429,7 @@ func (c *Client) EnvironmentList() ([]fission.Environment, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -472,7 +457,7 @@ func (c *Client) WatchCreate(w *fission.Watch) (*fission.Metadata, error) {
|
|||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleCreateResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -532,6 +517,7 @@ func (c *Client) WatchList() ([]fission.Watch, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := c.handleResponse(resp)
|
body, err := c.handleResponse(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -546,3 +532,224 @@ func (c *Client) WatchList() ([]fission.Watch, error) {
|
|||||||
|
|
||||||
return watches, err
|
return watches, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) TimeTriggerCreate(t *fission.TimeTrigger) (*fission.Metadata, error) {
|
||||||
|
reqbody, err := json.Marshal(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Post(c.url("triggers/time"), "application/json", bytes.NewReader(reqbody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := c.handleCreateResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var m fission.Metadata
|
||||||
|
err = json.Unmarshal(body, &m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) TimeTriggerGet(m *fission.Metadata) (*fission.TimeTrigger, error) {
|
||||||
|
relativeUrl := fmt.Sprintf("triggers/time/%v", m.Name)
|
||||||
|
if len(m.Uid) > 0 {
|
||||||
|
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(c.url(relativeUrl))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := c.handleResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var t fission.TimeTrigger
|
||||||
|
err = json.Unmarshal(body, &t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) TimeTriggerUpdate(t *fission.TimeTrigger) (*fission.Metadata, error) {
|
||||||
|
reqbody, err := json.Marshal(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
relativeUrl := fmt.Sprintf("triggers/time/%v", t.Metadata.Name)
|
||||||
|
|
||||||
|
resp, err := c.put(relativeUrl, "application/json", reqbody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := c.handleResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var m fission.Metadata
|
||||||
|
err = json.Unmarshal(body, &m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) TimeTriggerDelete(m *fission.Metadata) error {
|
||||||
|
relativeUrl := fmt.Sprintf("triggers/time/%v", m.Name)
|
||||||
|
if len(m.Uid) > 0 {
|
||||||
|
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
|
||||||
|
}
|
||||||
|
err := c.delete(relativeUrl)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) TimeTriggerList() ([]fission.TimeTrigger, error) {
|
||||||
|
resp, err := http.Get(c.url("triggers/time"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := c.handleResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
triggers := make([]fission.TimeTrigger, 0)
|
||||||
|
err = json.Unmarshal(body, &triggers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return triggers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) MessageQueueTriggerCreate(t *fission.MessageQueueTrigger) (*fission.Metadata, error) {
|
||||||
|
reqbody, err := json.Marshal(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Post(c.url("triggers/messagequeue"), "application/json", bytes.NewReader(reqbody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := c.handleCreateResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var m fission.Metadata
|
||||||
|
err = json.Unmarshal(body, &m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) MessageQueueTriggerGet(m *fission.Metadata) (*fission.MessageQueueTrigger, error) {
|
||||||
|
relativeUrl := fmt.Sprintf("triggers/messagequeue/%v", m.Name)
|
||||||
|
if len(m.Uid) > 0 {
|
||||||
|
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(c.url(relativeUrl))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := c.handleResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var t fission.MessageQueueTrigger
|
||||||
|
err = json.Unmarshal(body, &t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) MessageQueueTriggerUpdate(mqTrigger *fission.MessageQueueTrigger) (*fission.Metadata, error) {
|
||||||
|
reqbody, err := json.Marshal(mqTrigger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
relativeUrl := fmt.Sprintf("triggers/messagequeue/%v", mqTrigger.Metadata.Name)
|
||||||
|
|
||||||
|
resp, err := c.put(relativeUrl, "application/json", reqbody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := c.handleResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var m fission.Metadata
|
||||||
|
err = json.Unmarshal(body, &m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) MessageQueueTriggerDelete(m *fission.Metadata) error {
|
||||||
|
relativeUrl := fmt.Sprintf("triggers/messagequeue/%v", m.Name)
|
||||||
|
if len(m.Uid) > 0 {
|
||||||
|
relativeUrl += fmt.Sprintf("?uid=%v", m.Uid)
|
||||||
|
}
|
||||||
|
err := c.delete(relativeUrl)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) MessageQueueTriggerList(mqType string) ([]fission.MessageQueueTrigger, error) {
|
||||||
|
relativeUrl := "triggers/messagequeue"
|
||||||
|
if len(mqType) > 0 {
|
||||||
|
relativeUrl += fmt.Sprintf("?mqtype=%v", mqType)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(c.url(relativeUrl))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := c.handleResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
triggers := make([]fission.MessageQueueTrigger, 0)
|
||||||
|
err = json.Unmarshal(body, &triggers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return triggers, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/fission/fission"
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
@@ -47,8 +47,8 @@ func (api *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
defer r.Body.Close()
|
|
||||||
|
|
||||||
var env fission.Environment
|
var env fission.Environment
|
||||||
err = json.Unmarshal(body, &env)
|
err = json.Unmarshal(body, &env)
|
||||||
@@ -71,6 +71,7 @@ func (api *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
api.respondWithSuccess(w, resp)
|
api.respondWithSuccess(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +104,7 @@ func (api *API) EnvironmentApiUpdate(w http.ResponseWriter, r *http.Request) {
|
|||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var env fission.Environment
|
var env fission.Environment
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type requestType int
|
type requestType int
|
||||||
@@ -62,6 +62,9 @@ func MakeFileStore(path string) *FileStore {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Printf("Created directory %v", path)
|
log.Printf("Created directory %v", path)
|
||||||
|
} else {
|
||||||
|
log.Printf("Unknown error initializing filestore: %v", err)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +89,9 @@ func (fs *FileStore) fileStoreService() {
|
|||||||
response.error = ioutil.WriteFile(path.Join(fs.root, req.fileName), req.fileContents, 0600)
|
response.error = ioutil.WriteFile(path.Join(fs.root, req.fileName), req.fileContents, 0600)
|
||||||
case DELETE:
|
case DELETE:
|
||||||
response.error = os.Remove(path.Join(fs.root, req.fileName))
|
response.error = os.Remove(path.Join(fs.root, req.fileName))
|
||||||
|
if os.IsNotExist(response.error) {
|
||||||
|
response.error = nil
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
log.Panic("bad request")
|
log.Panic("bad request")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,16 @@ limitations under the License.
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"encoding/json"
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"encoding/base64"
|
|
||||||
"github.com/fission/fission"
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ func (api *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var f fission.Function
|
var f fission.Function
|
||||||
@@ -77,6 +80,7 @@ func (api *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
api.respondWithSuccess(w, resp)
|
api.respondWithSuccess(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +119,7 @@ func (api *API) FunctionApiUpdate(w http.ResponseWriter, r *http.Request) {
|
|||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var f fission.Function
|
var f fission.Function
|
||||||
@@ -170,3 +175,33 @@ func (api *API) FunctionApiDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
api.respondWithSuccess(w, []byte(""))
|
api.respondWithSuccess(w, []byte(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FunctionLogsApiPost establishes a proxy server to log database, and redirect
|
||||||
|
// query command send from client to database then proxy back the db response.
|
||||||
|
func (api *API) FunctionLogsApiPost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
// get dbType from url
|
||||||
|
dbType := vars["dbType"]
|
||||||
|
|
||||||
|
// find correspond db http url
|
||||||
|
dbCnf := api.getLogDBConfig(dbType)
|
||||||
|
|
||||||
|
svcUrl, err := url.Parse(dbCnf.httpURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to establish proxy server for function logs: %v", err)
|
||||||
|
}
|
||||||
|
// set up proxy server director
|
||||||
|
director := func(req *http.Request) {
|
||||||
|
// only replace url Scheme and Host to remote influxDB
|
||||||
|
// and leave query string intact
|
||||||
|
req.URL.Scheme = svcUrl.Scheme
|
||||||
|
req.URL.Host = svcUrl.Host
|
||||||
|
req.URL.Path = svcUrl.Path
|
||||||
|
// set up http basic auth for database authentication
|
||||||
|
req.SetBasicAuth(dbCnf.username, dbCnf.password)
|
||||||
|
}
|
||||||
|
proxy := &httputil.ReverseProxy{
|
||||||
|
Director: director,
|
||||||
|
}
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/fission/fission"
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
@@ -108,7 +108,26 @@ func (fs *FunctionStore) Delete(m fission.Metadata) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return fs.ResourceStore.delete(typeName, m.Name)
|
|
||||||
|
bufs, err := fs.ResourceStore.getAll("file/" + m.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(bufs) == 0 {
|
||||||
|
return fs.ResourceStore.delete(typeName, m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fnew, err := fs.Get(&fission.Metadata{Name: m.Name})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
latestUid := bufs[len(bufs)-1] // function always tracks the latest version of code
|
||||||
|
if latestUid == fnew.Uid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fnew.Uid = latestUid
|
||||||
|
return fs.ResourceStore.update(fnew)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *FunctionStore) List() ([]fission.Function, error) {
|
func (fs *FunctionStore) List() ([]fission.Function, error) {
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/fission/fission"
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
@@ -47,6 +47,7 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var t fission.HTTPTrigger
|
var t fission.HTTPTrigger
|
||||||
@@ -56,6 +57,20 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
triggers, err := api.HTTPTriggerStore.List()
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, url := range triggers {
|
||||||
|
if url.UrlPattern == t.UrlPattern && url.Method == t.Method {
|
||||||
|
err = fission.MakeError(fission.ErrorNameExists,
|
||||||
|
"HTTPTrigger with same URL & method already exists")
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uid, err := api.HTTPTriggerStore.Create(&t)
|
uid, err := api.HTTPTriggerStore.Create(&t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
@@ -69,6 +84,7 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
api.respondWithSuccess(w, resp)
|
api.respondWithSuccess(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +117,7 @@ func (api *API) HTTPTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
|
|||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var t fission.HTTPTrigger
|
var t fission.HTTPTrigger
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Fission Authors.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/fission/fission"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *API) MessageQueueTriggerApiList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
mqType := r.FormValue("mqtype")
|
||||||
|
triggers, err := api.MessageQueueTriggerStore.List(mqType)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := json.Marshal(triggers)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api.respondWithSuccess(w, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) MessageQueueApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var mqTrigger fission.MessageQueueTrigger
|
||||||
|
err = json.Unmarshal(body, &mqTrigger)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// trigger name must not conflict with any other trigger
|
||||||
|
// even they are different message queue type
|
||||||
|
triggers, err := api.MessageQueueTriggerStore.List("")
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, trigger := range triggers {
|
||||||
|
if trigger.Name == mqTrigger.Name {
|
||||||
|
err = fission.MakeError(fission.ErrorNameExists,
|
||||||
|
"Message queue trigger with same name already exists")
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save trigger info
|
||||||
|
uid, err := api.MessageQueueTriggerStore.Create(&mqTrigger)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mqTriggerMeta := fission.Metadata{Name: mqTrigger.Metadata.Name, Uid: uid}
|
||||||
|
resp, err := json.Marshal(mqTriggerMeta)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
api.respondWithSuccess(w, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) MessageQueueApiGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
mqTriggerMeta := fission.Metadata{
|
||||||
|
Name: vars["mqTrigger"],
|
||||||
|
Uid: r.FormValue("uid"), // empty if uid is absent
|
||||||
|
}
|
||||||
|
mqTrigger, err := api.MessageQueueTriggerStore.Get(&mqTriggerMeta)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := json.Marshal(mqTrigger)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api.respondWithSuccess(w, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) MessageQueueApiUpdate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
mqtName := vars["mqTrigger"]
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var mqTrigger fission.MessageQueueTrigger
|
||||||
|
err = json.Unmarshal(body, &mqTrigger)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if mqtName != mqTrigger.Metadata.Name {
|
||||||
|
err = fission.MakeError(fission.ErrorInvalidArgument, "Message queue trigger name doesn't match URL")
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := api.MessageQueueTriggerStore.Update(&mqTrigger)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mqTriggerMeta := fission.Metadata{Name: mqTrigger.Metadata.Name, Uid: uid}
|
||||||
|
resp, err := json.Marshal(mqTriggerMeta)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api.respondWithSuccess(w, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) MessageQueueApiDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
mqTriggerMeta := fission.Metadata{
|
||||||
|
Name: vars["mqTrigger"],
|
||||||
|
Uid: r.FormValue("uid"), // empty if uid is absent
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(mqTriggerMeta.Uid) == 0 {
|
||||||
|
log.WithFields(log.Fields{"mqTrigger": mqTriggerMeta.Name}).Info("Deleting all versions")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := api.MessageQueueTriggerStore.Delete(mqTriggerMeta)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api.respondWithSuccess(w, []byte(""))
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
CopyrigmqTrigger 2017 The Fission Authors.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
mqTriggertp://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fission/fission"
|
||||||
|
uuid "github.com/satori/go.uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MessageQueueTriggerStore struct {
|
||||||
|
ResourceStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mqs *MessageQueueTriggerStore) Create(mqTrigger *fission.MessageQueueTrigger) (string, error) {
|
||||||
|
mqTrigger.Metadata.Uid = uuid.NewV4().String()
|
||||||
|
return mqTrigger.Metadata.Uid, mqs.ResourceStore.create(mqTrigger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mqs *MessageQueueTriggerStore) Get(m *fission.Metadata) (*fission.MessageQueueTrigger, error) {
|
||||||
|
var mqTrigger fission.MessageQueueTrigger
|
||||||
|
err := mqs.ResourceStore.read(m.Name, &mqTrigger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &mqTrigger, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mqs *MessageQueueTriggerStore) Update(mqTrigger *fission.MessageQueueTrigger) (string, error) {
|
||||||
|
mqTrigger.Metadata.Uid = uuid.NewV4().String()
|
||||||
|
return mqTrigger.Metadata.Uid, mqs.ResourceStore.update(mqTrigger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mqs *MessageQueueTriggerStore) Delete(m fission.Metadata) error {
|
||||||
|
typeName, err := getTypeName(fission.MessageQueueTrigger{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return mqs.ResourceStore.delete(typeName, m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mqs *MessageQueueTriggerStore) List(mqType string) ([]fission.MessageQueueTrigger, error) {
|
||||||
|
typeName, err := getTypeName(fission.MessageQueueTrigger{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bufs, err := mqs.ResourceStore.getAll(typeName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
triggers := make([]fission.MessageQueueTrigger, 0, len(bufs))
|
||||||
|
js := JsonSerializer{}
|
||||||
|
for _, buf := range bufs {
|
||||||
|
var mqTrigger fission.MessageQueueTrigger
|
||||||
|
err = js.deserialize([]byte(buf), &mqTrigger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(mqType) > 0 && mqType != mqTrigger.MessageQueueType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
triggers = append(triggers, mqTrigger)
|
||||||
|
}
|
||||||
|
|
||||||
|
return triggers, nil
|
||||||
|
}
|
||||||
+48
-15
@@ -18,13 +18,17 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/coreos/etcd/client"
|
"github.com/coreos/etcd/client"
|
||||||
"github.com/satori/go.uuid"
|
"github.com/satori/go.uuid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -93,7 +97,7 @@ func (rs *ResourceStore) create(r resource) error {
|
|||||||
|
|
||||||
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
|
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
|
||||||
&client.SetOptions{PrevExist: client.PrevNoExist})
|
&client.SetOptions{PrevExist: client.PrevNoExist})
|
||||||
return err
|
return handleEtcdErrorForResource(err, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *ResourceStore) read(rkey string, res resource) error {
|
func (rs *ResourceStore) read(rkey string, res resource) error {
|
||||||
@@ -105,7 +109,7 @@ func (rs *ResourceStore) read(rkey string, res resource) error {
|
|||||||
|
|
||||||
resp, err := rs.KeysAPI.Get(context.Background(), key, nil)
|
resp, err := rs.KeysAPI.Get(context.Background(), key, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return handleEtcdError(err, typName, rkey)
|
||||||
}
|
}
|
||||||
return rs.serializer.deserialize([]byte(resp.Node.Value), res)
|
return rs.serializer.deserialize([]byte(resp.Node.Value), res)
|
||||||
}
|
}
|
||||||
@@ -123,24 +127,24 @@ func (rs *ResourceStore) update(r resource) error {
|
|||||||
|
|
||||||
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
|
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
|
||||||
&client.SetOptions{PrevExist: client.PrevExist})
|
&client.SetOptions{PrevExist: client.PrevExist})
|
||||||
return err
|
return handleEtcdErrorForResource(err, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *ResourceStore) delete(typename, rkey string) error {
|
func (rs *ResourceStore) delete(typename, rkey string) error {
|
||||||
key := typename + "/" + rkey
|
key := typename + "/" + rkey
|
||||||
_, err := rs.KeysAPI.Delete(context.Background(), key, nil) // ignore response
|
_, err := rs.KeysAPI.Delete(context.Background(), key, nil) // ignore response
|
||||||
return err
|
return handleEtcdError(err, typename, rkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAll finds all entries under key. If none or found or key
|
// getAll finds all entries under key. If none or found or key
|
||||||
// doesn't exist, returns an empty slice.
|
// doesn't exist, returns an empty slice.
|
||||||
func (rs *ResourceStore) getAll(key string) ([]string, error) {
|
func (rs *ResourceStore) getAll(key string) ([]string, error) {
|
||||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Recursive: true})
|
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Recursive: true, Sort: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if client.IsKeyNotFound(err) {
|
if client.IsKeyNotFound(err) {
|
||||||
return []string{}, nil
|
return []string{}, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, handleEtcdError(err, "", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make([]string, 0, len(resp.Node.Nodes))
|
res := make([]string, 0, len(resp.Node.Nodes))
|
||||||
@@ -162,7 +166,7 @@ func (rs *ResourceStore) writeFile(parentKey string, contents []byte) (string, s
|
|||||||
resp, err := rs.KeysAPI.CreateInOrder(context.Background(), parentKey, uid, nil)
|
resp, err := rs.KeysAPI.CreateInOrder(context.Background(), parentKey, uid, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = rs.FileStore.delete(uid)
|
_ = rs.FileStore.delete(uid)
|
||||||
return "", "", err
|
return "", "", handleEtcdError(err, "file", parentKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.Node.Key, uid, nil
|
return resp.Node.Key, uid, nil
|
||||||
@@ -172,7 +176,7 @@ func (rs *ResourceStore) readFile(key string, uid *string) ([]byte, error) {
|
|||||||
key = "file/" + key
|
key = "file/" + key
|
||||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, handleEtcdError(err, "file", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if uid == nil {
|
if uid == nil {
|
||||||
@@ -201,7 +205,7 @@ func (rs *ResourceStore) deleteFile(key string, uid string) error {
|
|||||||
key = "file/" + key
|
key = "file/" + key
|
||||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return handleEtcdError(err, "file", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
var node *client.Node
|
var node *client.Node
|
||||||
@@ -222,12 +226,12 @@ func (rs *ResourceStore) deleteFile(key string, uid string) error {
|
|||||||
|
|
||||||
_, err = rs.KeysAPI.Delete(context.Background(), node.Key, nil)
|
_, err = rs.KeysAPI.Delete(context.Background(), node.Key, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return handleEtcdError(err, "", node.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(resp.Node.Nodes) == 1 {
|
if len(resp.Node.Nodes) == 1 {
|
||||||
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
|
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
|
||||||
return err
|
return handleEtcdError(err, "file", key)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -236,7 +240,7 @@ func (rs *ResourceStore) deleteAllFiles(key string) error {
|
|||||||
key = "file/" + key
|
key = "file/" + key
|
||||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return handleEtcdError(err, "file", key)
|
||||||
}
|
}
|
||||||
for _, u := range resp.Node.Nodes {
|
for _, u := range resp.Node.Nodes {
|
||||||
err = rs.FileStore.delete(u.Value)
|
err = rs.FileStore.delete(u.Value)
|
||||||
@@ -246,9 +250,38 @@ func (rs *ResourceStore) deleteAllFiles(key string) error {
|
|||||||
|
|
||||||
_, err = rs.KeysAPI.Delete(context.Background(), u.Key, nil)
|
_, err = rs.KeysAPI.Delete(context.Background(), u.Key, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return handleEtcdError(err, "", u.Key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
|
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
|
||||||
return err
|
return handleEtcdError(err, "file", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleEtcdErrorForResource(e error, r resource) error {
|
||||||
|
resourceType, _ := getTypeName(r)
|
||||||
|
return handleEtcdError(e, resourceType, r.Key())
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleEtcdError(e error, resourceType string, resourceKey string) error {
|
||||||
|
ee, ok := e.(client.Error)
|
||||||
|
if !ok {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
code := fission.ErrorInternal
|
||||||
|
msg := ee.Error()
|
||||||
|
|
||||||
|
if len(resourceType) > 0 {
|
||||||
|
resourceType = strings.ToLower(resourceType) + " "
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: handle any other etcd error codes we care about
|
||||||
|
switch ee.Code {
|
||||||
|
case client.ErrorCodeNodeExist:
|
||||||
|
code = fission.ErrorNameExists
|
||||||
|
msg = fmt.Sprintf("%s'%s' already exists", resourceType, resourceKey)
|
||||||
|
case client.ErrorCodeKeyNotFound:
|
||||||
|
code = fission.ErrorNotFound
|
||||||
|
msg = fmt.Sprintf("%s'%s' does not exist", resourceType, resourceKey)
|
||||||
|
}
|
||||||
|
return fission.MakeError(code, msg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Fission Authors.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/robfig/cron"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/fission/fission"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *API) TimeTriggerApiList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
triggers, err := api.TimeTriggerStore.List()
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := json.Marshal(triggers)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.respondWithSuccess(w, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) TimeTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var t fission.TimeTrigger
|
||||||
|
err = json.Unmarshal(body, &t)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
triggers, err := api.TimeTriggerStore.List()
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, trigger := range triggers {
|
||||||
|
if trigger.Name == t.Name {
|
||||||
|
err = fission.MakeError(fission.ErrorNameExists,
|
||||||
|
"TimeTrigger with same name already exists")
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = cron.Parse(t.Cron)
|
||||||
|
if err != nil {
|
||||||
|
err = fission.MakeError(fission.ErrorInvalidArgument, "TimeTrigger cron spec is not valid")
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := api.TimeTriggerStore.Create(&t)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &fission.Metadata{Name: t.Metadata.Name, Uid: uid}
|
||||||
|
resp, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
api.respondWithSuccess(w, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) TimeTriggerApiGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var m fission.Metadata
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
m.Name = vars["timeTrigger"]
|
||||||
|
m.Uid = r.FormValue("uid") // empty if uid is absent
|
||||||
|
|
||||||
|
t, err := api.TimeTriggerStore.Get(&m)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := json.Marshal(t)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.respondWithSuccess(w, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) TimeTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
name := vars["timeTrigger"]
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var t fission.TimeTrigger
|
||||||
|
err = json.Unmarshal(body, &t)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != t.Metadata.Name {
|
||||||
|
err = fission.MakeError(fission.ErrorInvalidArgument, "TimeTrigger name doesn't match URL")
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = cron.Parse(t.Cron)
|
||||||
|
if err != nil {
|
||||||
|
err = fission.MakeError(fission.ErrorInvalidArgument, "TimeTrigger cron spec is not valid")
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := api.TimeTriggerStore.Update(&t)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m := &fission.Metadata{Name: t.Metadata.Name, Uid: uid}
|
||||||
|
resp, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api.respondWithSuccess(w, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) TimeTriggerApiDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
var m fission.Metadata
|
||||||
|
m.Name = vars["timeTrigger"]
|
||||||
|
|
||||||
|
m.Uid = r.FormValue("uid") // empty if uid is absent
|
||||||
|
if len(m.Uid) == 0 {
|
||||||
|
log.WithFields(log.Fields{"timeTrigger": m.Name}).Info("Deleting all versions")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := api.TimeTriggerStore.Delete(m)
|
||||||
|
if err != nil {
|
||||||
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
api.respondWithSuccess(w, []byte(""))
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
Copyrigtt 2017 The Fission Authors.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
tttp://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/satori/go.uuid"
|
||||||
|
|
||||||
|
"github.com/fission/fission"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimeTriggerStore struct {
|
||||||
|
ResourceStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tts *TimeTriggerStore) Create(tt *fission.TimeTrigger) (string, error) {
|
||||||
|
tt.Metadata.Uid = uuid.NewV4().String()
|
||||||
|
return tt.Metadata.Uid, tts.ResourceStore.create(tt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tts *TimeTriggerStore) Get(m *fission.Metadata) (*fission.TimeTrigger, error) {
|
||||||
|
var tt fission.TimeTrigger
|
||||||
|
err := tts.ResourceStore.read(m.Name, &tt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &tt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tts *TimeTriggerStore) Update(tt *fission.TimeTrigger) (string, error) {
|
||||||
|
tt.Metadata.Uid = uuid.NewV4().String()
|
||||||
|
return tt.Metadata.Uid, tts.ResourceStore.update(tt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tts *TimeTriggerStore) Delete(m fission.Metadata) error {
|
||||||
|
typeName, err := getTypeName(fission.TimeTrigger{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tts.ResourceStore.delete(typeName, m.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tts *TimeTriggerStore) List() ([]fission.TimeTrigger, error) {
|
||||||
|
typeName, err := getTypeName(fission.TimeTrigger{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bufs, err := tts.ResourceStore.getAll(typeName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
triggers := make([]fission.TimeTrigger, 0, len(bufs))
|
||||||
|
js := JsonSerializer{}
|
||||||
|
for _, buf := range bufs {
|
||||||
|
var tt fission.TimeTrigger
|
||||||
|
err = js.deserialize([]byte(buf), &tt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
triggers = append(triggers, tt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return triggers, nil
|
||||||
|
}
|
||||||
@@ -21,8 +21,8 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/fission/fission"
|
"github.com/fission/fission"
|
||||||
)
|
)
|
||||||
@@ -47,6 +47,7 @@ func (api *API) WatchApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
api.respondWithError(w, err)
|
api.respondWithError(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var watch fission.Watch
|
var watch fission.Watch
|
||||||
@@ -71,6 +72,7 @@ func (api *API) WatchApiCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
api.respondWithSuccess(w, resp)
|
api.respondWithSuccess(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
bin/*
|
||||||
|
obj/*
|
||||||
|
out/*
|
||||||
|
.vscode/*
|
||||||
|
project.lock*
|
||||||
|
TODO_dotnet
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
FROM microsoft/dotnet:1.1.0-runtime
|
||||||
|
|
||||||
|
WORKDIR /fission-workdir
|
||||||
|
COPY out .
|
||||||
|
EXPOSE 8888
|
||||||
|
|
||||||
|
ENTRYPOINT ["dotnet"]
|
||||||
|
|
||||||
|
CMD ["fission-dotnet.dll"]
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using Fission.DotNetCore.Compiler;
|
||||||
|
using Fission.DotNetCore.Api;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Nancy;
|
||||||
|
using System.IO;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Fission.DotNetCore
|
||||||
|
{
|
||||||
|
public class ExecutorModule : NancyModule
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
private const string CODE_PATH = "/tmp/func.cs";
|
||||||
|
#else
|
||||||
|
private const string CODE_PATH = "/userfunc/user";
|
||||||
|
#endif
|
||||||
|
private static Function _userFunc;
|
||||||
|
private static Logger _logger = new Logger();
|
||||||
|
|
||||||
|
public ExecutorModule()
|
||||||
|
{
|
||||||
|
Post("/specialize", args => Specialize());
|
||||||
|
Get("/", _ => Run());
|
||||||
|
Post("/", _ => Run());
|
||||||
|
Put("/", _ => Run());
|
||||||
|
Head("/", _ => Run());
|
||||||
|
Options("/", _ => Run());
|
||||||
|
Delete("/", _ => Run());
|
||||||
|
}
|
||||||
|
|
||||||
|
private object Specialize()
|
||||||
|
{
|
||||||
|
var errors = new List<string>();
|
||||||
|
if (File.Exists(CODE_PATH))
|
||||||
|
{
|
||||||
|
var code = File.ReadAllText(CODE_PATH);
|
||||||
|
_userFunc = FissionCompiler.Compile(code, out errors);
|
||||||
|
if (_userFunc == null)
|
||||||
|
{
|
||||||
|
var errstr = string.Join(Environment.NewLine, errors);
|
||||||
|
_logger.WriteError(errstr);
|
||||||
|
var response = (Response)errstr;
|
||||||
|
response.StatusCode = HttpStatusCode.InternalServerError;
|
||||||
|
return response;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var errstr = $"Unable to locate code at '{CODE_PATH}'";
|
||||||
|
_logger.WriteError(errstr);
|
||||||
|
var response = (Response)errstr;
|
||||||
|
response.StatusCode = HttpStatusCode.InternalServerError;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object Run()
|
||||||
|
{
|
||||||
|
if (_userFunc == null)
|
||||||
|
{
|
||||||
|
var response = (Response)"Generic container: no requests supported";
|
||||||
|
response.StatusCode = HttpStatusCode.InternalServerError;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _userFunc.Invoke(FissionContext.Build(Request, new Logger()));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.WriteError(e.ToString());
|
||||||
|
var response = (Response)e.Message;
|
||||||
|
response.StatusCode = HttpStatusCode.BadRequest;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.Emit;
|
||||||
|
|
||||||
|
namespace Fission.DotNetCore.Compiler
|
||||||
|
{
|
||||||
|
//adapted from this article http://www.tugberkugurlu.com/archive/compiling-c-sharp-code-into-memory-and-executing-it-with-roslyn
|
||||||
|
class FissionCompiler
|
||||||
|
{
|
||||||
|
public static Function Compile(string code, out List<string> errors)
|
||||||
|
{
|
||||||
|
errors = new List<string>();
|
||||||
|
|
||||||
|
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
|
||||||
|
|
||||||
|
string assemblyName = Path.GetRandomFileName();
|
||||||
|
|
||||||
|
var coreDir = Directory.GetParent(typeof(Enumerable).GetTypeInfo().Assembly.Location);
|
||||||
|
|
||||||
|
List<MetadataReference> references = new List<MetadataReference>
|
||||||
|
{
|
||||||
|
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "mscorlib.dll"),
|
||||||
|
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
|
||||||
|
MetadataReference.CreateFromFile(Assembly.GetEntryAssembly().Location),
|
||||||
|
MetadataReference.CreateFromFile(typeof(System.Runtime.Serialization.Json.DataContractJsonSerializer).GetTypeInfo().Assembly.Location)
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var referencedAssembly in Assembly.GetEntryAssembly().GetReferencedAssemblies())
|
||||||
|
{
|
||||||
|
var assembly = Assembly.Load(referencedAssembly);
|
||||||
|
references.Add(MetadataReference.CreateFromFile(assembly.Location));
|
||||||
|
}
|
||||||
|
|
||||||
|
CSharpCompilation compilation = CSharpCompilation.Create(
|
||||||
|
assemblyName,
|
||||||
|
syntaxTrees: new[] { syntaxTree },
|
||||||
|
references: references,
|
||||||
|
options: new CSharpCompilationOptions(
|
||||||
|
OutputKind.DynamicallyLinkedLibrary,
|
||||||
|
optimizationLevel: OptimizationLevel.Release));
|
||||||
|
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
EmitResult result = compilation.Emit(ms);
|
||||||
|
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
|
||||||
|
diagnostic.IsWarningAsError ||
|
||||||
|
diagnostic.Severity == DiagnosticSeverity.Error).ToList();
|
||||||
|
|
||||||
|
foreach (Diagnostic diagnostic in failures)
|
||||||
|
{
|
||||||
|
errors.Add($"{diagnostic.Id}: {diagnostic.GetMessage()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ms.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
Assembly assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
|
||||||
|
var type = assembly.GetType("FissionFunction");
|
||||||
|
var info = type.GetMember("Execute").First() as MethodInfo;
|
||||||
|
return new Function(assembly, type, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Text;
|
||||||
|
using Nancy;
|
||||||
|
|
||||||
|
namespace Fission.DotNetCore.Api
|
||||||
|
{
|
||||||
|
public class FissionContext
|
||||||
|
{
|
||||||
|
public FissionContext(Dictionary<string, object> args, Logger logger, FissionHttpRequest request)
|
||||||
|
{
|
||||||
|
if (args == null) throw new ArgumentNullException(nameof(args));
|
||||||
|
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||||
|
if (request == null) throw new ArgumentNullException(nameof(request));
|
||||||
|
Arguments = args;
|
||||||
|
Logger = logger;
|
||||||
|
Request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, object> Arguments { get; private set; }
|
||||||
|
|
||||||
|
public FissionHttpRequest Request { get; private set; }
|
||||||
|
|
||||||
|
public Logger Logger { get; private set; }
|
||||||
|
|
||||||
|
public static FissionContext Build(Request request, Logger logger)
|
||||||
|
{
|
||||||
|
return new FissionContext(((DynamicDictionary)request.Query).ToDictionary(),
|
||||||
|
logger,
|
||||||
|
new FissionHttpRequest(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Logger
|
||||||
|
{
|
||||||
|
public void Write(Severity severity, string format, params object[] args)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{DateTime.Now.ToString("MM/dd/yy H:mm:ss zzz")} {severity}: " + format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteInfo(string format, params object[] args)
|
||||||
|
{
|
||||||
|
Write(Severity.Info, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteWarning(string format, params object[] args)
|
||||||
|
{
|
||||||
|
Write(Severity.Warning, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteError(string format, params object[] args)
|
||||||
|
{
|
||||||
|
Write(Severity.Error, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteCritical(string format, params object[] args)
|
||||||
|
{
|
||||||
|
Write(Severity.Critical, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteVerbose(string format, params object[] args)
|
||||||
|
{
|
||||||
|
Write(Severity.Verbose, format, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Severity
|
||||||
|
{
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
Critical,
|
||||||
|
Verbose
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FissionHttpRequest
|
||||||
|
{
|
||||||
|
private readonly Request _request;
|
||||||
|
internal FissionHttpRequest(Request request)
|
||||||
|
{
|
||||||
|
if (request == null) throw new ArgumentNullException(nameof(request));
|
||||||
|
_request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream Body { get { return _request.Body; } }
|
||||||
|
|
||||||
|
public string BodyAsString()
|
||||||
|
{
|
||||||
|
int length = (int)_request.Body.Length;
|
||||||
|
byte[] data = new byte[length];
|
||||||
|
_request.Body.Read(data, 0, length);
|
||||||
|
return Encoding.UTF8.GetString(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, IEnumerable<string>> Headers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var headers = new Dictionary<string, IEnumerable<string>>();
|
||||||
|
foreach (var kv in _request.Headers)
|
||||||
|
{
|
||||||
|
headers.Add(kv.Key, kv.Value);
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate Certificate { get { return _request.ClientCertificate; } }
|
||||||
|
public string Url { get { return _request.Url.ToString(); } }
|
||||||
|
public string Method { get { return _request.Method; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using Fission.DotNetCore.Api;
|
||||||
|
|
||||||
|
namespace Fission.DotNetCore.Compiler
|
||||||
|
{
|
||||||
|
class Function
|
||||||
|
{
|
||||||
|
private readonly Assembly _assembly;
|
||||||
|
private readonly Type _type;
|
||||||
|
private readonly MethodInfo _info;
|
||||||
|
|
||||||
|
public Function(Assembly assembly, Type type, MethodInfo info)
|
||||||
|
{
|
||||||
|
if (info == null) throw new ArgumentNullException(nameof(info));
|
||||||
|
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
|
||||||
|
if (type == null) throw new ArgumentNullException(nameof(type));
|
||||||
|
_assembly = assembly;
|
||||||
|
_type = type;
|
||||||
|
_info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Invoke(FissionContext context)
|
||||||
|
{
|
||||||
|
return _info.Invoke(_assembly.CreateInstance(_type.FullName), new[] { context });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Nancy.Owin;
|
||||||
|
|
||||||
|
namespace Fission.DotNetCore
|
||||||
|
{
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var host = new WebHostBuilder()
|
||||||
|
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||||
|
.UseKestrel()
|
||||||
|
.UseUrls("http://*:8888")
|
||||||
|
.Configure(app => app.UseOwin(x => x.UseNancy()))
|
||||||
|
.Build();
|
||||||
|
host.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
# Fission: dotnet C# Environment
|
||||||
|
|
||||||
|
This is a simple dotnet C# environment for Fission.
|
||||||
|
|
||||||
|
It's a Docker image containing the dotnet 1.1.0 runtime. The image
|
||||||
|
uses Kestrel with Nancy to host the internal web server and uses
|
||||||
|
Roslyn to compile the uploaded code.
|
||||||
|
|
||||||
|
The image supports compiling and running code with types defined in
|
||||||
|
mscorlib and does not at present support other library references.
|
||||||
|
One workaround for this would be to add the references to this project's
|
||||||
|
project.json file and rebuild the container.
|
||||||
|
|
||||||
|
The environment works via convention where you create a C# class
|
||||||
|
called FissionFunction which has a method named Execute taking a single
|
||||||
|
parameter, a FissionContext object.
|
||||||
|
|
||||||
|
The FissionContext object gives access to the arguments and other items
|
||||||
|
like logging. Please see FissionContext.cs for public API.
|
||||||
|
|
||||||
|
Example of simplest possible class to be executed:
|
||||||
|
|
||||||
|
```
|
||||||
|
using System;
|
||||||
|
using Fission.DotNetCore.Api;
|
||||||
|
|
||||||
|
public class FissionFunction {
|
||||||
|
public string Execute(FissionContext context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Please see examples below.
|
||||||
|
|
||||||
|
## Rebuilding and pushing the image
|
||||||
|
|
||||||
|
To rebuild the image you need either a computer with dotnet 1.1.0
|
||||||
|
installed or else you will have to map the source directory into a
|
||||||
|
container containing the dotnet 1.1.0 environment.
|
||||||
|
|
||||||
|
### Locally installed Dotnet 1.1.0
|
||||||
|
|
||||||
|
Simply move to the source directory in a terminal and run the ./build.sh script.
|
||||||
|
|
||||||
|
The script will restore dependencies, compile a release build and
|
||||||
|
and build the container. If you need to change the name of the container
|
||||||
|
simply change it in the script.
|
||||||
|
|
||||||
|
After the build finishes push the new image to a Docker registry using the
|
||||||
|
standard procedure.
|
||||||
|
|
||||||
|
### Build in a container
|
||||||
|
|
||||||
|
Move to the directory containing the source and start the Docker container
|
||||||
|
with dotnet and mount the current directory to a build location:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run -it --rm -v $PWD:/build microsoft/dotnet
|
||||||
|
```
|
||||||
|
|
||||||
|
Move to the build directory inside the container and restore the packages:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd /build
|
||||||
|
dotnet restore
|
||||||
|
log : Restoring packages for /source/project.json...
|
||||||
|
log : Installing System.Net.WebSockets 4.0.0.
|
||||||
|
log : Installing runtime.native.System.IO.Compression 4.1.0.
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Compile and publish a release build of the source to the 'out' folder:
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet publish -c Release -o out
|
||||||
|
Publishing source for .NETCoreApp,Version
|
||||||
|
...
|
||||||
|
```
|
||||||
|
Exit the build container and build the Docker container on the local host:
|
||||||
|
|
||||||
|
```
|
||||||
|
exit
|
||||||
|
docker build -t USER/dotnet-env .
|
||||||
|
```
|
||||||
|
After the build finishes push the new image to a Docker registry using the
|
||||||
|
standard procedure.
|
||||||
|
|
||||||
|
## Echo example
|
||||||
|
|
||||||
|
### Setup fission environment
|
||||||
|
First you need to setup the fission according to your cluster setup as
|
||||||
|
specified here: https://github.com/fission/fission
|
||||||
|
|
||||||
|
|
||||||
|
### Create the class to run
|
||||||
|
|
||||||
|
Secondly you need to create a file /tmp/func.cs containing the following code:
|
||||||
|
|
||||||
|
```
|
||||||
|
using System;
|
||||||
|
using Fission.DotNetCore.Api;
|
||||||
|
|
||||||
|
public class FissionFunction
|
||||||
|
{
|
||||||
|
public string Execute(FissionContext context){
|
||||||
|
context.Logger.WriteInfo("executing.. {0}", context.Arguments["text"]);
|
||||||
|
return (string)context.Arguments["text"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### Run the example
|
||||||
|
|
||||||
|
Lastly to run the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission env create --name dotnet --image fission/dotnet-env
|
||||||
|
|
||||||
|
$ fission function create --name echo --env dotnet --code /tmp/func.cs
|
||||||
|
|
||||||
|
$ fission route create --method GET --url /echo --function echo
|
||||||
|
|
||||||
|
$ curl http://$FISSION_ROUTER/echo?text=hello%20world!
|
||||||
|
hello world
|
||||||
|
```
|
||||||
|
|
||||||
|
## Addition service example
|
||||||
|
|
||||||
|
### Setup fission environment
|
||||||
|
First you need to setup the fission according to your cluster setup as
|
||||||
|
specified here: https://github.com/fission/fission
|
||||||
|
|
||||||
|
|
||||||
|
### Create the class to run
|
||||||
|
|
||||||
|
Secondly you need to create a file /tmp/func.cs containing the following code:
|
||||||
|
|
||||||
|
```
|
||||||
|
using System;
|
||||||
|
using Fission.DotNetCore.Api;
|
||||||
|
|
||||||
|
public class FissionFunction
|
||||||
|
{
|
||||||
|
public string Execute(FissionContext context){
|
||||||
|
var x = Convert.ToInt32(context.Arguments["x"]);
|
||||||
|
var y = Convert.ToInt32(context.Arguments["y"]);
|
||||||
|
return (x+y).ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### Run the example
|
||||||
|
|
||||||
|
Lastly to run the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission env create --name dotnet --image fission/dotnet-env
|
||||||
|
|
||||||
|
$ fission function create --name addition --env dotnet --code /tmp/func.cs
|
||||||
|
|
||||||
|
$ fission route create --method GET --url /add --function addition
|
||||||
|
|
||||||
|
$ curl "http://$FISSION_ROUTER/add?x=30&y=12"
|
||||||
|
42
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing http request information example
|
||||||
|
|
||||||
|
### Setup fission environment
|
||||||
|
First you need to setup the fission according to your cluster setup as
|
||||||
|
specified here: https://github.com/fission/fission
|
||||||
|
|
||||||
|
|
||||||
|
### Create the class to run
|
||||||
|
|
||||||
|
Secondly you need to create a file /tmp/func.cs containing the following code:
|
||||||
|
|
||||||
|
```
|
||||||
|
using System;
|
||||||
|
using Fission.DotNetCore.Api;
|
||||||
|
|
||||||
|
public class FissionFunction
|
||||||
|
{
|
||||||
|
public string Execute(FissionContext context){
|
||||||
|
var buffer = new System.Text.StringBuilder();
|
||||||
|
foreach(var header in context.Request.Headers){
|
||||||
|
buffer.AppendLine(header.Key);
|
||||||
|
foreach(var item in header.Value){
|
||||||
|
buffer.AppendLine($"\t{item}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.AppendLine($"Url: {context.Request.Url}, method: {context.Request.Method}");
|
||||||
|
return buffer.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
### Run the example
|
||||||
|
|
||||||
|
Lastly to run the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission env create --name dotnet --image fission/dotnet-env
|
||||||
|
|
||||||
|
$ fission function create --name httpinfo --env dotnet --code /tmp/func.cs
|
||||||
|
|
||||||
|
$ fission route create --method GET --url /http_info --function httpinfo
|
||||||
|
|
||||||
|
$ curl "http://$FISSION_ROUTER/http_info"
|
||||||
|
Accept
|
||||||
|
*/*;q=1
|
||||||
|
Host
|
||||||
|
fissionserver:8888
|
||||||
|
User-Agent
|
||||||
|
curl/7.47.0
|
||||||
|
Url: http://fissionserver:8888, method: GET
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing http request body example
|
||||||
|
|
||||||
|
### Setup fission environment
|
||||||
|
First you need to setup the fission according to your cluster setup as
|
||||||
|
specified here: https://github.com/fission/fission
|
||||||
|
|
||||||
|
|
||||||
|
### Create the class to run
|
||||||
|
|
||||||
|
Secondly you need to create a file /tmp/func.cs containing the following code:
|
||||||
|
|
||||||
|
```
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.Serialization.Json;
|
||||||
|
using Fission.DotNetCore.Api;
|
||||||
|
|
||||||
|
public class FissionFunction
|
||||||
|
{
|
||||||
|
public string Execute(FissionContext context)
|
||||||
|
{
|
||||||
|
var person = Person.Deserialize(context.Request.Body);
|
||||||
|
return $"Hello, my name is {person.Name} and I am {person.Age} years old.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Person
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public int Age { get; set; }
|
||||||
|
|
||||||
|
public static Person Deserialize(Stream json)
|
||||||
|
{
|
||||||
|
var serializer = new DataContractJsonSerializer(typeof(Person));
|
||||||
|
return (Person)serializer.ReadObject(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
### Run the example
|
||||||
|
|
||||||
|
Lastly to run the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission env create --name dotnet --image fission/dotnet-env
|
||||||
|
|
||||||
|
$ fission function create --name httpbody --env dotnet --code /tmp/func.cs
|
||||||
|
|
||||||
|
$ fission route create --method GET --url /http_body --function httpbody
|
||||||
|
|
||||||
|
$ curl -XPOST "http://$FISSION_ROUTER/http_body" -d '{ "Name":"Arthur", "Age":42}'
|
||||||
|
Hello, my name is Arthur and I am 42 years old.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Developing/debugging the enviroment locally
|
||||||
|
|
||||||
|
The easiest way to debug the environment is to open the directory in
|
||||||
|
Visual Studio Code (VSCode) as that will setup debugger for you the
|
||||||
|
first time.
|
||||||
|
|
||||||
|
Remember to install the excellent extension
|
||||||
|
"C# for Visual Studio Code(powered by OmniSharp)" to get statement completion
|
||||||
|
|
||||||
|
The class ExecutorModule contain preprocessor directive overriding where
|
||||||
|
the input code file should be found:
|
||||||
|
|
||||||
|
```
|
||||||
|
#if DEBUG
|
||||||
|
private const string CODE_PATH = "/tmp/func.cs";
|
||||||
|
#else
|
||||||
|
private const string CODE_PATH = "/userfunc/user";
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
So what you need to do is:
|
||||||
|
1. Open the directory in VSCode.
|
||||||
|
This will prompt restore of packages and query is debugger setup is needed. Accept both prompts.
|
||||||
|
2. Press F5 to start the web server. Set breakpoints etc..
|
||||||
|
3. Add a code file containing valid C# at /tmp/func.cs
|
||||||
|
4. Specialize the service with curl via post
|
||||||
|
```
|
||||||
|
$ curl -XPOST http://localhost:8888/specialize
|
||||||
|
```
|
||||||
|
5. Call your function with curl
|
||||||
|
```
|
||||||
|
$ curl -XGET http://localhost:8888
|
||||||
|
```
|
||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
dotnet restore
|
||||||
|
dotnet publish -c Release -o out
|
||||||
|
docker build -t fission/dotnet-env .
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0.0-*",
|
||||||
|
"buildOptions": {
|
||||||
|
"emitEntryPoint": true,
|
||||||
|
"outputName": "fission-dotnet"
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"netcoreapp1.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.NETCore.App": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"type": "platform"
|
||||||
|
},
|
||||||
|
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
|
||||||
|
"Microsoft.AspNetCore.Owin": "1.0.0",
|
||||||
|
"Nancy": "2.0.0-barneyrubble",
|
||||||
|
"Microsoft.CodeAnalysis.CSharp": "1.3.0-*",
|
||||||
|
"System.Runtime.Loader": "4.0.0-*",
|
||||||
|
"System.Runtime.Serialization.Json": "4.0.2"
|
||||||
|
},
|
||||||
|
"imports": "netstandard1.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
FROM golang:1.8.1
|
||||||
|
|
||||||
|
ENV GOPATH /usr
|
||||||
|
ENV APP ${GOPATH}/src/github.com/fission/fission/environments/go
|
||||||
|
|
||||||
|
ADD context ${APP}/context
|
||||||
|
ADD server.go ${APP}
|
||||||
|
|
||||||
|
WORKDIR ${APP}
|
||||||
|
RUN go get
|
||||||
|
RUN go build -o /server server.go
|
||||||
|
|
||||||
|
ENTRYPOINT ["/server"]
|
||||||
|
EXPOSE 8888
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Fission: Go Environment
|
||||||
|
|
||||||
|
This is the Go environment for Fission.
|
||||||
|
|
||||||
|
It's a Docker image containing a Go 1.8 runtime, along with a dynamic loader.
|
||||||
|
|
||||||
|
## Build this image
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t USER/go-runtime . && docker push USER/go-runtime
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that if you build the runtime, you must also build the go-builder
|
||||||
|
image, to ensure that it's at the same version of go:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd builder && docker build -t USER/go-builder . && docker push USER/go-builder
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the image in fission
|
||||||
|
|
||||||
|
You can add this customized image to fission with "fission env
|
||||||
|
create":
|
||||||
|
|
||||||
|
```
|
||||||
|
fission env create --name go-runtime --image USER/go-runtime
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, if you already have an environment, you can update its image:
|
||||||
|
|
||||||
|
```
|
||||||
|
fission env update --name go-runtime --image USER/go-runtime
|
||||||
|
```
|
||||||
|
|
||||||
|
After this, fission functions that have the env parameter set to the
|
||||||
|
same environment name as this command will use this environment.
|
||||||
|
|
||||||
|
## Creating functions to use this image
|
||||||
|
|
||||||
|
See the [examples README](examples/go/README.md).
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
FROM golang:1.8.1
|
||||||
|
|
||||||
|
ENV GOPATH /usr
|
||||||
|
RUN go get github.com/fission/fission
|
||||||
|
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]
|
||||||
|
then
|
||||||
|
echo "No arguments, building current dir"
|
||||||
|
filename='.'
|
||||||
|
else
|
||||||
|
filename=$1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker run --rm -v $(pwd):/build -w /build fission/go-builder:1.8.1 go build -buildmode=plugin -o function.so $filename && echo "Compiled plugin: function.so. Use 'fission function create' to upload to fission."
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
type (
|
||||||
|
Context map[string]interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func New() Context {
|
||||||
|
ctx := make(map[string]interface{})
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"plugin"
|
||||||
|
|
||||||
|
"github.com/fission/fission/environments/go/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CODE_PATH = "/userfunc/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
var userFunc http.HandlerFunc
|
||||||
|
|
||||||
|
func loadPlugin() http.HandlerFunc {
|
||||||
|
p, err := plugin.Open(CODE_PATH)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
sym, err := p.Lookup("Handler")
|
||||||
|
if err != nil {
|
||||||
|
panic("Entry point not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch h := sym.(type) {
|
||||||
|
case *http.Handler:
|
||||||
|
return (*h).ServeHTTP
|
||||||
|
case *http.HandlerFunc:
|
||||||
|
return *h
|
||||||
|
case func(http.ResponseWriter, *http.Request):
|
||||||
|
return h
|
||||||
|
case func(context.Context, http.ResponseWriter, *http.Request):
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
c := context.New()
|
||||||
|
h(c, w, r)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("Entry point not found: bad type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func specializeHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if userFunc != nil {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
w.Write([]byte("Not a generic container"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := os.Stat(CODE_PATH)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(CODE_PATH + ": not found"))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Specializing ...")
|
||||||
|
userFunc = loadPlugin()
|
||||||
|
fmt.Println("Done")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/specialize", specializeHandler)
|
||||||
|
|
||||||
|
// Generic route -- all http requests go to the user function.
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if userFunc == nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
w.Write([]byte("Generic container: no requests supported"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userFunc(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Println("Listening on 8888 ...")
|
||||||
|
http.ListenAndServe(":8888", nil)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# A docker image for the func container.
|
# A docker image for the func container.
|
||||||
|
|
||||||
FROM node:4-onbuild
|
FROM node:7-onbuild
|
||||||
|
|
||||||
ADD server.js /usr/src/app/server.js
|
ADD server.js /usr/src/app/server.js
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Fission: NodeJS Environment
|
||||||
|
|
||||||
|
This is the NodeJS environment for Fission.
|
||||||
|
|
||||||
|
It's a Docker image containing a NodeJS runtime, along with a dynamic
|
||||||
|
loader. A few common dependencies are included in the package.json
|
||||||
|
file.
|
||||||
|
|
||||||
|
## Customizing this image
|
||||||
|
|
||||||
|
To add package dependencies, edit package.json to add what you need,
|
||||||
|
and rebuild this image (instructions below).
|
||||||
|
|
||||||
|
You also may want to customize what's available to the function in its
|
||||||
|
request context. You can do this by editing `server.js` (see the
|
||||||
|
comment in that file about customizing request context).
|
||||||
|
|
||||||
|
## Rebuilding and pushing the image
|
||||||
|
|
||||||
|
You'll need access to a Docker registry to push the image: you can
|
||||||
|
sign up for Docker hub at hub.docker.com, or use registries from
|
||||||
|
gcr.io, quay.io, etc. Let's assume you're using a docker hub account
|
||||||
|
called USER. Build and push the image to the the registry:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t USER/nodejs-env . && docker push USER/nodejs-env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the image in fission
|
||||||
|
|
||||||
|
You can add this customized image to fission with "fission env
|
||||||
|
create":
|
||||||
|
|
||||||
|
```
|
||||||
|
fission env create --name nodejs --image USER/nodejs-env
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, if you already have an environment, you can update its image:
|
||||||
|
|
||||||
|
```
|
||||||
|
fission env update --name nodejs --image USER/nodejs-env
|
||||||
|
```
|
||||||
|
|
||||||
|
After this, fission functions that have the env parameter set to the
|
||||||
|
same environment name as this command will use this environment.
|
||||||
@@ -1,27 +1,26 @@
|
|||||||
{
|
{
|
||||||
"name": "fission-nodejs-runtime",
|
"name": "fission-nodejs-runtime",
|
||||||
"version": "0.0.0",
|
"version": "0.1.0",
|
||||||
"author": "Soam Vasani",
|
"author": "Soam Vasani",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
"name": "Soam Vasani",
|
"name": "Soam Vasani",
|
||||||
"email": "soamvasani@platform9.com"
|
"email": "soamvasani@platform9.com"
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "NodeJS run container for the fission framework",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.2.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"express": "",
|
|
||||||
"minimist": "",
|
|
||||||
"body-parser": "",
|
|
||||||
"morgan": "",
|
|
||||||
|
|
||||||
"co": "~4.6.0",
|
|
||||||
"request": "",
|
|
||||||
"request-promise": "^1.0.2",
|
|
||||||
"mz": "~2.1.0",
|
|
||||||
"underscore": ">=1.8.3"
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"description": "NodeJS run container for the fission framework",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.6.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "",
|
||||||
|
"co": "~4.6.0",
|
||||||
|
"express": "",
|
||||||
|
"minimist": "",
|
||||||
|
"morgan": "",
|
||||||
|
"mz": "~2.1.0",
|
||||||
|
"request": "^2.81.0",
|
||||||
|
"request-promise-native": "^1.0.3",
|
||||||
|
"underscore": ">=1.8.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
const process = require('process');
|
const process = require('process');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -18,6 +19,16 @@ if (!argv.port) {
|
|||||||
argv.port = 8888;
|
argv.port = 8888;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Node resolves module paths according to a file's location. We load
|
||||||
|
// the file from argv.codepath, but tell users to put dependencies in
|
||||||
|
// the server's package.json; this means the function's dependencies
|
||||||
|
// are in /usr/src/app/node_modules. We could be smarter and have the
|
||||||
|
// function deps in the right place in argv.codepath; but for now we
|
||||||
|
// just symlink the function's node_modules to the server's
|
||||||
|
// node_modules.
|
||||||
|
fs.symlinkSync('/usr/src/app/node_modules', `${path.dirname(argv.codepath)}/node_modules`);
|
||||||
|
|
||||||
// User function. Starts out undefined.
|
// User function. Starts out undefined.
|
||||||
let userFunction;
|
let userFunction;
|
||||||
|
|
||||||
@@ -64,11 +75,13 @@ app.all('/', function (req, res) {
|
|||||||
res.status(500).send("Generic container: no requests supported");
|
res.status(500).send("Generic container: no requests supported");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
request: req,
|
request: req,
|
||||||
response: res
|
response: res
|
||||||
// TODO: context should also have: URL template params, query string
|
// TODO: context should also have: URL template params, query string
|
||||||
};
|
};
|
||||||
|
|
||||||
function callback(status, body, headers) {
|
function callback(status, body, headers) {
|
||||||
if (!status)
|
if (!status)
|
||||||
return;
|
return;
|
||||||
@@ -79,12 +92,32 @@ app.all('/', function (req, res) {
|
|||||||
}
|
}
|
||||||
res.status(status).send(body);
|
res.status(status).send(body);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
userFunction(context, callback);
|
//
|
||||||
} catch(e) {
|
// Customizing the request context
|
||||||
console.log(`Function error: ${e}`);
|
//
|
||||||
callback(500, "Internal server error")
|
// If you want to modify the context to add anything to it,
|
||||||
|
// you can do that here by adding properties to the context.
|
||||||
|
//
|
||||||
|
|
||||||
|
let functionProm;
|
||||||
|
if (userFunction.length === 1) { // One argument (context)
|
||||||
|
// Make sure their function returns a promise
|
||||||
|
Promise.resolve(userFunction(context)).then(function({ status, body, headers }) {
|
||||||
|
callback(status, body, headers);
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.log(`Function error: ${err}`);
|
||||||
|
callback(500, "Internal server error");
|
||||||
|
});
|
||||||
|
} else { // 2 arguments (context, callback)
|
||||||
|
try {
|
||||||
|
userFunction(context, callback);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`Function error: ${err}`);
|
||||||
|
callback(500, "Internal server error");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(argv.port);
|
app.listen(argv.port);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
module.exports = function (context, callback) {
|
module.exports = async function (context) {
|
||||||
console.log("headers=", JSON.stringify(context.request.headers));
|
console.log("headers=", JSON.stringify(context.request.headers));
|
||||||
console.log("body=", JSON.stringify(context.request.body));
|
console.log("body=", JSON.stringify(context.request.body));
|
||||||
|
|
||||||
callback(200, "Hello, world !\n");
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: "Hello, world !\n"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
FROM php:7.1-alpine
|
||||||
|
|
||||||
|
ENV PATH="/root/.composer/vendor/bin:${PATH}" \
|
||||||
|
COMPOSER_ALLOW_SUPERUSER=1
|
||||||
|
|
||||||
|
RUN echo '#!/bin/sh' > /usr/local/bin/apk-install \
|
||||||
|
&& echo 'apk add --update "$@" && rm -rf /var/cache/apk/*' >> /usr/local/bin/apk-install \
|
||||||
|
&& chmod +x /usr/local/bin/apk-install
|
||||||
|
|
||||||
|
RUN echo 'http://dl-4.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories \
|
||||||
|
&& apk update \
|
||||||
|
&& apk-install \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
curl-dev \
|
||||||
|
libcurl \
|
||||||
|
zlib-dev \
|
||||||
|
freetype-dev \
|
||||||
|
jpeg-dev \
|
||||||
|
libjpeg-turbo-dev \
|
||||||
|
postgresql-dev \
|
||||||
|
libmcrypt-dev \
|
||||||
|
libpng-dev \
|
||||||
|
icu-dev \
|
||||||
|
gettext-dev \
|
||||||
|
vim \
|
||||||
|
libxml2-dev \
|
||||||
|
freetype-dev \
|
||||||
|
unzip \
|
||||||
|
libc6-compat \
|
||||||
|
openssl \
|
||||||
|
gcc \
|
||||||
|
autoconf
|
||||||
|
|
||||||
|
RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/
|
||||||
|
|
||||||
|
# Install useful extensions
|
||||||
|
RUN docker-php-ext-install \
|
||||||
|
opcache \
|
||||||
|
bcmath \
|
||||||
|
ctype \
|
||||||
|
curl \
|
||||||
|
dom \
|
||||||
|
iconv \
|
||||||
|
fileinfo \
|
||||||
|
gd \
|
||||||
|
gettext \
|
||||||
|
intl \
|
||||||
|
json \
|
||||||
|
mcrypt \
|
||||||
|
mysqli \
|
||||||
|
pgsql \
|
||||||
|
pcntl \
|
||||||
|
pdo \
|
||||||
|
ftp \
|
||||||
|
pdo_mysql \
|
||||||
|
pdo_pgsql \
|
||||||
|
phar \
|
||||||
|
simplexml \
|
||||||
|
xmlrpc \
|
||||||
|
zip
|
||||||
|
|
||||||
|
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
|
|
||||||
|
COPY . /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN composer install
|
||||||
|
|
||||||
|
EXPOSE 8888
|
||||||
|
|
||||||
|
ENTRYPOINT ["php"]
|
||||||
|
CMD ["-S","0.0.0.0:8888","index.php"]
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Fission: PHP Environment
|
||||||
|
|
||||||
|
This is the PHP environment for Fission.
|
||||||
|
|
||||||
|
It's a Docker image containing a PHP 7.1 runtime. This image use php:7.1-cli base image with the built-in PHP server
|
||||||
|
|
||||||
|
This environment didn't force you to use class or create a main function.
|
||||||
|
|
||||||
|
A few common extensions are included :
|
||||||
|
- curl
|
||||||
|
- gd
|
||||||
|
- json
|
||||||
|
- mcrypt
|
||||||
|
- PDO (pdo_mysql, pdo_pgsql, pdo_sqlite)
|
||||||
|
- OPcache
|
||||||
|
- ftp
|
||||||
|
- iconv
|
||||||
|
- phar
|
||||||
|
- mysqli
|
||||||
|
- pgsql
|
||||||
|
- SimpleXML
|
||||||
|
- xmlrpc
|
||||||
|
- zip
|
||||||
|
|
||||||
|
## Customizing this image
|
||||||
|
|
||||||
|
To add other extensions or packages(composer.json) you need to edit the Dockerfile and rebuild this image (instructions below).
|
||||||
|
|
||||||
|
## Rebuilding and pushing the image
|
||||||
|
|
||||||
|
You'll need access to a Docker registry to push the image: you can
|
||||||
|
sign up for Docker hub at hub.docker.com, or use registries from
|
||||||
|
gcr.io, quay.io, etc. Let's assume you're using a docker hub account
|
||||||
|
called USER. Build and push the image to the the registry:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t USER/php7-env . && docker push USER/php7-env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the image in fission
|
||||||
|
|
||||||
|
You can add this customized image to fission with "fission env
|
||||||
|
create":
|
||||||
|
|
||||||
|
```
|
||||||
|
fission env create --name php7 --image USER/php7-env
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, if you already have an environment, you can update its image:
|
||||||
|
|
||||||
|
```
|
||||||
|
fission env update --name php7 --image USER/php7-env
|
||||||
|
```
|
||||||
|
|
||||||
|
After this, fission functions that have the env parmeter set to the
|
||||||
|
same environment name as this command will use this environment.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"PHPEnv\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"zendframework/zend-diactoros": "^1.3",
|
||||||
|
"monolog/monolog": "^1.22"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+244
@@ -0,0 +1,244 @@
|
|||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"hash": "26efb7d1cc58e9ecdc8765fac5eca4e1",
|
||||||
|
"content-hash": "c0c5cd003c9c3ca4cf3346b232e86258",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "monolog/monolog",
|
||||||
|
"version": "1.22.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Seldaek/monolog.git",
|
||||||
|
"reference": "bad29cb8d18ab0315e6c477751418a82c850d558"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/bad29cb8d18ab0315e6c477751418a82c850d558",
|
||||||
|
"reference": "bad29cb8d18ab0315e6c477751418a82c850d558",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0",
|
||||||
|
"psr/log": "~1.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/log-implementation": "1.0.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
|
||||||
|
"doctrine/couchdb": "~1.0@dev",
|
||||||
|
"graylog2/gelf-php": "~1.0",
|
||||||
|
"jakub-onderka/php-parallel-lint": "0.9",
|
||||||
|
"php-amqplib/php-amqplib": "~2.4",
|
||||||
|
"php-console/php-console": "^3.1.3",
|
||||||
|
"phpunit/phpunit": "~4.5",
|
||||||
|
"phpunit/phpunit-mock-objects": "2.3.0",
|
||||||
|
"ruflin/elastica": ">=0.90 <3.0",
|
||||||
|
"sentry/sentry": "^0.13",
|
||||||
|
"swiftmailer/swiftmailer": "~5.3"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
|
||||||
|
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
|
||||||
|
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
|
||||||
|
"ext-mongo": "Allow sending log messages to a MongoDB server",
|
||||||
|
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
|
||||||
|
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
|
||||||
|
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
|
||||||
|
"php-console/php-console": "Allow sending log messages to Google Chrome",
|
||||||
|
"rollbar/rollbar": "Allow sending log messages to Rollbar",
|
||||||
|
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
|
||||||
|
"sentry/sentry": "Allow sending log messages to a Sentry server"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Monolog\\": "src/Monolog"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jordi Boggiano",
|
||||||
|
"email": "j.boggiano@seld.be",
|
||||||
|
"homepage": "http://seld.be"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
|
||||||
|
"homepage": "http://github.com/Seldaek/monolog",
|
||||||
|
"keywords": [
|
||||||
|
"log",
|
||||||
|
"logging",
|
||||||
|
"psr-3"
|
||||||
|
],
|
||||||
|
"time": "2016-11-26 00:15:39"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-message",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-message.git",
|
||||||
|
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
|
||||||
|
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Message\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "http://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP messages",
|
||||||
|
"homepage": "https://github.com/php-fig/http-message",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"http-message",
|
||||||
|
"psr",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response"
|
||||||
|
],
|
||||||
|
"time": "2016-08-06 14:39:51"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/log",
|
||||||
|
"version": "1.0.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/log.git",
|
||||||
|
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
|
||||||
|
"reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Log\\": "Psr/Log/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "http://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for logging libraries",
|
||||||
|
"homepage": "https://github.com/php-fig/log",
|
||||||
|
"keywords": [
|
||||||
|
"log",
|
||||||
|
"psr",
|
||||||
|
"psr-3"
|
||||||
|
],
|
||||||
|
"time": "2016-10-10 12:19:37"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "zendframework/zend-diactoros",
|
||||||
|
"version": "1.3.10",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/zendframework/zend-diactoros.git",
|
||||||
|
"reference": "83e8d98b9915de76c659ce27d683c02a0f99fa90"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/83e8d98b9915de76c659ce27d683c02a0f99fa90",
|
||||||
|
"reference": "83e8d98b9915de76c659ce27d683c02a0f99fa90",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^5.4 || ^7.0",
|
||||||
|
"psr/http-message": "~1.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/http-message-implementation": "~1.0.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^4.6 || ^5.5",
|
||||||
|
"zendframework/zend-coding-standard": "~1.0.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.3-dev",
|
||||||
|
"dev-develop": "1.4-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Zend\\Diactoros\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-2-Clause"
|
||||||
|
],
|
||||||
|
"description": "PSR HTTP Message implementations",
|
||||||
|
"homepage": "https://github.com/zendframework/zend-diactoros",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"psr",
|
||||||
|
"psr-7"
|
||||||
|
],
|
||||||
|
"time": "2017-01-23 04:53:24"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": [],
|
||||||
|
"platform-dev": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
|
$server = new PHPEnv\Server();
|
||||||
|
$server->run();
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PHPEnv;
|
||||||
|
|
||||||
|
use Monolog\Logger;
|
||||||
|
use Monolog\Handler\StreamHandler;
|
||||||
|
use Zend\Diactoros\ServerRequest;
|
||||||
|
use Zend\Diactoros\Response;
|
||||||
|
use Zend\Diactoros\Server as ZendServer;
|
||||||
|
|
||||||
|
class Server {
|
||||||
|
private $server;
|
||||||
|
|
||||||
|
public function __construct(){
|
||||||
|
$codepath = '/userfunc/user';
|
||||||
|
|
||||||
|
//if i have a local function i use it
|
||||||
|
$devcodepath = '/app/userfuncdev.php';
|
||||||
|
if(file_exists($devcodepath))
|
||||||
|
$codepath = $devcodepath;
|
||||||
|
|
||||||
|
$logger = new Logger("Function");
|
||||||
|
$logger->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));
|
||||||
|
|
||||||
|
$this->server = ZendServer::createServer(
|
||||||
|
function (ServerRequest $request, Response $response) use ($logger, $codepath) {
|
||||||
|
$path = parse_url($request->getUri(), PHP_URL_PATH);
|
||||||
|
|
||||||
|
if($path == "/specialize" && $request->getMethod() == "POST"){
|
||||||
|
//Nothing to do
|
||||||
|
return new Response\EmptyResponse(201);
|
||||||
|
}else{
|
||||||
|
if(!file_exists($codepath)){
|
||||||
|
$response = $response->withStatus(500);
|
||||||
|
$response->getBody()->write("Generic container: no requests supported");
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
include($codepath);
|
||||||
|
//If the function as an handler class it will be called with request, response and logger
|
||||||
|
if(function_exists("handler")){
|
||||||
|
ob_end_clean();
|
||||||
|
return handler(array("request"=>$request, "response"=>$response,"logger"=>$logger));
|
||||||
|
}
|
||||||
|
//php code didn't have handler function, i will return the content
|
||||||
|
$bodyRowContent = ob_get_contents();
|
||||||
|
ob_end_clean();
|
||||||
|
return $response->getBody()->write($bodyRowContent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
$_SERVER,
|
||||||
|
$_GET,
|
||||||
|
$_POST,
|
||||||
|
$_COOKIE,
|
||||||
|
$_FILES
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(){
|
||||||
|
$this->server->listen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
FROM ubuntu:16.04
|
FROM alpine:3.5
|
||||||
|
|
||||||
RUN apt-get update -y
|
RUN apk update
|
||||||
RUN apt-get install -y python3 python3-pip python3-dev build-essential
|
RUN apk add --no-cache python3 python3-dev build-base
|
||||||
RUN pip3 install --upgrade pip
|
RUN pip3 install --upgrade pip
|
||||||
|
RUN rm -r /root/.cache
|
||||||
|
|
||||||
COPY . /app
|
COPY . /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -41,5 +41,5 @@ Or, if you already have an environment, you can update its image:
|
|||||||
fission env update --name python --image USER/python-env
|
fission env update --name python --image USER/python-env
|
||||||
```
|
```
|
||||||
|
|
||||||
After this, fission functions that have the env parmeter set to the
|
After this, fission functions that have the env parameter set to the
|
||||||
same environment name as this command will use this environment.
|
same environment name as this command will use this environment.
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
FROM ruby:2.4.1
|
||||||
|
|
||||||
|
RUN apt-get update -qq && apt-get install -y build-essential
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
WORKDIR /app
|
||||||
|
ADD Gemfile /app/Gemfile
|
||||||
|
ADD Gemfile.lock /app/Gemfile.lock
|
||||||
|
RUN bundle install
|
||||||
|
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
EXPOSE 8888
|
||||||
|
|
||||||
|
ENTRYPOINT ["ruby"]
|
||||||
|
CMD ["server.rb"]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "rack"
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
rack (2.0.3)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
rack
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
1.15.1
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# Fission: Ruby Environment
|
||||||
|
|
||||||
|
This is the Ruby environment for Fission.
|
||||||
|
|
||||||
|
It's a Docker image containing a Ruby 2.4.1 runtime. The image uses
|
||||||
|
Rack with WEBrick to host the internal web server.
|
||||||
|
|
||||||
|
The environment works via convention where you create a Ruby method
|
||||||
|
called `handler` with a single optional argument, a `Fission::Context`
|
||||||
|
object.
|
||||||
|
|
||||||
|
The `Fission::Context` object gives access to the Rack env, and a
|
||||||
|
request object. Please see `fission/context.rb` for the public api.
|
||||||
|
|
||||||
|
The `Fission::Request` object is a subclass of `Rack::Request` and
|
||||||
|
provides access to parameters and headers. See `fission/request.rb`
|
||||||
|
for the public api.
|
||||||
|
|
||||||
|
## Customizing this image
|
||||||
|
|
||||||
|
To add package dependencies, edit Gemfile to add what you
|
||||||
|
need, and rebuild this image (instructions below).
|
||||||
|
|
||||||
|
## Rebuilding and pushing the image
|
||||||
|
|
||||||
|
You'll need access to a Docker registry to push the image: you can
|
||||||
|
sign up for Docker hub at hub.docker.com, or use registries from
|
||||||
|
gcr.io, quay.io, etc. Let's assume you're using a docker hub account
|
||||||
|
called USER. Build and push the image to the the registry:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t USER/ruby-env . && docker push USER/ruby-env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the image in fission
|
||||||
|
|
||||||
|
You can add this customized image to fission with "fission env
|
||||||
|
create":
|
||||||
|
|
||||||
|
```
|
||||||
|
fission env create --name ruby --image USER/ruby-env
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, if you already have an environment, you can update its image:
|
||||||
|
|
||||||
|
```
|
||||||
|
fission env update --name ruby --image USER/ruby-env
|
||||||
|
```
|
||||||
|
|
||||||
|
After this, fission functions that have the env parameter set to the
|
||||||
|
same environment name as this command will use this environment.
|
||||||
|
|
||||||
|
## Creating functions to use this image
|
||||||
|
|
||||||
|
See the [examples README](examples/ruby/README.md).
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'request'
|
||||||
|
|
||||||
|
module Fission
|
||||||
|
class Context
|
||||||
|
attr_reader :env
|
||||||
|
|
||||||
|
def initialize(env)
|
||||||
|
@env = env
|
||||||
|
end
|
||||||
|
|
||||||
|
def request
|
||||||
|
@request ||= Request.new(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'context'
|
||||||
|
|
||||||
|
module Fission
|
||||||
|
module Handler
|
||||||
|
def self.call(env)
|
||||||
|
response = if method(:handler).arity > 0
|
||||||
|
handler(Context.new(env))
|
||||||
|
else
|
||||||
|
handler
|
||||||
|
end
|
||||||
|
|
||||||
|
response.is_a?(Array) ? response : Rack::Response.new([response]).finish
|
||||||
|
rescue
|
||||||
|
Rack::Response.new(['500 Internal Server Error'], 500, {}).finish
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Fission
|
||||||
|
HEADER_PREFIX = 'HTTP_'
|
||||||
|
PARAM_HEADER_PREFIX = 'HTTP_X_FISSION_PARAMS_'
|
||||||
|
PARAMETERS_KEY = 'fission.request.parameters'
|
||||||
|
|
||||||
|
class Request < Rack::Request
|
||||||
|
def headers
|
||||||
|
Hash[
|
||||||
|
*env.select { |k,v| k.start_with?(HEADER_PREFIX) }
|
||||||
|
.map { |k,v| [k.sub(/\A#{HEADER_PREFIX}/, '').split('_').map(&:capitalize).join('-'), v] }
|
||||||
|
.sort
|
||||||
|
.flatten
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def params
|
||||||
|
env[PARAMETERS_KEY] ||= super.merge(path_parameters)
|
||||||
|
end
|
||||||
|
|
||||||
|
def path_parameters
|
||||||
|
Hash[
|
||||||
|
*env.select { |k,v| k.start_with?(PARAM_HEADER_PREFIX) }
|
||||||
|
.map { |k,v| [k.sub(/\A#{PARAM_HEADER_PREFIX}/, '').downcase, v] }
|
||||||
|
.sort
|
||||||
|
.flatten
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Fission
|
||||||
|
CODE_PATH = '/userfunc/user'
|
||||||
|
|
||||||
|
module Specializer
|
||||||
|
def self.call(env)
|
||||||
|
load CODE_PATH
|
||||||
|
Rack::Response.new([], 201).finish
|
||||||
|
rescue
|
||||||
|
Rack::Response.new(['500 Internal Server Error'], 500, {}).finish
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rack'
|
||||||
|
|
||||||
|
require_relative 'fission/specializer'
|
||||||
|
require_relative 'fission/handler'
|
||||||
|
|
||||||
|
app = Rack::Builder.new do
|
||||||
|
use Rack::CommonLogger, $stderr
|
||||||
|
|
||||||
|
map "/specialize" do
|
||||||
|
run Fission::Specializer
|
||||||
|
end
|
||||||
|
|
||||||
|
map "/" do
|
||||||
|
run Fission::Handler
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Rack::Handler::WEBrick.run app, Host: '0.0.0.0', Port: 8888
|
||||||
@@ -18,11 +18,13 @@ package fission
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (e Error) Error() string {
|
func (err Error) Error() string {
|
||||||
return fmt.Sprintf("(Error %v) %v", e.Code, e.Message)
|
return fmt.Sprintf("%v - %v", err.Description(), err.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeError(code int, msg string) Error {
|
func MakeError(code int, msg string) Error {
|
||||||
@@ -36,29 +38,39 @@ func MakeErrorFromHTTP(resp *http.Response) error {
|
|||||||
|
|
||||||
var errCode int
|
var errCode int
|
||||||
switch resp.StatusCode {
|
switch resp.StatusCode {
|
||||||
|
case 400:
|
||||||
|
errCode = ErrorInvalidArgument
|
||||||
case 403:
|
case 403:
|
||||||
errCode = ErrorNotAuthorized
|
errCode = ErrorNotAuthorized
|
||||||
case 404:
|
case 404:
|
||||||
errCode = ErrorNotFound
|
errCode = ErrorNotFound
|
||||||
case 400:
|
case 409:
|
||||||
errCode = ErrorInvalidArgument
|
errCode = ErrorNameExists
|
||||||
default:
|
default:
|
||||||
errCode = ErrorInternal
|
errCode = ErrorInternal
|
||||||
}
|
}
|
||||||
return MakeError(errCode, resp.Status)
|
|
||||||
|
msg := resp.Status
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err == nil && len(body) > 0 {
|
||||||
|
msg = strings.TrimSpace(string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return MakeError(errCode, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err Error) HTTPStatus() int {
|
func (err Error) HTTPStatus() int {
|
||||||
var code int
|
var code int
|
||||||
switch err.Code {
|
switch err.Code {
|
||||||
case ErrorNotFound:
|
|
||||||
code = 404
|
|
||||||
case ErrorInvalidArgument:
|
case ErrorInvalidArgument:
|
||||||
code = 400
|
code = 400
|
||||||
case ErrorNoSpace:
|
|
||||||
code = 500
|
|
||||||
case ErrorNotAuthorized:
|
case ErrorNotAuthorized:
|
||||||
code = 403
|
code = 403
|
||||||
|
case ErrorNotFound:
|
||||||
|
code = 404
|
||||||
|
case ErrorNameExists:
|
||||||
|
code = 409
|
||||||
default:
|
default:
|
||||||
code = 500
|
code = 500
|
||||||
}
|
}
|
||||||
@@ -70,11 +82,19 @@ func GetHTTPError(err error) (int, string) {
|
|||||||
var code int
|
var code int
|
||||||
fe, ok := err.(Error)
|
fe, ok := err.(Error)
|
||||||
if ok {
|
if ok {
|
||||||
msg = fe.Message
|
|
||||||
code = fe.HTTPStatus()
|
code = fe.HTTPStatus()
|
||||||
|
msg = fe.Message
|
||||||
} else {
|
} else {
|
||||||
code = 500
|
code = 500
|
||||||
msg = err.Error()
|
msg = err.Error()
|
||||||
}
|
}
|
||||||
return code, msg
|
return code, msg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (err Error) Description() string {
|
||||||
|
idx := int(err.Code)
|
||||||
|
if idx < 0 || idx > len(errorDescriptions)-1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return errorDescriptions[idx]
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using Fission.DotNetCore.Api;
|
||||||
|
|
||||||
|
public class FissionFunction
|
||||||
|
{
|
||||||
|
public string Execute(FissionContext context){
|
||||||
|
var x = Convert.ToInt32(context.Arguments["x"]);
|
||||||
|
var y = Convert.ToInt32(context.Arguments["y"]);
|
||||||
|
return (x+y).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Fission.DotNetCore.Api;
|
||||||
|
|
||||||
|
public class FissionFunction
|
||||||
|
{
|
||||||
|
public string Execute(FissionContext context)
|
||||||
|
{
|
||||||
|
return "Hello World!";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Runtime.Serialization.Json;
|
||||||
|
using Fission.DotNetCore.Api;
|
||||||
|
|
||||||
|
public class FissionFunction
|
||||||
|
{
|
||||||
|
public string Execute(FissionContext context)
|
||||||
|
{
|
||||||
|
var person = Person.Deserialize(context.Request.Body);
|
||||||
|
return $"Hello, my name is {person.Name} and I am {person.Age} years old.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Person
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public int Age { get; set; }
|
||||||
|
|
||||||
|
public static Person Deserialize(Stream json)
|
||||||
|
{
|
||||||
|
var serializer = new DataContractJsonSerializer(typeof(Person));
|
||||||
|
return (Person)serializer.ReadObject(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using Fission.DotNetCore.Api;
|
||||||
|
|
||||||
|
public class FissionFunction
|
||||||
|
{
|
||||||
|
public string Execute(FissionContext context){
|
||||||
|
var buffer = new System.Text.StringBuilder();
|
||||||
|
foreach(var header in context.Request.Headers){
|
||||||
|
buffer.AppendLine(header.Key);
|
||||||
|
foreach(var item in header.Value){
|
||||||
|
buffer.AppendLine($"\t{item}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.AppendLine($"Url: {context.Request.Url}, method: {context.Request.Method}");
|
||||||
|
return buffer.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Go examples
|
||||||
|
|
||||||
|
The `go` runtime uses the [`plugin` package](https://golang.org/pkg/plugin/) to dynamically load an HTTP handler.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
First, set up your fission deployment with the go environment.
|
||||||
|
|
||||||
|
```
|
||||||
|
fission env create --name go-env --image fission/go-env:1.8.1
|
||||||
|
```
|
||||||
|
|
||||||
|
To ensure that you build functions using the same version as the
|
||||||
|
runtime, fission provides a docker image and helper script for
|
||||||
|
building functions.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
### hello.go
|
||||||
|
|
||||||
|
`hello.go` is an very basic HTTP handler returning `"Hello, World!"`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Download the build helper script
|
||||||
|
$ curl https://raw.githubusercontent.com/fission/fission/master/environments/go/builder/go-function-build > go-function-build
|
||||||
|
$ chmod +x go-function-build
|
||||||
|
|
||||||
|
# Build the function as a plugin. Outputs result to 'function.so'
|
||||||
|
$ go-function-build hello.go
|
||||||
|
|
||||||
|
# Upload the function to fission
|
||||||
|
$ fission function create --name hello --env go-runtime --package function.so
|
||||||
|
|
||||||
|
# Map /hello to the hello function
|
||||||
|
$ fission route create --method GET --url /hello --function hello
|
||||||
|
|
||||||
|
# Run the function
|
||||||
|
$ curl http://$FISSION_ROUTER/hello
|
||||||
|
Hello, World!
|
||||||
|
```
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
msg := "Hello, World!"
|
||||||
|
w.Write([]byte(msg))
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# Fission Node.js Examples
|
||||||
|
|
||||||
|
This directory contains several examples to get you started using Node.js with Fission.
|
||||||
|
|
||||||
|
Before running any of these functions, make sure you have created a `nodejs` Fission environment:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission env create --name nodejs --image fission/node-env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Function signature
|
||||||
|
|
||||||
|
Every Node.js function has the same basic form:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
module.exports = async function(context) {
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: 'Your body here',
|
||||||
|
headers: {
|
||||||
|
'Foo': 'Bar'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Since it is an `async` function, you can `await` `Promise`s, as demonstrated in the `stock.js` function.
|
||||||
|
|
||||||
|
## hello.js
|
||||||
|
|
||||||
|
This is a basic "Hello, World!" example. It simply returns a status of `200` and text body.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Upload your function code to fission
|
||||||
|
$ fission function create --name hello --env nodejs --code hello.js
|
||||||
|
|
||||||
|
# Map GET /hello to your new function
|
||||||
|
$ fission route create --method GET --url /hello --function hello
|
||||||
|
|
||||||
|
# Run the function.
|
||||||
|
$ curl http://$FISSION_ROUTER/hello
|
||||||
|
Hello, world!
|
||||||
|
```
|
||||||
|
|
||||||
|
## hello-callback.js
|
||||||
|
|
||||||
|
This is a basic "Hello, World!" example implemented with the legacy callback implementation. If you declare your function with two arguments (`context`, `callback`), a callback taking three arguments (`status`, `body`, `headers`) is provided.
|
||||||
|
|
||||||
|
⚠️️ Callback support is only provided for backwards compatibility! We recommend that you use `async` functions instead.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Upload your function code to fission
|
||||||
|
$ fission function create --name hello-callback --env nodejs --code hello-callback.js
|
||||||
|
|
||||||
|
# Map GET /hello-callback to your new function
|
||||||
|
$ fission route create --method GET --url /hello-callback --function hello-callback
|
||||||
|
|
||||||
|
# Run the function.
|
||||||
|
$ curl http://$FISSION_ROUTER/hello-callback
|
||||||
|
Hello, world!
|
||||||
|
```
|
||||||
|
|
||||||
|
## stock.js
|
||||||
|
|
||||||
|
This is a basic example of how you can easily use asynchronous requests in your functions. By default, the Node.js environment makes the [`request-promise-native`](https://github.com/request/request-promise-native) library available. In this example, the Google Finance API is used to determine when a stock was last traded.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Upload your function code to fission
|
||||||
|
$ fission function create --name stock --env nodejs --code stock.js
|
||||||
|
|
||||||
|
# Map GET /stock to your new function
|
||||||
|
$ fission route create --method GET --url /stock --function stock
|
||||||
|
|
||||||
|
# Run the function.
|
||||||
|
$ curl -H "Content-Type: application/json" -X POST -d '{"symbol":"AAPL"}' http://$FISSION_ROUTER/stock
|
||||||
|
|
||||||
|
{"text":"AAPL last traded at 138.99"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## kubeEventsSlack.js
|
||||||
|
|
||||||
|
This example watches Kubernetes events and sends them to a Slack channel. To use this, create an incoming webhook for your Slack channel, and replace the `slackWebhookPath` in the example code.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Upload your function code to fission
|
||||||
|
$ fission fn create --name kubeEventsSlack --env nodejs --code kubeEventsSlack.js
|
||||||
|
|
||||||
|
# Watch all services in the default namespace:
|
||||||
|
$ fission watch create --function kubeEventsSlack --type service --ns default
|
||||||
|
```
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
module.exports = function(context, callback) {
|
||||||
|
callback(200, "Hello, world!\n");
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
|
|
||||||
module.exports = function(context, callback) {
|
module.exports = async function(context) {
|
||||||
callback(200, "Hello, world!\n");
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: "Hello, world!\n"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Watch kubernetes events and send them to a slack channel. This
|
||||||
|
// uses Slack's incoming webhooks. To use this, create an incoming
|
||||||
|
// webhook for your slack channel through Slack's UI, and populate the
|
||||||
|
// relative path below.
|
||||||
|
//
|
||||||
|
// Create the function in fission:
|
||||||
|
//
|
||||||
|
// fission fn create --name kubeEventsSlack --env nodejs --code kubeEventsSlack.js
|
||||||
|
//
|
||||||
|
// Then, watch all services in the default namespace:
|
||||||
|
//
|
||||||
|
// fission watch create --function kubeEventsSlack --type service --ns default
|
||||||
|
//
|
||||||
|
|
||||||
|
let https = require('https');
|
||||||
|
|
||||||
|
const slackWebhookPath = "YOUR RELATIVE PATH HERE"; // Something like "/services/XXX/YYY/zZz123"
|
||||||
|
|
||||||
|
function upcaseFirst(s) {
|
||||||
|
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendSlackMessage(msg) {
|
||||||
|
let postData = `{"text": "${msg}"}`;
|
||||||
|
let options = {
|
||||||
|
hostname: "hooks.slack.com",
|
||||||
|
path: slackWebhookPath,
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
let req = https.request(options, function(res) {
|
||||||
|
console.log(`slack request status = ${res.statusCode}`);
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
req.write(postData);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async function(context) {
|
||||||
|
console.log(context.request.headers);
|
||||||
|
|
||||||
|
let obj = context.request.body;
|
||||||
|
let version = obj.metadata.resourceVersion;
|
||||||
|
let eventType = context.request.get('X-Kubernetes-Event-Type');
|
||||||
|
let objType = context.request.get('X-Kubernetes-Object-Type');
|
||||||
|
|
||||||
|
let msg = `${upcaseFirst(eventType)} ${objType} ${obj.metadata.name}`;
|
||||||
|
console.log(msg, version);
|
||||||
|
|
||||||
|
if (eventType == 'DELETED' || eventType == 'ADDED') {
|
||||||
|
console.log("sending event to slack")
|
||||||
|
await sendSlackMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
-23
@@ -1,31 +1,41 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var http = require('http');
|
const rp = require('request-promise-native');
|
||||||
|
|
||||||
module.exports = function (context, callback) {
|
module.exports = async function (context) {
|
||||||
let body = context.request.body;
|
const body = context.request.body;
|
||||||
console.log(`body text: ${body['text']}`);
|
console.log(`body = ${body}`);
|
||||||
|
const symbol = body['text'].split(' ')[1];
|
||||||
|
|
||||||
var symbol = body['text'].split(' ')[1];
|
console.log(`Got symbol: ${symbol}`);
|
||||||
|
|
||||||
http.get({
|
if (!symbol) {
|
||||||
host: 'finance.google.com',
|
return {
|
||||||
path: `/finance/info?q=NYSE:${symbol}`
|
status: 400,
|
||||||
}, function(response) {
|
body: {
|
||||||
var resp = '';
|
text: 'You must provide a stock symbol.'
|
||||||
response.on('data', function(d) {
|
|
||||||
resp += d;
|
|
||||||
});
|
|
||||||
response.on('end', function() {
|
|
||||||
|
|
||||||
try {
|
|
||||||
var parsed = JSON.parse(resp.slice(3));
|
|
||||||
var lastTrade = parsed[0]['l_cur']
|
|
||||||
callback(200, `{ "text": "${symbol} last traded at ${lastTrade}" }`);
|
|
||||||
} catch (e) {
|
|
||||||
callback(200, `{ "text": "Error (invalid NYSE symbol?)" }`);
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
});
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await rp(`http://finance.google.com/finance/info?q=NYSE:${symbol}`);
|
||||||
|
const parsed = JSON.parse(response.slice(3));
|
||||||
|
const lastTrade = parsed[0]['l_cur'];
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
text: `${symbol} last traded at ${lastTrade}`
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
body: e
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
echo "Hello from PHP";
|
||||||
|
$logger->warning("Hello logger");
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
function handler($context){
|
||||||
|
/** @var \Psr\Http\Message\ResponseInterface $response */
|
||||||
|
$response = $context["response"];
|
||||||
|
/** @var \Psr\Http\Message\ServerRequestInterface $request */
|
||||||
|
$request = $context["request"];
|
||||||
|
/** @var \Psr\Log\LoggerInterface $logger */
|
||||||
|
$logger = $context["logger"];
|
||||||
|
|
||||||
|
$response->getBody()->write("Hello from handler PHP");
|
||||||
|
$logger->warning("Hello logger");
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
use \Psr\Http\Message\ResponseInterface;
|
||||||
|
use \Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use \Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
function sendError($response,$message){
|
||||||
|
$response = $response->withStatus(500);
|
||||||
|
$response->getBody()->write($message);
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handler($context){
|
||||||
|
/** @var ResponseInterface $response */
|
||||||
|
$response = $context["response"];
|
||||||
|
/** @var ServerRequestInterface $request */
|
||||||
|
$request = $context["request"];
|
||||||
|
/** @var LoggerInterface $logger */
|
||||||
|
$logger = $context["logger"];
|
||||||
|
|
||||||
|
$logger->debug("Request : ",$request->getParsedBody());
|
||||||
|
if($request->getMethod() != "POST")
|
||||||
|
return sendError($response,"You must use POST method");
|
||||||
|
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
if(!isset($body["currency"]))
|
||||||
|
return sendError($response,"'currency' is not present in the POST request");
|
||||||
|
|
||||||
|
$allowedCurrency = ["ltc","btc"];
|
||||||
|
if(!in_array($body["currency"],$allowedCurrency))
|
||||||
|
return sendError($response,"'currency' is non allowed. Use one of them : ".implode(",",$allowedCurrency));
|
||||||
|
|
||||||
|
$curl = curl_init();
|
||||||
|
curl_setopt_array($curl, array(
|
||||||
|
CURLOPT_RETURNTRANSFER => 1,
|
||||||
|
CURLOPT_URL => 'https://api.cryptonator.com/api/ticker/'.$body["currency"].'-usd'
|
||||||
|
));
|
||||||
|
$result = curl_exec($curl);
|
||||||
|
curl_close($curl);
|
||||||
|
|
||||||
|
$result = json_decode($result,true);
|
||||||
|
if($result){
|
||||||
|
$logger->debug("Response API",$result);
|
||||||
|
$response->getBody()->write(json_encode(array("text"=>sprintf("%s-USB = %02f",$body["currency"],$result["ticker"]["price"]))));
|
||||||
|
}else
|
||||||
|
return sendError($response,"Cryptonator API not available");
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ def main():
|
|||||||
items = [("<li>%s</li>" % escape(m.decode('utf-8'))) for m in messages]
|
items = [("<li>%s</li>" % escape(m.decode('utf-8'))) for m in messages]
|
||||||
ul = "<ul>%s</ul>" % "\n".join(items)
|
ul = "<ul>%s</ul>" % "\n".join(items)
|
||||||
return """
|
return """
|
||||||
<html><body>
|
<html><body style="font-family:sans-serif;font-size:2rem;padding:40px">
|
||||||
<h1>Guestbook</h1>
|
<h1>Guestbook</h1>
|
||||||
<form action="/guestbook" method="POST">
|
<form action="/guestbook" method="POST">
|
||||||
<input type="text" name="text">
|
<input type="text" name="text">
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# Ruby examples
|
||||||
|
|
||||||
|
This directory contains several examples to get you started using Ruby
|
||||||
|
with Fission.
|
||||||
|
|
||||||
|
Before running any of these functions, make sure you have created a
|
||||||
|
`ruby` Fission environment:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission env create --name ruby --image USER/ruby-env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Method signature
|
||||||
|
|
||||||
|
A standard Ruby function has the basic form:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
def handler(context)
|
||||||
|
return [200, {}, []]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
If the fission context is not required, the function can be simplified:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
def handler
|
||||||
|
[200, {}, ["Hello, world!\n"]]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
If a simple text response is to be returned, with a status of 200, this
|
||||||
|
can be further simplified.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
def handler
|
||||||
|
"Hello, world!\n"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hello example (`hello.rb`)
|
||||||
|
|
||||||
|
This example is the simplest possible Ruby function, as described above.
|
||||||
|
|
||||||
|
To run the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission function create --name hello --env ruby --code examples/ruby/hello.rb
|
||||||
|
|
||||||
|
$ fission route create --method GET --url /hello --function hello
|
||||||
|
|
||||||
|
$ curl http://$FISSION_ROUTER/hello
|
||||||
|
Hello, world!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Request data example (`request_data.rb`)
|
||||||
|
|
||||||
|
This example shows basic use of the `Fission::Context` and
|
||||||
|
`Fission::Request` objects.
|
||||||
|
|
||||||
|
To run the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ fission function create --name request --env ruby --code examples/ruby/request_data.rb
|
||||||
|
|
||||||
|
$ fission route create --method GET --url /request/{id} --function request
|
||||||
|
|
||||||
|
$ curl http://$FISSION_ROUTER/request/123?key=abc
|
||||||
|
---ENV---
|
||||||
|
GATEWAY_INTERFACE=CGI/1.1
|
||||||
|
PATH_INFO=/
|
||||||
|
QUERY_STRING=key=abc
|
||||||
|
REMOTE_ADDR=172.17.0.8
|
||||||
|
REMOTE_HOST=172.17.0.8
|
||||||
|
REQUEST_METHOD=GET
|
||||||
|
REQUEST_URI=http://192.168.64.200:31314/?key=abc
|
||||||
|
SCRIPT_NAME=
|
||||||
|
SERVER_NAME=192.168.64.200
|
||||||
|
SERVER_PORT=31314
|
||||||
|
SERVER_PROTOCOL=HTTP/1.1
|
||||||
|
SERVER_SOFTWARE=WEBrick/1.3.1 (Ruby/2.4.1/2017-03-22)
|
||||||
|
HTTP_HOST=192.168.64.200:31314
|
||||||
|
HTTP_USER_AGENT=curl/7.52.1
|
||||||
|
HTTP_ACCEPT=*/*
|
||||||
|
HTTP_X_FISSION_PARAMS_ID=123
|
||||||
|
HTTP_X_FORWARDED_FOR=172.17.0.1
|
||||||
|
HTTP_ACCEPT_ENCODING=gzip
|
||||||
|
rack.version=1=3
|
||||||
|
...
|
||||||
|
HTTP_VERSION=HTTP/1.1
|
||||||
|
REQUEST_PATH=/
|
||||||
|
|
||||||
|
---HEADERS---
|
||||||
|
Accept: */*
|
||||||
|
Accept-Encoding: gzip
|
||||||
|
Host: 192.168.64.200:31314
|
||||||
|
User-Agent: curl/7.52.1
|
||||||
|
Version: HTTP/1.1
|
||||||
|
X-Fission-Params-Id: 123
|
||||||
|
X-Forwarded-For: 172.17.0.1
|
||||||
|
|
||||||
|
---PARAMS---
|
||||||
|
key=abc
|
||||||
|
id=123
|
||||||
|
|
||||||
|
--BODY--
|
||||||
|
|
||||||
|
```
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
require 'net/http'
|
||||||
|
require 'uri'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
|
SLACK_BASE_URL = 'https://hooks.slack.com/'
|
||||||
|
SLACK_WEBHOOK_PATH = 'YOUR RELATIVE PATH HERE' # Something like "/services/XXX/YYY/zZz123"
|
||||||
|
|
||||||
|
def send_slack_message(message)
|
||||||
|
uri = URI.join(SLACK_BASE_URL, SLACK_WEBHOOK_PATH)
|
||||||
|
data = "{'channel': '#hackdays-serverless', 'username': 'fissionbot', 'text': \"#{message}\", 'icon_emoji': ':fission:'}"
|
||||||
|
res = Net::HTTP.post_form(uri, payload: data)
|
||||||
|
res.success?
|
||||||
|
end
|
||||||
|
|
||||||
|
def handler(context)
|
||||||
|
request = context.request
|
||||||
|
|
||||||
|
event_type = request.headers['X-Kubernetes-Event-Type']
|
||||||
|
object_type = request.headers['X-Kubernetes-Object-Type']
|
||||||
|
|
||||||
|
object = JSON.parse(request.body.read)
|
||||||
|
object_name = object.dig('metadata', 'name')
|
||||||
|
object_namespace = object.dig('metadata', 'namespace')
|
||||||
|
|
||||||
|
message = "#{event_type} #{object_type} #{object_namespace}/#{object_name}"
|
||||||
|
|
||||||
|
if send_slack_message(message)
|
||||||
|
"Slack message sent - #{message}"
|
||||||
|
else
|
||||||
|
Rack::Response.new(["Failed to send Slack message - #{message}"], 500).finish
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
def handler
|
||||||
|
"Hello, world!\n"
|
||||||
|
end
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
def handler(context)
|
||||||
|
request = context.request
|
||||||
|
msg = <<~MSG
|
||||||
|
---ENV---
|
||||||
|
#{request.env.map { |h| h.join('=') }.join("\n") }
|
||||||
|
|
||||||
|
---HEADERS---
|
||||||
|
#{request.headers.map { |h| h.join(': ') }.join("\n") }
|
||||||
|
|
||||||
|
---PARAMS---
|
||||||
|
#{request.params.map { |h| h.join('=') }.join("\n") }
|
||||||
|
|
||||||
|
--BODY--
|
||||||
|
#{request.body.read}
|
||||||
|
MSG
|
||||||
|
|
||||||
|
Rack::Response.new([msg]).finish
|
||||||
|
end
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
GOOS=linux GOARCH=386 go build
|
GOOS=linux GOARCH=386 go build
|
||||||
|
|||||||
@@ -7,13 +7,19 @@ import (
|
|||||||
"github.com/docopt/docopt-go"
|
"github.com/docopt/docopt-go"
|
||||||
"github.com/fission/fission/controller"
|
"github.com/fission/fission/controller"
|
||||||
"github.com/fission/fission/kubewatcher"
|
"github.com/fission/fission/kubewatcher"
|
||||||
|
"github.com/fission/fission/logger"
|
||||||
|
"github.com/fission/fission/mqtrigger"
|
||||||
"github.com/fission/fission/poolmgr"
|
"github.com/fission/fission/poolmgr"
|
||||||
"github.com/fission/fission/router"
|
"github.com/fission/fission/router"
|
||||||
|
"github.com/fission/fission/timer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runController(port int, etcdUrl string, filepath string) {
|
func runController(port int, etcdUrl string, filepath string) {
|
||||||
// filePath will be created if it doesn't exist.
|
// filePath will be created if it doesn't exist.
|
||||||
fileStore := controller.MakeFileStore(filepath)
|
fileStore := controller.MakeFileStore(filepath)
|
||||||
|
if fileStore == nil {
|
||||||
|
log.Fatalf("Failed to initialize filestore")
|
||||||
|
}
|
||||||
|
|
||||||
rs, err := controller.MakeResourceStore(fileStore, []string{etcdUrl})
|
rs, err := controller.MakeResourceStore(fileStore, []string{etcdUrl})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -44,6 +50,25 @@ func runKubeWatcher(controllerUrl, routerUrl string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runLogger() {
|
||||||
|
logger.Start()
|
||||||
|
log.Fatalf("Error: Logger exited.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTimer(controllerUrl, routerUrl string) {
|
||||||
|
err := timer.Start(controllerUrl, routerUrl)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error starting timer: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMessageQueueMgr(controllerUrl, routerUrl string) {
|
||||||
|
err := messagequeue.Start(controllerUrl, routerUrl)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error starting timer: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getPort(portArg interface{}) int {
|
func getPort(portArg interface{}) int {
|
||||||
portArgStr := portArg.(string)
|
portArgStr := portArg.(string)
|
||||||
port, err := strconv.Atoi(portArgStr)
|
port, err := strconv.Atoi(portArgStr)
|
||||||
@@ -77,6 +102,9 @@ Usage:
|
|||||||
fission-bundle --routerPort=<port> [--controllerUrl=<url> --poolmgrUrl=<url>]
|
fission-bundle --routerPort=<port> [--controllerUrl=<url> --poolmgrUrl=<url>]
|
||||||
fission-bundle --poolmgrPort=<port> [--controllerUrl=<url> --namespace=<namespace>]
|
fission-bundle --poolmgrPort=<port> [--controllerUrl=<url> --namespace=<namespace>]
|
||||||
fission-bundle --kubewatcher [--controllerUrl=<url> --routerUrl=<url>]
|
fission-bundle --kubewatcher [--controllerUrl=<url> --routerUrl=<url>]
|
||||||
|
fission-bundle --logger
|
||||||
|
fission-bundle --timer [--controllerUrl=<url> --routerUrl=<url>]
|
||||||
|
fission-bundle --mqt [--controllerUrl=<url> --routerUrl=<url>]
|
||||||
Options:
|
Options:
|
||||||
--controllerPort=<port> Port that the controller should listen on.
|
--controllerPort=<port> Port that the controller should listen on.
|
||||||
--routerPort=<port> Port that the router should listen on.
|
--routerPort=<port> Port that the router should listen on.
|
||||||
@@ -88,6 +116,9 @@ Options:
|
|||||||
--filepath=<filepath> Directory to store functions in.
|
--filepath=<filepath> Directory to store functions in.
|
||||||
--namespace=<namespace> Kubernetes namespace in which to run function containers. Defaults to 'fission-function'.
|
--namespace=<namespace> Kubernetes namespace in which to run function containers. Defaults to 'fission-function'.
|
||||||
--kubewatcher Start Kubernetes events watcher.
|
--kubewatcher Start Kubernetes events watcher.
|
||||||
|
--logger Start logger.
|
||||||
|
--timer Start Timer.
|
||||||
|
--mqt Start message queue trigger.
|
||||||
`
|
`
|
||||||
arguments, err := docopt.Parse(usage, nil, true, "fission-bundle", false)
|
arguments, err := docopt.Parse(usage, nil, true, "fission-bundle", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -120,5 +151,17 @@ Options:
|
|||||||
runKubeWatcher(controllerUrl, routerUrl)
|
runKubeWatcher(controllerUrl, routerUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if arguments["--logger"] == true {
|
||||||
|
runLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments["--timer"] == true {
|
||||||
|
runTimer(controllerUrl, routerUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments["--mqt"] == true {
|
||||||
|
runMessageQueueMgr(controllerUrl, routerUrl)
|
||||||
|
}
|
||||||
|
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,3 +30,40 @@ spec:
|
|||||||
nodePort: 31313
|
nodePort: 31313
|
||||||
selector:
|
selector:
|
||||||
svc: controller
|
svc: controller
|
||||||
|
|
||||||
|
---
|
||||||
|
# If you want to switch to external db service, please checkout
|
||||||
|
# sample Kubernetes Service config in fission-logger.yaml.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: influxdb
|
||||||
|
namespace: fission
|
||||||
|
labels:
|
||||||
|
svc: influxdb
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
ports:
|
||||||
|
- port: 8086
|
||||||
|
targetPort: 8086
|
||||||
|
nodePort: 31315
|
||||||
|
selector:
|
||||||
|
svc: influxdb
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: nats-streaming
|
||||||
|
namespace: fission
|
||||||
|
labels:
|
||||||
|
svc: nats-streaming
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
ports:
|
||||||
|
- port: 4222
|
||||||
|
targetPort: 4222
|
||||||
|
nodePort: 31316
|
||||||
|
selector:
|
||||||
|
svc: nats-streaming
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# Sample Kubernetes Service config for external db service.
|
||||||
|
#
|
||||||
|
# apiVersion: v1
|
||||||
|
# kind: Service
|
||||||
|
# metadata:
|
||||||
|
# name: influxdb
|
||||||
|
# namespace: fission
|
||||||
|
# spec:
|
||||||
|
# ports:
|
||||||
|
# - name: "8086"
|
||||||
|
# port: 8086
|
||||||
|
# targetPort: 8086
|
||||||
|
# protocol: TCP
|
||||||
|
|
||||||
|
# ---
|
||||||
|
# apiVersion: v1
|
||||||
|
# kind: Endpoints
|
||||||
|
# metadata:
|
||||||
|
# name: influxdb
|
||||||
|
# namespace: fission
|
||||||
|
# subsets:
|
||||||
|
# - addresses:
|
||||||
|
# - ip: replace.influxdb.host.here
|
||||||
|
# ports:
|
||||||
|
# - name: "8086"
|
||||||
|
# port: 8086
|
||||||
|
# protocol: TCP
|
||||||
|
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: influxdb
|
||||||
|
namespace: fission
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: influxdb
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: influxdb
|
||||||
|
image: tutum/influxdb
|
||||||
|
env:
|
||||||
|
- name: PRE_CREATE_DB
|
||||||
|
value: fissionFunctionLog
|
||||||
|
# Create a random username/password for InfluxDB here, and
|
||||||
|
# repeat it in the fluentd spec below.
|
||||||
|
- name: ADMIN_USER
|
||||||
|
value: ""
|
||||||
|
- name: INFLUXDB_INIT_PWD
|
||||||
|
value: ""
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: logger
|
||||||
|
namespace: fission
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: logger
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: logger
|
||||||
|
image: fission/fission-bundle
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ["/fission-bundle"]
|
||||||
|
args: ["--logger"]
|
||||||
|
volumeMounts:
|
||||||
|
- name: container-log
|
||||||
|
mountPath: /var/log/containers
|
||||||
|
readOnly: true
|
||||||
|
- name: docker-log
|
||||||
|
mountPath: /var/lib/docker/containers
|
||||||
|
readOnly: true
|
||||||
|
- name: fission-log
|
||||||
|
mountPath: /var/log/fission
|
||||||
|
readOnly: false
|
||||||
|
ports:
|
||||||
|
- containerPort: 1234
|
||||||
|
hostPort: 1234
|
||||||
|
protocol: TCP
|
||||||
|
- name: fluentd
|
||||||
|
image: fission/fluentd
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: INFLUXDB_ADDRESS
|
||||||
|
value: influxdb
|
||||||
|
- name: INFLUXDB_PORT
|
||||||
|
value: "8086"
|
||||||
|
- name: INFLUXDB_DBNAME
|
||||||
|
value: "fissionFunctionLog"
|
||||||
|
# Username/password for fluentd to push logs to
|
||||||
|
# influxdb. This should match the influxdb
|
||||||
|
# username/passowrd defined above.
|
||||||
|
- name: INFLUXDB_USERNAME
|
||||||
|
value: ""
|
||||||
|
- name: INFLUXDB_PASSWD
|
||||||
|
value: ""
|
||||||
|
volumeMounts:
|
||||||
|
- name: container-log
|
||||||
|
mountPath: /var/log/containers
|
||||||
|
readOnly: true
|
||||||
|
- name: docker-log
|
||||||
|
mountPath: /var/lib/docker/containers
|
||||||
|
readOnly: true
|
||||||
|
- name: fission-log
|
||||||
|
mountPath: /var/log/fission
|
||||||
|
readOnly: false
|
||||||
|
volumes:
|
||||||
|
- name: container-log
|
||||||
|
hostPath:
|
||||||
|
path: /var/log/containers
|
||||||
|
- name: docker-log
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/docker/containers
|
||||||
|
- name: fission-log
|
||||||
|
hostPath:
|
||||||
|
path: /var/log/fission
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# To enable nats authentication, please follow the instruction described in
|
||||||
|
# http://nats.io/documentation/server/gnatsd-authentication/.
|
||||||
|
# And dont forget to change the MESSAGE_QUEUE_URL in mqtrigger deployment.
|
||||||
|
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: mqtrigger
|
||||||
|
namespace: fission
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: mqtrigger
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: mqtrigger
|
||||||
|
image: fission/fission-bundle
|
||||||
|
command: ["/fission-bundle"]
|
||||||
|
args: ["--mqt"]
|
||||||
|
env:
|
||||||
|
- name: MESSAGE_QUEUE_TYPE
|
||||||
|
value: nats-streaming
|
||||||
|
- name: MESSAGE_QUEUE_URL
|
||||||
|
value: nats://nats-streaming:4222
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: nats-streaming
|
||||||
|
name: nats-streaming
|
||||||
|
namespace: fission
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
svc: nats-streaming
|
||||||
|
strategy:
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 1
|
||||||
|
type: RollingUpdate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: null
|
||||||
|
labels:
|
||||||
|
svc: nats-streaming
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nats-streaming
|
||||||
|
image: nats-streaming
|
||||||
|
args: ["--cluster_id", "fissionMQTrigger"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 4222
|
||||||
|
hostPort: 4222
|
||||||
|
protocol: TCP
|
||||||
|
|
||||||
@@ -30,3 +30,40 @@ spec:
|
|||||||
nodePort: 31313
|
nodePort: 31313
|
||||||
selector:
|
selector:
|
||||||
svc: controller
|
svc: controller
|
||||||
|
|
||||||
|
---
|
||||||
|
# If you want to switch to external db service, please checkout
|
||||||
|
# sample Kubernetes Service config in fission-logger.yaml.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: influxdb
|
||||||
|
namespace: fission
|
||||||
|
labels:
|
||||||
|
svc: influxdb
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
ports:
|
||||||
|
- port: 8086
|
||||||
|
targetPort: 8086
|
||||||
|
nodePort: 31315
|
||||||
|
selector:
|
||||||
|
svc: influxdb
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: nats-streaming
|
||||||
|
namespace: fission
|
||||||
|
labels:
|
||||||
|
svc: nats-streaming
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
ports:
|
||||||
|
- port: 4222
|
||||||
|
targetPort: 4222
|
||||||
|
nodePort: 31316
|
||||||
|
selector:
|
||||||
|
svc: nats-streaming
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ProjectRequest
|
||||||
|
metadata:
|
||||||
|
name: fission
|
||||||
|
labels:
|
||||||
|
name: fission
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ProjectRequest
|
||||||
|
metadata:
|
||||||
|
name: fission-function
|
||||||
|
labels:
|
||||||
|
name: fission-function
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: poolmgr
|
||||||
|
namespace: fission
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: fission:poolmgr
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- extensions
|
||||||
|
attributeRestrictions: null
|
||||||
|
resources:
|
||||||
|
- deployments
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- update
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
attributeRestrictions: null
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- update
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: controller
|
||||||
|
namespace: fission
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: controller
|
||||||
|
spec:
|
||||||
|
volumes:
|
||||||
|
- name: controller-storage
|
||||||
|
emptyDir: {}
|
||||||
|
containers:
|
||||||
|
- name: controller
|
||||||
|
image: fission/fission-bundle
|
||||||
|
command: ["/fission-bundle"]
|
||||||
|
args: ["--controllerPort", "8888", "--filepath", "/filestore"]
|
||||||
|
volumeMounts:
|
||||||
|
- name: controller-storage
|
||||||
|
mountPath: /filestore
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: router
|
||||||
|
namespace: fission
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: router
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: router
|
||||||
|
image: fission/fission-bundle
|
||||||
|
command: ["/fission-bundle"]
|
||||||
|
args: ["--routerPort", "8888"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: poolmgr
|
||||||
|
namespace: fission
|
||||||
|
labels:
|
||||||
|
svc: poolmgr
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8888
|
||||||
|
selector:
|
||||||
|
svc: poolmgr
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: poolmgr
|
||||||
|
namespace: fission
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: poolmgr
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: poolmgr
|
||||||
|
image: fission/fission-bundle
|
||||||
|
command: ["/fission-bundle"]
|
||||||
|
args: ["--poolmgrPort", "8888"]
|
||||||
|
serviceAccount: poolmgr
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: kubewatcher
|
||||||
|
namespace: fission
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: kubewatcher
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: kubewatcher
|
||||||
|
image: fission/fission-bundle
|
||||||
|
command: ["/fission-bundle"]
|
||||||
|
args: ["--kubewatcher"]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: etcd
|
||||||
|
namespace: fission
|
||||||
|
labels:
|
||||||
|
svc: etcd
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 2379
|
||||||
|
targetPort: 2379
|
||||||
|
selector:
|
||||||
|
svc: etcd
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: etcd
|
||||||
|
namespace: fission
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
svc: etcd
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: etcd
|
||||||
|
image: quay.io/coreos/etcd
|
||||||
|
env:
|
||||||
|
- name: ETCD_LISTEN_CLIENT_URLS
|
||||||
|
value: http://0.0.0.0:2379
|
||||||
|
- name: ETCD_ADVERTISE_CLIENT_URLS
|
||||||
|
value: http://etcd:2379
|
||||||
|
- name: ETCD_DATA_DIR
|
||||||
|
value: /tmp/default.etcd
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
groupNames: null
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: fission:poolmgr
|
||||||
|
namespace: fission-function
|
||||||
|
roleRef:
|
||||||
|
name: fission:poolmgr
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: poolmgr
|
||||||
|
namespace: fission
|
||||||
|
userNames:
|
||||||
|
- system:serviceaccount:fission:poolmgr
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user