Compare commits
93
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
.idea
|
||||
environments/php7/vendor/
|
||||
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*~
|
||||
|
||||
# Pycharm IDE
|
||||
.idea
|
||||
vendor/
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@ dist: trusty
|
||||
|
||||
language: go
|
||||
go:
|
||||
- 1.7
|
||||
- 1.8
|
||||
- tip
|
||||
|
||||
install:
|
||||
@@ -16,5 +16,5 @@ script:
|
||||
- ./fission-bundle/build.sh
|
||||
- hack/verify-gofmt.sh
|
||||
- /tmp/test-etcd/etcd &
|
||||
- go test -v -i $(go list ./... | grep -v '/vendor/')
|
||||
- go test -v $(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/' | grep -v 'examples/go')
|
||||
|
||||
@@ -110,4 +110,22 @@ be loaded with any function.
|
||||
When poolmgr needs to create a service for a function, it calls
|
||||
fetcher to fetch the function. Fetcher downloads the function into a
|
||||
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
|
||||
@@ -37,6 +37,11 @@ container.
|
||||
annoying to wait until runtime to see a syntax error that could have
|
||||
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
|
||||
|
||||
* Some people want to operate at the image level but still get the
|
||||
@@ -97,71 +102,226 @@ Roughly in order of priority:
|
||||
|
||||
### 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>
|
||||
|
||||
<user edits file>
|
||||
$ $EDITOR blah.go
|
||||
<fixes errors>
|
||||
|
||||
$ fission function update --code blah.go
|
||||
<success>
|
||||
$ fission env create -f golang.yaml
|
||||
```
|
||||
|
||||
(Or perhaps we could have a `fission function check --env x --code y`
|
||||
which just does compilation, without creating a function object?
|
||||
Useful for integration into IDEs. Basically, just an on-demand
|
||||
builder. Useful when you don't wanna setup anything on your laptop.)
|
||||
#### Function creation for compiled languages
|
||||
|
||||
User writes a function in a compiled language, for example Go.
|
||||
|
||||
```
|
||||
$ 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
|
||||
"compilation" step can be used to check for syntax errors.
|
||||
|
||||
#### Compiled language, without using fission builds
|
||||
|
||||
#### Collections of files
|
||||
|
||||
We should probably have a manifest in YAML/JSON/etc syntax for
|
||||
specifying a function. We could also use that YAML to let users
|
||||
specify the function's environment, resource requirements, etc.
|
||||
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
|
||||
"deployment package".
|
||||
|
||||
```
|
||||
$ fission funcion create -f blah.yaml
|
||||
$ fission function create --deployment-package foo.zip
|
||||
|
||||
$ cat blah.yaml
|
||||
type: Function
|
||||
metadata:
|
||||
name: ...
|
||||
environment: ...
|
||||
files:
|
||||
- foo.py
|
||||
- bar.py
|
||||
- baz/*.py
|
||||
```
|
||||
$ fission route ... # routes work as usual
|
||||
```
|
||||
|
||||
The yaml file could specify a list of files. The fission client would
|
||||
deal with packaging up this set of files and uploading the package.
|
||||
In this use case, fission is no longer operating at the source
|
||||
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
|
||||
|
||||
A manifest could point at a function's dependency spec. The
|
||||
Environment's "builder" container could then fetch these deps.
|
||||
The source package of a function can contain dependency specs.
|
||||
|
||||
So a NodeJS function manifest could contain a reference to
|
||||
package.json. A "builder" container in the NodeJS environment would
|
||||
then run npm on that function.
|
||||
Fission framework proper does not treat this spec in any special way;
|
||||
it's just another file in the source package. These will be
|
||||
interpreted by the environment builder.
|
||||
|
||||
``` $ cat func.yaml
|
||||
type: Function
|
||||
metadata: ...
|
||||
environment: ...
|
||||
dependencies: package.json
|
||||
|
||||
$ fission function create -f func.yaml
|
||||
```
|
||||
$ fission function create --source-files *.js --source-files package.json
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
+59
-3
@@ -7,6 +7,8 @@
|
||||
* [Get and Run Fission: GKE or other Cloud](#get-and-run-fission-gke-or-other-cloud)
|
||||
* [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
|
||||
|
||||
@@ -17,14 +19,14 @@ You can install Kubernetes on your laptop with [minikube](https://github.com/kub
|
||||
#### 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/
|
||||
$ 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
|
||||
```
|
||||
|
||||
#### 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/
|
||||
$ 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
|
||||
```
|
||||
|
||||
@@ -80,6 +82,18 @@ svc```). Then:
|
||||
$ 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¬
|
||||
```
|
||||
|
||||
### Install the client CLI
|
||||
|
||||
Get the CLI binary for Mac:
|
||||
@@ -101,7 +115,7 @@ 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
|
||||
$ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js
|
||||
|
||||
$ fission function create --name hello --env nodejs --code hello.js
|
||||
|
||||
@@ -110,3 +124,45 @@ Finally, you're ready to use Fission!
|
||||
$ curl http://$FISSION_ROUTER/hello
|
||||
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
|
||||
# After Fission 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).
|
||||
|
||||
@@ -63,7 +63,7 @@ Usage
|
||||
$ fission env create --name nodejs --image fission/node-env
|
||||
|
||||
# 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
|
||||
$ fission function create --name hello --env nodejs --code hello.js
|
||||
@@ -88,14 +88,14 @@ You can install Kubernetes on your laptop with [minikube](https://github.com/kub
|
||||
#### 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/
|
||||
$ 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
|
||||
```
|
||||
|
||||
#### 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/
|
||||
$ 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
|
||||
```
|
||||
|
||||
@@ -150,6 +150,37 @@ 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}')
|
||||
```
|
||||
### 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 http://fission.io/fission-openshift.yaml
|
||||
$ oc create -f http://fission.io/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 http://fission.io/fission-openshift.yaml
|
||||
$ oc create -f http://fission.io/fission-cloud.yaml
|
||||
```
|
||||
After these steps, you should be able to run fission client as with kubernetes.
|
||||
|
||||
### Install the client CLI
|
||||
|
||||
@@ -172,7 +203,7 @@ 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
|
||||
$ curl https://raw.githubusercontent.com/fission/fission/master/examples/nodejs/hello.js > hello.js
|
||||
|
||||
$ fission function create --name hello --env nodejs --code hello.js
|
||||
|
||||
@@ -182,6 +213,9 @@ Finally, you're ready to use Fission!
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
You can also set up persistence for logs: [instructions here](INSTALL.md).
|
||||
|
||||
|
||||
Compiling Fission
|
||||
=================
|
||||
|
||||
@@ -189,15 +223,19 @@ Compiling Fission
|
||||
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.
|
||||
You'll need go 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, then from the top level
|
||||
directory:
|
||||
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
|
||||
@@ -207,7 +245,8 @@ directory:
|
||||
$ pushd fission-bundle
|
||||
$ ./build.sh
|
||||
|
||||
# Edit push.sh to point to your registry
|
||||
# Edit push.sh to point to your registry, or comment out the `docker push`
|
||||
# line if building into your local minikube for dev purposes
|
||||
$ $EDITOR push.sh
|
||||
$ ./push.sh
|
||||
$ popd
|
||||
|
||||
Vendored
+2
-2
@@ -17,10 +17,10 @@ limitations under the License.
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
|
||||
Vendored
+5
-3
@@ -16,9 +16,11 @@ limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import "testing"
|
||||
import "log"
|
||||
import "time"
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func checkErr(err error) {
|
||||
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
|
||||
+46
-7
@@ -21,25 +21,37 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fission/fission"
|
||||
"github.com/fission/fission/fission/logdb"
|
||||
)
|
||||
|
||||
type API struct {
|
||||
FunctionStore
|
||||
HTTPTriggerStore
|
||||
EnvironmentStore
|
||||
WatchStore
|
||||
}
|
||||
type (
|
||||
API struct {
|
||||
FunctionStore
|
||||
HTTPTriggerStore
|
||||
TimeTriggerStore
|
||||
EnvironmentStore
|
||||
WatchStore
|
||||
}
|
||||
|
||||
logDBConfig struct {
|
||||
httpURL string
|
||||
username string
|
||||
password string
|
||||
}
|
||||
)
|
||||
|
||||
func MakeAPI(rs *ResourceStore) *API {
|
||||
api := &API{
|
||||
FunctionStore: FunctionStore{ResourceStore: *rs},
|
||||
HTTPTriggerStore: HTTPTriggerStore{ResourceStore: *rs},
|
||||
TimeTriggerStore: TimeTriggerStore{ResourceStore: *rs},
|
||||
EnvironmentStore: EnvironmentStore{ResourceStore: *rs},
|
||||
WatchStore: WatchStore{ResourceStore: *rs},
|
||||
}
|
||||
@@ -47,6 +59,7 @@ func MakeAPI(rs *ResourceStore) *API {
|
||||
}
|
||||
|
||||
func (api *API) respondWithSuccess(w http.ResponseWriter, resp []byte) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_, err := w.Write(resp)
|
||||
if err != nil {
|
||||
// this will probably fail too, but try anyway
|
||||
@@ -61,7 +74,25 @@ func (api *API) respondWithError(w http.ResponseWriter, err error) {
|
||||
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) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
fmt.Fprintf(w, "{\"message\": \"Fission API\", \"version\": \"0.1.0\"}\n")
|
||||
}
|
||||
|
||||
@@ -93,6 +124,14 @@ func (api *API) Serve(port int) {
|
||||
r.HandleFunc("/v1/watches/{watch}", api.WatchApiUpdate).Methods("PUT")
|
||||
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("/proxy/{dbType}", api.FunctionLogsApiPost).Methods("POST")
|
||||
|
||||
address := fmt.Sprintf(":%v", port)
|
||||
|
||||
log.WithFields(log.Fields{"port": port}).Info("Server started")
|
||||
|
||||
+175
-1
@@ -24,8 +24,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
etcdClient "github.com/coreos/etcd/client"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/fission/fission"
|
||||
@@ -36,6 +36,27 @@ var g struct {
|
||||
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) {
|
||||
log.SetFormatter(&log.TextFormatter{DisableColors: true})
|
||||
|
||||
@@ -50,12 +71,17 @@ func TestFunctionApi(t *testing.T) {
|
||||
},
|
||||
Code: "code1",
|
||||
}
|
||||
_, err := g.client.FunctionGet(&fission.Metadata{Name: "foo"})
|
||||
assertNotFoundFails(err, "function")
|
||||
|
||||
m, err := g.client.FunctionCreate(testFunc)
|
||||
panicIf(err)
|
||||
uid1 := 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)
|
||||
panicIf(err)
|
||||
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,
|
||||
"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"})
|
||||
panicIf(err)
|
||||
err = g.client.FunctionDelete(&fission.Metadata{Name: "bar"})
|
||||
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) {
|
||||
testTrigger := &fission.HTTPTrigger{
|
||||
Metadata: fission.Metadata{
|
||||
@@ -119,10 +214,16 @@ func TestHTTPTriggerApi(t *testing.T) {
|
||||
Uid: "",
|
||||
},
|
||||
}
|
||||
_, err := g.client.HTTPTriggerGet(&fission.Metadata{Name: "foo"})
|
||||
assertNotFoundFails(err, "trigger")
|
||||
|
||||
m, err := g.client.HTTPTriggerCreate(testTrigger)
|
||||
panicIf(err)
|
||||
defer g.client.HTTPTriggerDelete(m)
|
||||
|
||||
_, err = g.client.HTTPTriggerCreate(testTrigger)
|
||||
assertNameReuseFails(err, "trigger")
|
||||
|
||||
tr, err := g.client.HTTPTriggerGet(m)
|
||||
panicIf(err)
|
||||
testTrigger.Metadata.Uid = m.Uid
|
||||
@@ -140,6 +241,10 @@ func TestHTTPTriggerApi(t *testing.T) {
|
||||
|
||||
testTrigger.Metadata.Name = "yyy"
|
||||
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)
|
||||
defer g.client.HTTPTriggerDelete(m)
|
||||
|
||||
@@ -156,10 +261,16 @@ func TestEnvironmentApi(t *testing.T) {
|
||||
},
|
||||
RunContainerImageUrl: "gcr.io/xyz",
|
||||
}
|
||||
_, err := g.client.EnvironmentGet(&fission.Metadata{Name: "foo"})
|
||||
assertNotFoundFails(err, "environment")
|
||||
|
||||
m, err := g.client.EnvironmentCreate(testEnv)
|
||||
panicIf(err)
|
||||
defer g.client.EnvironmentDelete(m)
|
||||
|
||||
_, err = g.client.EnvironmentCreate(testEnv)
|
||||
assertNameReuseFails(err, "environment")
|
||||
|
||||
tr, err := g.client.EnvironmentGet(m)
|
||||
panicIf(err)
|
||||
testEnv.Metadata.Uid = m.Uid
|
||||
@@ -201,10 +312,16 @@ func TestWatchApi(t *testing.T) {
|
||||
},
|
||||
Target: "",
|
||||
}
|
||||
_, err := g.client.WatchGet(&fission.Metadata{Name: "foo"})
|
||||
assertNotFoundFails(err, "watch")
|
||||
|
||||
m, err := g.client.WatchCreate(testWatch)
|
||||
panicIf(err)
|
||||
defer g.client.WatchDelete(m)
|
||||
|
||||
_, err = g.client.WatchCreate(testWatch)
|
||||
assertNameReuseFails(err, "watch")
|
||||
|
||||
w, err := g.client.WatchGet(m)
|
||||
panicIf(err)
|
||||
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")
|
||||
}
|
||||
|
||||
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) {
|
||||
flag.Parse()
|
||||
|
||||
@@ -232,6 +396,7 @@ func TestMain(m *testing.M) {
|
||||
|
||||
ks.Delete(context.Background(), "Function", &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(), "Watch", &etcdClient.DeleteOptions{Recursive: true})
|
||||
|
||||
@@ -241,6 +406,15 @@ func TestMain(m *testing.M) {
|
||||
resp, err := http.Get("http://localhost:8888/")
|
||||
panicIf(err)
|
||||
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)
|
||||
panicIf(err)
|
||||
|
||||
|
||||
+125
-31
@@ -26,8 +26,6 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
@@ -80,19 +78,15 @@ func (c *Client) url(relativeUrl string) string {
|
||||
|
||||
func (c *Client) handleResponse(resp *http.Response) ([]byte, error) {
|
||||
if resp.StatusCode != 200 {
|
||||
var errCode int
|
||||
switch resp.StatusCode {
|
||||
case 403:
|
||||
errCode = fission.ErrorNotAuthorized
|
||||
case 404:
|
||||
errCode = fission.ErrorNotFound
|
||||
case 400:
|
||||
errCode = fission.ErrorInvalidArgument
|
||||
default:
|
||||
errCode = fission.ErrorInternal
|
||||
}
|
||||
return nil, fission.MakeError(errCode,
|
||||
fmt.Sprintf("HTTP error %v", resp.StatusCode))
|
||||
return nil, fission.MakeErrorFromHTTP(resp)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
func (c *Client) handleCreateResponse(resp *http.Response) ([]byte, error) {
|
||||
if resp.StatusCode != 201 {
|
||||
return nil, fission.MakeErrorFromHTTP(resp)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
return body, err
|
||||
@@ -114,12 +108,8 @@ func (c *Client) FunctionCreate(f *fission.Function) (*fission.Metadata, error)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
body, err := c.handleCreateResponse(resp)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"name": f.Metadata.Name,
|
||||
"err": err,
|
||||
}).Error("Failed to create function")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -223,6 +213,7 @@ func (c *Client) FunctionList() ([]fission.Function, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
@@ -250,12 +241,8 @@ func (c *Client) HTTPTriggerCreate(t *fission.HTTPTrigger) (*fission.Metadata, e
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
body, err := c.handleCreateResponse(resp)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"name": t.Metadata.Name,
|
||||
"err": err,
|
||||
}).Error("Failed to create http trigger")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -334,6 +321,7 @@ func (c *Client) HTTPTriggerList() ([]fission.HTTPTrigger, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
@@ -361,12 +349,8 @@ func (c *Client) EnvironmentCreate(env *fission.Environment) (*fission.Metadata,
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
body, err := c.handleCreateResponse(resp)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"name": env.Metadata.Name,
|
||||
"err": err,
|
||||
}).Error("Failed to create environment")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -445,6 +429,7 @@ func (c *Client) EnvironmentList() ([]fission.Environment, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
@@ -472,7 +457,7 @@ func (c *Client) WatchCreate(w *fission.Watch) (*fission.Metadata, error) {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
body, err := c.handleCreateResponse(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -532,6 +517,7 @@ func (c *Client) WatchList() ([]fission.Watch, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := c.handleResponse(resp)
|
||||
if err != nil {
|
||||
@@ -546,3 +532,111 @@ func (c *Client) WatchList() ([]fission.Watch, error) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
@@ -47,8 +47,8 @@ func (api *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
var env fission.Environment
|
||||
err = json.Unmarshal(body, &env)
|
||||
@@ -71,6 +71,7 @@ func (api *API) EnvironmentApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
@@ -103,6 +104,7 @@ func (api *API) EnvironmentApiUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var env fission.Environment
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type requestType int
|
||||
@@ -62,6 +62,9 @@ func MakeFileStore(path string) *FileStore {
|
||||
return nil
|
||||
}
|
||||
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)
|
||||
case DELETE:
|
||||
response.error = os.Remove(path.Join(fs.root, req.fileName))
|
||||
if os.IsNotExist(response.error) {
|
||||
response.error = nil
|
||||
}
|
||||
default:
|
||||
log.Panic("bad request")
|
||||
}
|
||||
|
||||
@@ -17,14 +17,16 @@ limitations under the License.
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"encoding/json"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"encoding/base64"
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
@@ -48,6 +50,7 @@ func (api *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var f fission.Function
|
||||
@@ -77,6 +80,7 @@ func (api *API) FunctionApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
@@ -115,6 +119,7 @@ func (api *API) FunctionApiUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var f fission.Function
|
||||
@@ -170,3 +175,33 @@ func (api *API) FunctionApiDelete(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
log "github.com/Sirupsen/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
@@ -108,7 +108,26 @@ func (fs *FunctionStore) Delete(m fission.Metadata) error {
|
||||
if err != nil {
|
||||
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) {
|
||||
|
||||
@@ -21,8 +21,8 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
@@ -47,6 +47,7 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var t fission.HTTPTrigger
|
||||
@@ -56,6 +57,20 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
@@ -69,6 +84,7 @@ func (api *API) HTTPTriggerApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
api.respondWithSuccess(w, resp)
|
||||
}
|
||||
|
||||
@@ -101,6 +117,7 @@ func (api *API) HTTPTriggerApiUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var t fission.HTTPTrigger
|
||||
|
||||
+48
-15
@@ -18,13 +18,17 @@ package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/etcd/client"
|
||||
"github.com/satori/go.uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -93,7 +97,7 @@ func (rs *ResourceStore) create(r resource) error {
|
||||
|
||||
_, err = rs.KeysAPI.Set(context.Background(), key, string(serialized),
|
||||
&client.SetOptions{PrevExist: client.PrevNoExist})
|
||||
return err
|
||||
return handleEtcdErrorForResource(err, r)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
return handleEtcdError(err, typName, rkey)
|
||||
}
|
||||
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),
|
||||
&client.SetOptions{PrevExist: client.PrevExist})
|
||||
return err
|
||||
return handleEtcdErrorForResource(err, r)
|
||||
}
|
||||
|
||||
func (rs *ResourceStore) delete(typename, rkey string) error {
|
||||
key := typename + "/" + rkey
|
||||
_, 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
|
||||
// doesn't exist, returns an empty slice.
|
||||
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 client.IsKeyNotFound(err) {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, err
|
||||
return nil, handleEtcdError(err, "", key)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
_ = rs.FileStore.delete(uid)
|
||||
return "", "", err
|
||||
return "", "", handleEtcdError(err, "file", parentKey)
|
||||
}
|
||||
|
||||
return resp.Node.Key, uid, nil
|
||||
@@ -172,7 +176,7 @@ func (rs *ResourceStore) readFile(key string, uid *string) ([]byte, error) {
|
||||
key = "file/" + key
|
||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, handleEtcdError(err, "file", key)
|
||||
}
|
||||
|
||||
if uid == nil {
|
||||
@@ -201,7 +205,7 @@ func (rs *ResourceStore) deleteFile(key string, uid string) error {
|
||||
key = "file/" + key
|
||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
||||
if err != nil {
|
||||
return err
|
||||
return handleEtcdError(err, "file", key)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
return handleEtcdError(err, "", node.Key)
|
||||
}
|
||||
|
||||
if len(resp.Node.Nodes) == 1 {
|
||||
_, err = rs.KeysAPI.Delete(context.Background(), key, &client.DeleteOptions{Dir: true})
|
||||
return err
|
||||
return handleEtcdError(err, "file", key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -236,7 +240,7 @@ func (rs *ResourceStore) deleteAllFiles(key string) error {
|
||||
key = "file/" + key
|
||||
resp, err := rs.KeysAPI.Get(context.Background(), key, &client.GetOptions{Sort: true})
|
||||
if err != nil {
|
||||
return err
|
||||
return handleEtcdError(err, "file", key)
|
||||
}
|
||||
for _, u := range resp.Node.Nodes {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
return handleEtcdError(err, "", u.Key)
|
||||
}
|
||||
}
|
||||
_, 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"
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
@@ -47,6 +47,7 @@ func (api *API) WatchApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
api.respondWithError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var watch fission.Watch
|
||||
@@ -71,6 +72,7 @@ func (api *API) WatchApiCreate(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
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.
|
||||
|
||||
FROM node:4-onbuild
|
||||
FROM node:7-onbuild
|
||||
|
||||
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",
|
||||
"version": "0.0.0",
|
||||
"author": "Soam Vasani",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Soam Vasani",
|
||||
"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"
|
||||
"name": "fission-nodejs-runtime",
|
||||
"version": "0.1.0",
|
||||
"author": "Soam Vasani",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Soam Vasani",
|
||||
"email": "soamvasani@platform9.com"
|
||||
}
|
||||
],
|
||||
"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';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const process = require('process');
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
@@ -18,6 +19,16 @@ if (!argv.port) {
|
||||
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.
|
||||
let userFunction;
|
||||
|
||||
@@ -64,11 +75,13 @@ app.all('/', function (req, res) {
|
||||
res.status(500).send("Generic container: no requests supported");
|
||||
return;
|
||||
}
|
||||
|
||||
const context = {
|
||||
request: req,
|
||||
response: res
|
||||
// TODO: context should also have: URL template params, query string
|
||||
};
|
||||
|
||||
function callback(status, body, headers) {
|
||||
if (!status)
|
||||
return;
|
||||
@@ -79,12 +92,32 @@ app.all('/', function (req, res) {
|
||||
}
|
||||
res.status(status).send(body);
|
||||
}
|
||||
try {
|
||||
userFunction(context, callback);
|
||||
} catch(e) {
|
||||
console.log(`Function error: ${e}`);
|
||||
callback(500, "Internal server error")
|
||||
|
||||
//
|
||||
// Customizing the request context
|
||||
//
|
||||
// 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);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
module.exports = function (context, callback) {
|
||||
module.exports = async function (context) {
|
||||
console.log("headers=", JSON.stringify(context.request.headers));
|
||||
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 apt-get install -y python3 python3-pip python3-dev build-essential
|
||||
RUN apk update
|
||||
RUN apk add --no-cache python3 python3-dev build-base
|
||||
RUN pip3 install --upgrade pip
|
||||
RUN rm -r /root/.cache
|
||||
|
||||
COPY . /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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
@@ -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,41 @@
|
||||
# Fission: Ruby Environment
|
||||
|
||||
This is the Ruby environment for Fission.
|
||||
|
||||
It's a Docker image containing a Ruby 2.4.1 runtime, along with a
|
||||
dynamic loader. A few common dependencies are included in the
|
||||
Gemfile.
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,66 @@
|
||||
# frozen_string_literal: true
|
||||
require 'rack'
|
||||
|
||||
CODEPATH = '/userfunc/user'
|
||||
|
||||
module Fission
|
||||
class Request < Rack::Request
|
||||
def headers
|
||||
Hash[
|
||||
*env.select { |k,v| k.start_with?('HTTP_') }
|
||||
.map { |k,v| [k.sub(/\AHTTP_/, '').split('_').map(&:capitalize).join('-'), v] }
|
||||
.sort
|
||||
.flatten
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
class Context
|
||||
attr_reader :env
|
||||
|
||||
def initialize(env)
|
||||
@env = env
|
||||
end
|
||||
|
||||
def request
|
||||
@request ||= Request.new(env)
|
||||
end
|
||||
end
|
||||
|
||||
module Specializer
|
||||
def self.call(env)
|
||||
load CODEPATH
|
||||
Rack::Response.new([], 201).finish
|
||||
rescue
|
||||
Rack::Response.new(['500 Internal Server Error'], 500, {}).finish
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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 (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("(Error %v) %v", e.Code, e.Message)
|
||||
func (err Error) Error() string {
|
||||
return fmt.Sprintf("%v - %v", err.Description(), err.Message)
|
||||
}
|
||||
|
||||
func MakeError(code int, msg string) Error {
|
||||
@@ -36,29 +38,39 @@ func MakeErrorFromHTTP(resp *http.Response) error {
|
||||
|
||||
var errCode int
|
||||
switch resp.StatusCode {
|
||||
case 400:
|
||||
errCode = ErrorInvalidArgument
|
||||
case 403:
|
||||
errCode = ErrorNotAuthorized
|
||||
case 404:
|
||||
errCode = ErrorNotFound
|
||||
case 400:
|
||||
errCode = ErrorInvalidArgument
|
||||
case 409:
|
||||
errCode = ErrorNameExists
|
||||
default:
|
||||
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 {
|
||||
var code int
|
||||
switch err.Code {
|
||||
case ErrorNotFound:
|
||||
code = 404
|
||||
case ErrorInvalidArgument:
|
||||
code = 400
|
||||
case ErrorNoSpace:
|
||||
code = 500
|
||||
case ErrorNotAuthorized:
|
||||
code = 403
|
||||
case ErrorNotFound:
|
||||
code = 404
|
||||
case ErrorNameExists:
|
||||
code = 409
|
||||
default:
|
||||
code = 500
|
||||
}
|
||||
@@ -70,11 +82,19 @@ func GetHTTPError(err error) (int, string) {
|
||||
var code int
|
||||
fe, ok := err.(Error)
|
||||
if ok {
|
||||
msg = fe.Message
|
||||
code = fe.HTTPStatus()
|
||||
msg = fe.Message
|
||||
} else {
|
||||
code = 500
|
||||
msg = err.Error()
|
||||
}
|
||||
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) {
|
||||
callback(200, "Hello, world!\n");
|
||||
module.exports = async function(context) {
|
||||
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';
|
||||
|
||||
var http = require('http');
|
||||
const rp = require('request-promise-native');
|
||||
|
||||
module.exports = function (context, callback) {
|
||||
let body = context.request.body;
|
||||
console.log(`body text: ${body['text']}`);
|
||||
module.exports = async function (context) {
|
||||
const body = context.request.body;
|
||||
console.log(`body = ${body}`);
|
||||
const symbol = body['text'].split(' ')[1];
|
||||
|
||||
var symbol = body['text'].split(' ')[1];
|
||||
console.log(`Got symbol: ${symbol}`);
|
||||
|
||||
http.get({
|
||||
host: 'finance.google.com',
|
||||
path: `/finance/info?q=NYSE:${symbol}`
|
||||
}, function(response) {
|
||||
var resp = '';
|
||||
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?)" }`);
|
||||
if (!symbol) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
text: 'You must provide a stock 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]
|
||||
ul = "<ul>%s</ul>" % "\n".join(items)
|
||||
return """
|
||||
<html><body>
|
||||
<html><body style="font-family:sans-serif;font-size:2rem;padding:40px">
|
||||
<h1>Guestbook</h1>
|
||||
<form action="/guestbook" method="POST">
|
||||
<input type="text" name="text">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,13 +7,18 @@ import (
|
||||
"github.com/docopt/docopt-go"
|
||||
"github.com/fission/fission/controller"
|
||||
"github.com/fission/fission/kubewatcher"
|
||||
"github.com/fission/fission/logger"
|
||||
"github.com/fission/fission/poolmgr"
|
||||
"github.com/fission/fission/router"
|
||||
"github.com/fission/fission/timer"
|
||||
)
|
||||
|
||||
func runController(port int, etcdUrl string, filepath string) {
|
||||
// filePath will be created if it doesn't exist.
|
||||
fileStore := controller.MakeFileStore(filepath)
|
||||
if fileStore == nil {
|
||||
log.Fatalf("Failed to initialize filestore")
|
||||
}
|
||||
|
||||
rs, err := controller.MakeResourceStore(fileStore, []string{etcdUrl})
|
||||
if err != nil {
|
||||
@@ -44,6 +49,18 @@ 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 getPort(portArg interface{}) int {
|
||||
portArgStr := portArg.(string)
|
||||
port, err := strconv.Atoi(portArgStr)
|
||||
@@ -77,6 +94,8 @@ Usage:
|
||||
fission-bundle --routerPort=<port> [--controllerUrl=<url> --poolmgrUrl=<url>]
|
||||
fission-bundle --poolmgrPort=<port> [--controllerUrl=<url> --namespace=<namespace>]
|
||||
fission-bundle --kubewatcher [--controllerUrl=<url> --routerUrl=<url>]
|
||||
fission-bundle --logger
|
||||
fission-bundle --timer [--controllerUrl=<url> --routerUrl=<url>]
|
||||
Options:
|
||||
--controllerPort=<port> Port that the controller should listen on.
|
||||
--routerPort=<port> Port that the router should listen on.
|
||||
@@ -88,6 +107,8 @@ Options:
|
||||
--filepath=<filepath> Directory to store functions in.
|
||||
--namespace=<namespace> Kubernetes namespace in which to run function containers. Defaults to 'fission-function'.
|
||||
--kubewatcher Start Kubernetes events watcher.
|
||||
--logger Start logger.
|
||||
--timer Start Timer.
|
||||
`
|
||||
arguments, err := docopt.Parse(usage, nil, true, "fission-bundle", false)
|
||||
if err != nil {
|
||||
@@ -120,5 +141,13 @@ Options:
|
||||
runKubeWatcher(controllerUrl, routerUrl)
|
||||
}
|
||||
|
||||
if arguments["--logger"] == true {
|
||||
runLogger()
|
||||
}
|
||||
|
||||
if arguments["--timer"] == true {
|
||||
runTimer(controllerUrl, routerUrl)
|
||||
}
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
@@ -30,3 +30,22 @@ spec:
|
||||
nodePort: 31313
|
||||
selector:
|
||||
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
|
||||
@@ -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
|
||||
@@ -30,3 +30,22 @@ spec:
|
||||
nodePort: 31313
|
||||
selector:
|
||||
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
|
||||
@@ -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
|
||||
@@ -0,0 +1,174 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: fission
|
||||
labels:
|
||||
name: fission
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: fission-function
|
||||
labels:
|
||||
name: fission-function
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: fission-svc
|
||||
namespace: fission
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1alpha1
|
||||
metadata:
|
||||
name: fission-admin
|
||||
namespace: fission
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: fission-svc
|
||||
namespace: fission
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: admin
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1alpha1
|
||||
metadata:
|
||||
name: fission-function-admin
|
||||
namespace: fission-function
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: fission-svc
|
||||
namespace: fission
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: admin
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: controller
|
||||
namespace: fission
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
svc: controller
|
||||
spec:
|
||||
containers:
|
||||
- name: controller
|
||||
image: fission/fission-bundle
|
||||
command: ["/fission-bundle"]
|
||||
args: ["--controllerPort", "8888", "--filepath", "/filestore"]
|
||||
serviceAccountName: fission-svc
|
||||
---
|
||||
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"]
|
||||
serviceAccountName: fission-svc
|
||||
---
|
||||
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"]
|
||||
serviceAccountName: fission-svc
|
||||
---
|
||||
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"]
|
||||
serviceAccountName: fission-svc
|
||||
---
|
||||
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
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: ETCD_LISTEN_CLIENT_URLS
|
||||
value: http://0.0.0.0:2379
|
||||
- name: ETCD_ADVERTISE_CLIENT_URLS
|
||||
value: http://etcd:2379
|
||||
serviceAccountName: fission-svc
|
||||
+24
-4
@@ -28,7 +28,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: controller
|
||||
image: fission/fission-bundle:alpha10
|
||||
image: fission/fission-bundle
|
||||
command: ["/fission-bundle"]
|
||||
args: ["--controllerPort", "8888", "--filepath", "/filestore"]
|
||||
|
||||
@@ -47,7 +47,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: router
|
||||
image: fission/fission-bundle:alpha10
|
||||
image: fission/fission-bundle
|
||||
command: ["/fission-bundle"]
|
||||
args: ["--routerPort", "8888"]
|
||||
|
||||
@@ -81,7 +81,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: poolmgr
|
||||
image: fission/fission-bundle:alpha10
|
||||
image: fission/fission-bundle
|
||||
command: ["/fission-bundle"]
|
||||
args: ["--poolmgrPort", "8888"]
|
||||
|
||||
@@ -100,10 +100,29 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: kubewatcher
|
||||
image: fission/fission-bundle:alpha10
|
||||
image: fission/fission-bundle
|
||||
command: ["/fission-bundle"]
|
||||
args: ["--kubewatcher"]
|
||||
|
||||
---
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: timer
|
||||
namespace: fission
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
svc: timer
|
||||
spec:
|
||||
containers:
|
||||
- name: timer
|
||||
image: fission/fission-bundle
|
||||
command: ["/fission-bundle"]
|
||||
args: ["--timer"]
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -135,6 +154,7 @@ spec:
|
||||
containers:
|
||||
- name: etcd
|
||||
image: quay.io/coreos/etcd
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: ETCD_LISTEN_CLIENT_URLS
|
||||
value: http://0.0.0.0:2379
|
||||
|
||||
+6
-1
@@ -35,7 +35,12 @@ func getClient(serverUrl string) *client.Client {
|
||||
fatal("Need --server or FISSION_URL set to your fission server.")
|
||||
}
|
||||
|
||||
serverUrl = "http://" + strings.TrimPrefix(serverUrl, "http://")
|
||||
isHTTPS := strings.Index(serverUrl, "https://") == 0
|
||||
isHTTP := strings.Index(serverUrl, "http://") == 0
|
||||
|
||||
if !(isHTTP || isHTTPS) {
|
||||
serverUrl = "http://" + serverUrl
|
||||
}
|
||||
|
||||
return client.MakeClient(serverUrl)
|
||||
}
|
||||
|
||||
+26
-3
@@ -18,19 +18,26 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/fission/fission"
|
||||
"os"
|
||||
)
|
||||
|
||||
func envCreate(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
|
||||
envName := c.String("name")
|
||||
if len(envName) == 0 {
|
||||
fatal("Need a name, use --name.")
|
||||
}
|
||||
|
||||
envImg := c.String("image")
|
||||
if len(envImg) == 0 {
|
||||
fatal("Need an image, use --image.")
|
||||
}
|
||||
|
||||
env := &fission.Environment{
|
||||
Metadata: fission.Metadata{
|
||||
@@ -50,6 +57,10 @@ func envGet(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
|
||||
envName := c.String("name")
|
||||
if len(envName) == 0 {
|
||||
fatal("Need a name, use --name.")
|
||||
}
|
||||
|
||||
m := &fission.Metadata{Name: envName}
|
||||
env, err := client.EnvironmentGet(m)
|
||||
checkErr(err, "get environment")
|
||||
@@ -66,12 +77,20 @@ func envUpdate(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
|
||||
envName := c.String("name")
|
||||
image := c.String("image")
|
||||
if len(envName) == 0 {
|
||||
fatal("Need a name, use --name.")
|
||||
}
|
||||
|
||||
envImg := c.String("image")
|
||||
if len(envImg) == 0 {
|
||||
fatal("Need an image, use --image.")
|
||||
}
|
||||
|
||||
env := &fission.Environment{
|
||||
Metadata: fission.Metadata{
|
||||
Name: envName,
|
||||
},
|
||||
RunContainerImageUrl: image,
|
||||
RunContainerImageUrl: envImg,
|
||||
}
|
||||
|
||||
_, err := client.EnvironmentUpdate(env)
|
||||
@@ -85,6 +104,10 @@ func envDelete(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
|
||||
envName := c.String("name")
|
||||
if len(envName) == 0 {
|
||||
fatal("Need a name , use --name.")
|
||||
}
|
||||
|
||||
m := &fission.Metadata{Name: envName}
|
||||
err := client.EnvironmentDelete(m)
|
||||
checkErr(err, "delete environment")
|
||||
|
||||
+161
-6
@@ -17,18 +17,51 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/satori/go.uuid"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/fission/fission"
|
||||
"github.com/fission/fission/fission/logdb"
|
||||
)
|
||||
|
||||
func fnFetchCode(filePath string) []byte {
|
||||
var code []byte
|
||||
var err error
|
||||
|
||||
if strings.HasPrefix(filePath, "http://") || strings.HasPrefix(filePath, "https://") {
|
||||
var resp *http.Response
|
||||
resp, err = http.Get(filePath)
|
||||
if err != nil {
|
||||
checkErr(err, fmt.Sprintf("download function"))
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("%v - HTTP response returned non 200 status", resp.StatusCode)
|
||||
checkErr(err, fmt.Sprintf("download function"))
|
||||
}
|
||||
|
||||
code, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
checkErr(err, fmt.Sprintf("download function body %v", filePath))
|
||||
}
|
||||
} else {
|
||||
code, err = ioutil.ReadFile(filePath)
|
||||
checkErr(err, fmt.Sprintf("read %v", filePath))
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func fnCreate(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
|
||||
@@ -44,11 +77,13 @@ func fnCreate(c *cli.Context) error {
|
||||
|
||||
fileName := c.String("code")
|
||||
if len(fileName) == 0 {
|
||||
fatal("Need --code argument.")
|
||||
fileName = c.String("package")
|
||||
if len(fileName) == 0 {
|
||||
fatal("Need --code or --package argument.")
|
||||
}
|
||||
}
|
||||
|
||||
code, err := ioutil.ReadFile(fileName)
|
||||
checkErr(err, fmt.Sprintf("read %v", fileName))
|
||||
code := fnFetchCode(fileName)
|
||||
|
||||
function := &fission.Function{
|
||||
Metadata: fission.Metadata{Name: fnName},
|
||||
@@ -56,7 +91,7 @@ func fnCreate(c *cli.Context) error {
|
||||
Code: string(code),
|
||||
}
|
||||
|
||||
_, err = client.FunctionCreate(function)
|
||||
_, err := client.FunctionCreate(function)
|
||||
checkErr(err, "create function")
|
||||
|
||||
fmt.Printf("function '%v' created\n", fnName)
|
||||
@@ -109,6 +144,10 @@ func fnGetMeta(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
|
||||
fnName := c.String("name")
|
||||
if len(fnName) == 0 {
|
||||
fatal("Need name of function, use --name")
|
||||
}
|
||||
|
||||
fnUid := c.String("uid")
|
||||
m := &fission.Metadata{Name: fnName, Uid: fnUid}
|
||||
|
||||
@@ -127,6 +166,9 @@ func fnUpdate(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
|
||||
fnName := c.String("name")
|
||||
if len(fnName) == 0 {
|
||||
fatal("Need name of function, use --name")
|
||||
}
|
||||
|
||||
function, err := client.FunctionGet(&fission.Metadata{Name: fnName})
|
||||
checkErr(err, fmt.Sprintf("read function '%v'", fnName))
|
||||
@@ -138,8 +180,7 @@ func fnUpdate(c *cli.Context) error {
|
||||
|
||||
fileName := c.String("code")
|
||||
if len(fileName) > 0 {
|
||||
code, err := ioutil.ReadFile(fileName)
|
||||
checkErr(err, fmt.Sprintf("read %v", fileName))
|
||||
code := fnFetchCode(fileName)
|
||||
|
||||
function.Code = string(code)
|
||||
}
|
||||
@@ -238,3 +279,117 @@ func fnEdit(c *cli.Context) error {
|
||||
fmt.Printf("function %v updated, new uuid: %v\n", newfn.Name, newfn.Uid)
|
||||
return nil
|
||||
}
|
||||
|
||||
func fnLogs(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
|
||||
fnName := c.String("name")
|
||||
if len(fnName) == 0 {
|
||||
fatal("Need name of function, use --name")
|
||||
}
|
||||
|
||||
dbType := c.String("dbtype")
|
||||
if len(dbType) == 0 {
|
||||
dbType = logdb.INFLUXDB
|
||||
}
|
||||
|
||||
fnPod := c.String("pod")
|
||||
m := &fission.Metadata{Name: fnName}
|
||||
|
||||
f, err := client.FunctionGet(m)
|
||||
checkErr(err, "get function")
|
||||
|
||||
// client first send db query to controller, then controller will
|
||||
// establishe a proxy server that bridges the client and the database.
|
||||
logDB, err := logdb.GetLogDB(dbType, c.GlobalString("server"))
|
||||
if err != nil {
|
||||
fatal("failed to connect log database")
|
||||
}
|
||||
|
||||
requestChan := make(chan struct{})
|
||||
responseChan := make(chan struct{})
|
||||
ctx := context.Background()
|
||||
|
||||
go func(ctx context.Context, requestChan, responseChan chan struct{}) {
|
||||
t := time.Unix(0, 0*int64(time.Millisecond))
|
||||
for {
|
||||
select {
|
||||
case <-requestChan:
|
||||
logFilter := logdb.LogFilter{
|
||||
Pod: fnPod,
|
||||
Function: f.Metadata.Name,
|
||||
FuncUid: f.Metadata.Uid,
|
||||
Since: t,
|
||||
}
|
||||
logEntries, err := logDB.GetLogs(logFilter)
|
||||
if err != nil {
|
||||
fatal("failed to query logs")
|
||||
}
|
||||
for _, logEntry := range logEntries {
|
||||
if c.Bool("d") {
|
||||
fmt.Printf("Timestamp: %s\nNamespace: %s\nFunction Name: %s\nFunction ID: %s\nPod: %s\nContainer: %s\nStream: %s\nLog: %s\n---\n",
|
||||
logEntry.Timestamp, logEntry.Namespace, logEntry.FuncName, logEntry.FuncUid, logEntry.Pod, logEntry.Container, logEntry.Stream, logEntry.Message)
|
||||
} else {
|
||||
fmt.Printf("[%s] %s\n", logEntry.Timestamp, logEntry.Message)
|
||||
}
|
||||
t = logEntry.Timestamp
|
||||
}
|
||||
responseChan <- struct{}{}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}(ctx, requestChan, responseChan)
|
||||
|
||||
for {
|
||||
requestChan <- struct{}{}
|
||||
<-responseChan
|
||||
if !c.Bool("f") {
|
||||
ctx.Done()
|
||||
return nil
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func fnPods(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
|
||||
fnName := c.String("name")
|
||||
if len(fnName) == 0 {
|
||||
fatal("Need name of function, use --name")
|
||||
}
|
||||
|
||||
dbType := c.String("dbtype")
|
||||
if len(dbType) == 0 {
|
||||
dbType = logdb.INFLUXDB
|
||||
}
|
||||
|
||||
m := &fission.Metadata{Name: fnName}
|
||||
|
||||
f, err := client.FunctionGet(m)
|
||||
checkErr(err, "get function")
|
||||
|
||||
// client first sends db query to the controller, then the controller
|
||||
// will establish a proxy server that bridges the client and the database.
|
||||
logDB, err := logdb.GetLogDB(dbType, c.GlobalString("server"))
|
||||
if err != nil {
|
||||
fatal("failed to connect log database")
|
||||
}
|
||||
|
||||
logFilter := logdb.LogFilter{
|
||||
Function: f.Metadata.Name,
|
||||
FuncUid: f.Metadata.Uid,
|
||||
}
|
||||
pods, err := logDB.GetPods(logFilter)
|
||||
if err != nil {
|
||||
fatal("failed to get pods of function")
|
||||
return err
|
||||
}
|
||||
fmt.Printf("NAME\t\n")
|
||||
for _, pod := range pods {
|
||||
fmt.Println(pod)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
Copyright 2016 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 logdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
influxdbClient "github.com/influxdata/influxdb/client/v2"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
const (
|
||||
INFLUXDB_DATABASE = "fissionFunctionLog"
|
||||
INFLUXDB_URL = "http://influxdb:8086/query"
|
||||
)
|
||||
|
||||
func NewInfluxDB(serverURL string) (InfluxDB, error) {
|
||||
return InfluxDB{endpoint: serverURL}, nil
|
||||
}
|
||||
|
||||
type InfluxDB struct {
|
||||
endpoint string
|
||||
}
|
||||
|
||||
func (influx InfluxDB) GetPods(filter LogFilter) ([]string, error) {
|
||||
parameters := make(map[string]interface{})
|
||||
parameters["funcuid"] = filter.FuncUid
|
||||
|
||||
queryCmd := "select * from \"log\" where \"funcuid\" = $funcuid group by \"pod\""
|
||||
query := influxdbClient.NewQueryWithParameters(queryCmd, INFLUXDB_DATABASE, "", parameters)
|
||||
|
||||
response, err := influx.query(query)
|
||||
if err != nil /*|| response.Err != ""*/ {
|
||||
return []string{}, err
|
||||
}
|
||||
pods := []string{}
|
||||
for _, r := range response.Results {
|
||||
for _, series := range r.Series {
|
||||
for _, pod := range series.Tags {
|
||||
pods = append(pods, pod)
|
||||
}
|
||||
}
|
||||
}
|
||||
return pods, nil
|
||||
}
|
||||
|
||||
func (influx InfluxDB) GetLogs(filter LogFilter) ([]LogEntry, error) {
|
||||
timestamp := filter.Since.UnixNano()
|
||||
var queryCmd string
|
||||
|
||||
// please check "Example 4: Bind a parameter in the WHERE clause to specific tag value"
|
||||
// at https://docs.influxdata.com/influxdb/v1.2/tools/api/
|
||||
parameters := make(map[string]interface{})
|
||||
parameters["funcuid"] = filter.FuncUid
|
||||
parameters["time"] = timestamp
|
||||
|
||||
if filter.Pod != "" {
|
||||
queryCmd = "select * from \"log\" where \"funcuid\" = $funcuid AND \"pod\" = $pod AND \"time\" > $time ORDER BY time ASC"
|
||||
parameters["pod"] = filter.Pod
|
||||
} else {
|
||||
queryCmd = "select * from \"log\" where \"funcuid\" = $funcuid AND \"time\" > $time ORDER BY time ASC"
|
||||
}
|
||||
|
||||
query := influxdbClient.NewQueryWithParameters(queryCmd, INFLUXDB_DATABASE, "", parameters)
|
||||
logEntries := []LogEntry{}
|
||||
response, err := influx.query(query)
|
||||
if err != nil {
|
||||
return logEntries, nil
|
||||
}
|
||||
for _, r := range response.Results {
|
||||
for _, series := range r.Series {
|
||||
for _, row := range series.Values {
|
||||
t, err := time.Parse(time.RFC3339, row[0].(string))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
logEntries = append(logEntries, LogEntry{
|
||||
Timestamp: t,
|
||||
Container: row[2].(string),
|
||||
FuncName: row[3].(string),
|
||||
FuncUid: row[4].(string),
|
||||
Message: strings.TrimSuffix(row[5].(string), "\n"),
|
||||
Namespace: row[6].(string),
|
||||
Pod: row[7].(string),
|
||||
Stream: row[8].(string),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return logEntries, nil
|
||||
}
|
||||
|
||||
func (influx InfluxDB) query(query influxdbClient.Query) (*influxdbClient.Response, error) {
|
||||
queryURL, err := url.Parse(influx.endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// connect to controller first, then controller will redirect our query command
|
||||
// to influxdb and proxy back the db response.
|
||||
queryURL.Path = fmt.Sprintf("/proxy/%s", INFLUXDB)
|
||||
req, err := http.NewRequest("POST", queryURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parametersBytes, err := json.Marshal(query.Parameters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set up http URL query string
|
||||
params := req.URL.Query()
|
||||
params.Set("q", query.Command)
|
||||
params.Set("db", query.Database)
|
||||
params.Set("params", string(parametersBytes))
|
||||
req.URL.RawQuery = params.Encode()
|
||||
|
||||
httpClient := http.Client{Timeout: 5 * time.Second}
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fission.MakeErrorFromHTTP(resp)
|
||||
}
|
||||
|
||||
// decode influxdb response
|
||||
response := influxdbClient.Response{}
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
decoder.UseNumber()
|
||||
if decoder.Decode(&response) != nil {
|
||||
return nil, fmt.Errorf("Failed to decode influxdb response: %v", err)
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2016 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 logdb
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
INFLUXDB = "influxdb"
|
||||
)
|
||||
|
||||
type LogDatabase interface {
|
||||
GetPods(LogFilter) ([]string, error)
|
||||
GetLogs(LogFilter) ([]LogEntry, error)
|
||||
}
|
||||
|
||||
type LogFilter struct {
|
||||
Pod string
|
||||
Function string
|
||||
FuncUid string
|
||||
Since time.Time
|
||||
}
|
||||
|
||||
type LogEntry struct {
|
||||
Timestamp time.Time
|
||||
Message string
|
||||
Stream string
|
||||
Container string
|
||||
Namespace string
|
||||
FuncName string
|
||||
FuncUid string
|
||||
Pod string
|
||||
}
|
||||
|
||||
func GetLogDB(dbType string, serverURL string) (LogDatabase, error) {
|
||||
switch dbType {
|
||||
case INFLUXDB:
|
||||
return NewInfluxDB(serverURL)
|
||||
}
|
||||
log.Fatalf("Log database type is incorrect, now only support %s", INFLUXDB)
|
||||
return nil, nil
|
||||
}
|
||||
+24
-3
@@ -38,16 +38,23 @@ func main() {
|
||||
// functions
|
||||
fnNameFlag := cli.StringFlag{Name: "name", Usage: "function name"}
|
||||
fnEnvNameFlag := cli.StringFlag{Name: "env", Usage: "environment name for function"}
|
||||
fnCodeFlag := cli.StringFlag{Name: "code", Usage: "file containing source code, or - for stdin"}
|
||||
fnCodeFlag := cli.StringFlag{Name: "code", Usage: "local path or URL for source code"}
|
||||
fnPackageFlag := cli.StringFlag{Name: "package", Usage: "local path or URL for binary package"}
|
||||
fnUidFlag := cli.StringFlag{Name: "uid", Usage: "function uid, optional (use latest if unspecified)"}
|
||||
fnPodFlag := cli.StringFlag{Name: "pod", Usage: "function pod name, optional (use latest if unspecified)"}
|
||||
fnFollowFlag := cli.BoolFlag{Name: "follow, f", Usage: "specify if the logs should be streamed"}
|
||||
fnDetailFlag := cli.BoolFlag{Name: "detail, d", Usage: "display detailed information"}
|
||||
fnLogDBTypeFlag := cli.StringFlag{Name: "dbtype", Usage: "log database type, e.g. influxdb (currently only influxdb is supported)"}
|
||||
fnSubcommands := []cli.Command{
|
||||
{Name: "create", Usage: "Create new function (and optionally, an HTTP route to it)", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, htUrlFlag, htMethodFlag}, Action: fnCreate},
|
||||
{Name: "create", Usage: "Create new function (and optionally, an HTTP route to it)", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnPackageFlag, htUrlFlag, htMethodFlag}, Action: fnCreate},
|
||||
{Name: "get", Usage: "Get function source code", Flags: []cli.Flag{fnNameFlag, fnUidFlag}, Action: fnGet},
|
||||
{Name: "edit", Usage: "Edit function source code in $EDITOR", Flags: []cli.Flag{fnNameFlag, fnUidFlag}, Action: fnEdit},
|
||||
{Name: "getmeta", Usage: "Get function metadata", Flags: []cli.Flag{fnNameFlag, fnUidFlag}, Action: fnGetMeta},
|
||||
{Name: "update", Usage: "Update function source code", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag}, Action: fnUpdate},
|
||||
{Name: "update", Usage: "Update function source code", Flags: []cli.Flag{fnNameFlag, fnEnvNameFlag, fnCodeFlag, fnPackageFlag}, Action: fnUpdate},
|
||||
{Name: "delete", Usage: "Delete function", Flags: []cli.Flag{fnNameFlag, fnUidFlag}, Action: fnDelete},
|
||||
{Name: "list", Usage: "List all functions", Flags: []cli.Flag{}, Action: fnList},
|
||||
{Name: "logs", Usage: "Display funtion logs", Flags: []cli.Flag{fnNameFlag, fnPodFlag, fnFollowFlag, fnDetailFlag, fnLogDBTypeFlag}, Action: fnLogs},
|
||||
{Name: "pods", Usage: "Display funtion pods", Flags: []cli.Flag{fnNameFlag, fnLogDBTypeFlag}, Action: fnPods},
|
||||
}
|
||||
|
||||
// httptriggers
|
||||
@@ -62,6 +69,19 @@ func main() {
|
||||
{Name: "list", Usage: "List HTTP triggers", Flags: []cli.Flag{}, Action: htList},
|
||||
}
|
||||
|
||||
// timetriggers
|
||||
ttNameFlag := cli.StringFlag{Name: "name", Usage: "Time Trigger name"}
|
||||
ttCronFlag := cli.StringFlag{Name: "cron", Usage: "Time Trigger cron spec ('0 30 * * *', '@every 5m', '@hourly')"}
|
||||
ttFnNameFlag := cli.StringFlag{Name: "function", Usage: "Function name"}
|
||||
ttFnUidFlag := cli.StringFlag{Name: "uid", Usage: "Function UID (optional; uses latest if unspecified)"}
|
||||
ttSubcommands := []cli.Command{
|
||||
{Name: "create", Aliases: []string{"add"}, Usage: "Create Time trigger", Flags: []cli.Flag{ttNameFlag, ttFnNameFlag, ttFnUidFlag, ttCronFlag}, Action: ttCreate},
|
||||
{Name: "get", Usage: "Get Time trigger", Flags: []cli.Flag{}, Action: ttGet},
|
||||
{Name: "update", Usage: "Update Time trigger", Flags: []cli.Flag{ttNameFlag, ttCronFlag}, Action: ttUpdate},
|
||||
{Name: "delete", Usage: "Delete Time trigger", Flags: []cli.Flag{ttNameFlag}, Action: ttDelete},
|
||||
{Name: "list", Usage: "List Time triggers", Flags: []cli.Flag{}, Action: ttList},
|
||||
}
|
||||
|
||||
// environments
|
||||
envNameFlag := cli.StringFlag{Name: "name", Usage: "Environment name"}
|
||||
envImageFlag := cli.StringFlag{Name: "image", Usage: "Environment image URL"}
|
||||
@@ -91,6 +111,7 @@ func main() {
|
||||
app.Commands = []cli.Command{
|
||||
{Name: "function", Aliases: []string{"fn"}, Usage: "Create, update and manage functions", Subcommands: fnSubcommands},
|
||||
{Name: "httptrigger", Aliases: []string{"ht", "route"}, Usage: "Manage HTTP triggers (routes) for functions", Subcommands: htSubcommands},
|
||||
{Name: "timetrigger", Aliases: []string{"tt", "timer"}, Usage: "Manage Time triggers (timers) for functions", Subcommands: ttSubcommands},
|
||||
{Name: "environment", Aliases: []string{"env"}, Usage: "Manage environments", Subcommands: envSubcommands},
|
||||
{Name: "watch", Aliases: []string{"w"}, Usage: "Manage watches", Subcommands: wSubCommands},
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/satori/go.uuid"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/fission/fission"
|
||||
)
|
||||
|
||||
func ttCreate(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
|
||||
name := c.String("name")
|
||||
if len(name) == 0 {
|
||||
name = uuid.NewV4().String()
|
||||
}
|
||||
fnName := c.String("function")
|
||||
if len(fnName) == 0 {
|
||||
fatal("Need a function name to create a trigger, use --function")
|
||||
}
|
||||
fnUid := c.String("uid")
|
||||
cron := c.String("cron")
|
||||
if len(cron) == 0 {
|
||||
fatal("Need a cron spec like '0 30 * * *', '@every 1h30m', '@hourly', use --cron")
|
||||
}
|
||||
|
||||
tt := &fission.TimeTrigger{
|
||||
Metadata: fission.Metadata{
|
||||
Name: name,
|
||||
},
|
||||
Cron: cron,
|
||||
Function: fission.Metadata{
|
||||
Name: fnName,
|
||||
Uid: fnUid,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := client.TimeTriggerCreate(tt)
|
||||
checkErr(err, "create Time trigger")
|
||||
|
||||
fmt.Printf("trigger '%v' created\n", name)
|
||||
return err
|
||||
}
|
||||
|
||||
func ttGet(c *cli.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ttUpdate(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
ttName := c.String("name")
|
||||
if len(ttName) == 0 {
|
||||
fatal("Need name of trigger, use --name")
|
||||
}
|
||||
|
||||
tt, err := client.TimeTriggerGet(&fission.Metadata{Name: ttName})
|
||||
checkErr(err, "get Time trigger")
|
||||
|
||||
newCron := c.String("cron")
|
||||
if len(newCron) != 0 {
|
||||
tt.Cron = newCron
|
||||
}
|
||||
|
||||
_, err = client.TimeTriggerUpdate(tt)
|
||||
checkErr(err, "update Time trigger")
|
||||
|
||||
fmt.Printf("trigger '%v' updated\n", ttName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ttDelete(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
ttName := c.String("name")
|
||||
if len(ttName) == 0 {
|
||||
fatal("Need name of trigger to delete, use --name")
|
||||
}
|
||||
|
||||
err := client.TimeTriggerDelete(&fission.Metadata{Name: ttName})
|
||||
checkErr(err, "delete trigger")
|
||||
|
||||
fmt.Printf("trigger '%v' deleted\n", ttName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ttList(c *cli.Context) error {
|
||||
client := getClient(c.GlobalString("server"))
|
||||
|
||||
tts, err := client.TimeTriggerList()
|
||||
checkErr(err, "list Time triggers")
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\t%v\n",
|
||||
"NAME", "CRON", "FUNCTION_NAME", "FUNCTION_UID")
|
||||
for _, tt := range tts {
|
||||
fmt.Fprintf(w, "%v\t%v\t%v\t%v\n",
|
||||
tt.Metadata.Name, tt.Cron, tt.Function.Name, tt.Function.Uid)
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
return nil
|
||||
}
|
||||
Generated
+15
-14
@@ -1,5 +1,5 @@
|
||||
hash: fb2f38693c5f2fade3db9434c266743d0f9ea9b5f878b44e79763e248136dcf2
|
||||
updated: 2016-11-14T01:06:12.764919869-08:00
|
||||
hash: 51b5eda27974aac228f42002cb0a9f89bf2f79d36a574a3694c142c2f83e4042
|
||||
updated: 2017-03-24T11:01:00.667996869+08:00
|
||||
imports:
|
||||
- name: github.com/blang/semver
|
||||
version: 60ec3488bfea7cca02b021d106d9911120d25fe9
|
||||
@@ -69,9 +69,15 @@ imports:
|
||||
- name: github.com/gorilla/context
|
||||
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
|
||||
- name: github.com/gorilla/handlers
|
||||
version: ee54c7b44cab12289237fb8631314790076e728b
|
||||
version: 3a5767ca75ece5f7f1440b1d16975247f8d8b221
|
||||
- name: github.com/gorilla/mux
|
||||
version: 0eeaf8392f5b04950925b8a69fe70f110fa7cbfc
|
||||
version: 392c28fe23e1c45ddba891b0320b3b5df220beea
|
||||
- name: github.com/influxdata/influxdb
|
||||
version: b7bb7e8359642b6e071735b50ae41f5eb343fd42
|
||||
subpackages:
|
||||
- client/v2
|
||||
- models
|
||||
- pkg/escape
|
||||
- name: github.com/jonboulle/clockwork
|
||||
version: 2eee05ed794112d45db504eb05aa693efd2b8b09
|
||||
- name: github.com/juju/ratelimit
|
||||
@@ -88,10 +94,12 @@ imports:
|
||||
version: 8a290539e2e8629dbc4e6bad948158f790ec31f4
|
||||
- name: github.com/PuerkitoBio/urlesc
|
||||
version: 5bd2802263f21d8788851d5305584c82a5c75d7e
|
||||
- name: github.com/robfig/cron
|
||||
version: df38d32658d8788cd446ba74db4bb5375c4b0cb3
|
||||
- name: github.com/satori/go.uuid
|
||||
version: 879c5887cd475cd7864858769793b2ceb0d44feb
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: d26492970760ca5d33129d2d799e34be5c4782eb
|
||||
- name: github.com/sirupsen/logrus
|
||||
version: ba1b36c82c5e05c4f912a88eab0dcd91a171688f
|
||||
- name: github.com/spf13/pflag
|
||||
version: 08b1a584251b5b62f458943640fc8ebd4d50aaa5
|
||||
- name: github.com/ugorji/go
|
||||
@@ -99,7 +107,7 @@ imports:
|
||||
subpackages:
|
||||
- codec
|
||||
- name: github.com/urfave/cli
|
||||
version: a14d7d367bc02b1f57d88de97926727f2d936387
|
||||
version: 0bdeddeeb0f650497d603c4ad7b20cfe685682f6
|
||||
- name: golang.org/x/net
|
||||
version: 6acef71eb69611914f7a30939ea9f6e194c78172
|
||||
subpackages:
|
||||
@@ -158,13 +166,6 @@ imports:
|
||||
- name: k8s.io/client-go
|
||||
version: 843f7c4f28b1f647f664f883697107d5c02c5acc
|
||||
subpackages:
|
||||
- 1.4/kubernetes
|
||||
- 1.4/pkg/api
|
||||
- 1.4/pkg/api/v1
|
||||
- 1.4/pkg/apis/extensions/v1beta1
|
||||
- 1.4/pkg/labels
|
||||
- 1.4/pkg/util/intstr
|
||||
- 1.4/rest
|
||||
- 1.5/discovery
|
||||
- 1.5/kubernetes
|
||||
- 1.5/kubernetes/typed/apps/v1alpha1
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user