Compare commits

...

60 Commits

Author SHA1 Message Date
Minghe
871bb29dbe Bundler (#473)
* refactor bundler
* enable unit test
2020-03-10 22:49:14 +08:00
Minghe Huang
3be144f644 clean up no need files 2020-03-10 10:18:07 +08:00
Minghe
2560dc23fc fix the wrong destination of copy issue (#466)
* fix the wrong destination of copy issue
* bump up version
2020-03-01 15:58:45 +08:00
dependabot-preview[bot]
3d6c3d10bf Bump github.com/golang/mock from 1.4.0 to 1.4.1 (#464)
Bumps [github.com/golang/mock](https://github.com/golang/mock) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/golang/mock/releases)
- [Changelog](https://github.com/golang/mock/blob/master/.goreleaser.yml)
- [Commits](https://github.com/golang/mock/compare/v1.4.0...v1.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-28 09:27:02 +08:00
Minghe
5f811693f1 bump up version 2020-02-21 17:28:07 +08:00
Minghe
0068fb92eb support 'format' option in 'fx list' (#462) 2020-02-21 17:26:39 +08:00
dependabot-preview[bot]
71174ead45 Bump github.com/stretchr/testify from 1.4.0 to 1.5.1 (#459)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.4.0 to 1.5.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.4.0...v1.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-21 15:10:47 +08:00
dependabot-preview[bot]
43c18caceb Bump github.com/briandowns/spinner from 1.7.0 to 1.9.0 (#457)
Bumps [github.com/briandowns/spinner](https://github.com/briandowns/spinner) from 1.7.0 to 1.9.0.
- [Release notes](https://github.com/briandowns/spinner/releases)
- [Commits](https://github.com/briandowns/spinner/compare/v1.7.0...v1.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-18 11:03:24 +08:00
dependabot-preview[bot]
7b4c9c3154 Bump github.com/spf13/viper from 1.6.1 to 1.6.2 (#451)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.6.1 to 1.6.2.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.6.1...v1.6.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-11 20:46:02 +08:00
dependabot-preview[bot]
9d2649433d Bump github.com/golang/mock from 1.3.1 to 1.4.0 (#452)
Bumps [github.com/golang/mock](https://github.com/golang/mock) from 1.3.1 to 1.4.0.
- [Release notes](https://github.com/golang/mock/releases)
- [Changelog](https://github.com/golang/mock/blob/master/.goreleaser.yml)
- [Commits](https://github.com/golang/mock/compare/1.3.1...v1.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: Minghe <h.minghe@gmail.com>
2020-02-11 20:44:41 +08:00
dependabot-preview[bot]
6353fa7dd3 Bump github.com/apex/log from 1.1.1 to 1.1.2 (#453)
Bumps [github.com/apex/log](https://github.com/apex/log) from 1.1.1 to 1.1.2.
- [Release notes](https://github.com/apex/log/releases)
- [Changelog](https://github.com/apex/log/blob/master/History.md)
- [Commits](https://github.com/apex/log/compare/v1.1.1...v1.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: Minghe <h.minghe@gmail.com>
2020-02-11 20:44:01 +08:00
Minghe
bfa837c88d release 0.9.0 (#456) 2020-02-11 20:29:46 +08:00
Minghe
bdc454e7e5 support before_build hook (#455) 2020-02-11 20:26:46 +08:00
dependabot-preview[bot]
9b3e85754c Bump github.com/pkg/errors from 0.9.0 to 0.9.1 (#450)
Bumps [github.com/pkg/errors](https://github.com/pkg/errors) from 0.9.0 to 0.9.1.
- [Release notes](https://github.com/pkg/errors/releases)
- [Commits](https://github.com/pkg/errors/compare/v0.9.0...v0.9.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-15 10:09:44 +08:00
dependabot-preview[bot]
af3dcc5f31 Bump github.com/pkg/errors from 0.8.1 to 0.9.0 (#449)
Bumps [github.com/pkg/errors](https://github.com/pkg/errors) from 0.8.1 to 0.9.0.
- [Release notes](https://github.com/pkg/errors/releases)
- [Commits](https://github.com/pkg/errors/compare/v0.8.1...v0.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-14 08:51:29 +08:00
Minghe
c375fb9eaf check if source file or directory is ready or not (#447) 2020-01-07 15:36:23 +08:00
Minghe
70c314229f add JSON to perl base image (#446) 2020-01-03 09:53:59 +08:00
Minghe
66e23ead00 update docs (#445) 2020-01-02 23:42:43 +08:00
Minghe Huang
2e5666c2b6 Squashed commit of the following:
commit 5a9c2b5942d16b3ff7b4ed7a02dafbf2c5462eae
Author: Minghe Huang <h.minghe@gmail.com>
Date:   Thu Jan 2 17:40:23 2020 +0800

    bump version
2020-01-02 17:40:46 +08:00
Minghe
7675656a54 fix force option when deploy a function on Docker infra (#443)
* fix force option when deploy a function on Docker infra

* fix test
2020-01-02 17:13:10 +08:00
Minghe
3d7f7b0ad1 Configurable auto remove (#440)
* enable autoremove to be configurable

* bump version
2019-12-31 14:29:01 +08:00
Minghe
a1ccbd6cab Add not fetch fx node bas (#439)
* add node-fetch to fx-node-base image, then we can use 'fetch' in
JavaScript/Node function

* bump version
2019-12-31 13:34:48 +08:00
Minghe
33cb4ce63c verify deploy function to Google Kubernetes Engine and update Doc (#435)
* verify deploy function to Google Kubernetes Engine and update Doc
* bump version
2019-12-27 23:08:51 +08:00
Minghe
aefb4497e2 When check a file is handler or not, should take extension account, (#433)
otherewise it might be a directory, not a file
2019-12-26 20:40:57 +08:00
Minghe Huang
0047e66f10 bump version 2019-12-26 20:38:50 +08:00
Minghe Huang
6bae4254af When check a file is handler or not, should take extension account,
otherewise it might be a directory, not a file
2019-12-26 18:47:11 +08:00
Minghe
a9689993b0 Fix packr missing auto release (#431)
* install packr

* install packr

* commit generate packr file

* no need change
2019-12-24 18:30:44 +08:00
Minghe
8c0182b29f move some installation into base image (#427) 2019-12-24 11:13:10 +08:00
Minghe Huang
02d55c7143 bump version 2019-12-23 13:57:31 +08:00
Minghe
f343b537f1 upgrade go-ssh-client pkg (#426)
* upgrade go-ssh-client pkg

* bump version
2019-12-20 10:24:14 +08:00
Minghe
a84e7da65f When there is services deployed via k8s mode on Desktop Docker for Mac (#424)
with kubernetes single node cluster, "fx list" will crash since some
service has no names, but the code trying to access names at index 0

Signed-off-by: Minghe Huang <h.minghe@gmail.com>
2019-12-19 16:05:40 +08:00
Minghe
f3b64387cb dependencies should be field of brew (#423) 2019-12-19 14:28:53 +08:00
Minghe
e132435ff8 add docker to dependency (#422)
* add docker to dependency

Signed-off-by: Minghe Huang <h.minghe@gmail.com>

* bump version
2019-12-19 13:41:26 +08:00
Minghe
fb492fa6f7 Merge pull request #415 from metrue/perl
Perl
2019-12-19 10:14:22 +08:00
Minghe Huang
159714491d add perl support,
fx up func.pl

Signed-off-by: Minghe Huang <h.minghe@gmail.com>
2019-12-18 21:06:56 +08:00
Minghe Huang
64cbbc70bb Squashed commit of the following:
commit f4e3d78e516f889a0256c6ddf922922ec12bc757
Author: Minghe Huang <h.minghe@gmail.com>
Date:   Wed Dec 18 21:02:43 2019 +0800

    check error when walk directory

    Signed-off-by: Minghe Huang <h.minghe@gmail.com>
2019-12-18 21:03:05 +08:00
Minghe
d0559f627e simple and clean code (#420)
Signed-off-by: Minghe Huang <h.minghe@gmail.com>
2019-12-18 20:56:26 +08:00
Minghe
0a6784e270 fix wrong cloud type issue (#419)
Signed-off-by: Minghe Huang <h.minghe@gmail.com>
2019-12-18 20:22:03 +08:00
Minghe
b6fd3c7e98 add contributor badge (#418) 2019-12-18 17:29:16 +08:00
Minghe
1c05534071 fix KUBECONFIG no effect issue (#416)
Signed-off-by: Minghe Huang <h.minghe@gmail.com>
2019-12-18 16:59:10 +08:00
Minghe
3627d5bb40 fix ci script (#414) 2019-12-18 11:20:32 +08:00
Minghe
1f7714c1e9 Refactor kubeconfig persist logic (#412)
* add Dir function to config

* clean up

* persist the kubeconf on the fly

Signed-off-by: Minghe Huang <h.minghe@gmail.com>

* fix test

Signed-off-by: Minghe Huang <h.minghe@gmail.com>

* simplicity the cloud config fetch

Signed-off-by: Minghe Huang <h.minghe@gmail.com>
2019-12-18 10:58:34 +08:00
Minghe
d868ebf4a1 enable CI check (#410)
* enable CI check

Signed-off-by: Minghe Huang <h.minghe@gmail.com>

* lint

* clean up lint issue

* fix Makefile

* uncomment

* fix coverage

* check infrastructure during up command (#411)

* check infrastructure during up command

Signed-off-by: Minghe Huang <h.minghe@gmail.com>

* add unit test

Signed-off-by: Minghe Huang <h.minghe@gmail.com>
2019-12-17 22:54:54 +08:00
Minghe
4640379b06 clean up no use deps (#408) 2019-12-16 18:02:45 +08:00
dependabot-preview[bot]
922120efbb Bump github.com/spf13/viper from 1.3.2 to 1.6.1 (#406)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.3.2 to 1.6.1.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.3.2...v1.6.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-13 09:24:03 +08:00
Minghe
91fec99b00 remove debugging information (#405)
Signed-off-by: Minghe Huang <h.minghe@gmail.com>
2019-12-12 22:57:01 +08:00
Minghe
2f89c1fe1f Refactor provision layer (#403) 2019-12-12 19:24:49 +08:00
Minghe
2298f39cca k3s on docker (#401)
* since we do the image build in initialize container of pod, so we have to make sure the image built can be access from kubelet on same node, containerd could not support that
* update docs
* bump version
* clean up
2019-12-07 01:42:13 +08:00
Minghe
23d68bc27b Refactor packer (#399) 2019-12-06 20:16:05 +08:00
Minghe
74c0423f0d add docs for rust example (#398) 2019-12-06 14:58:30 +08:00
Minghe
06f87c4d8e fix deploy to aks issue, and update docs (#396)
* fix deploy to aks issue, and update docs
* update docs
2019-12-06 11:54:27 +08:00
Minghe
35262de828 release v0.8.7 (#394) 2019-12-06 10:04:40 +08:00
dependabot-preview[bot]
34a495984c Bump github.com/olekukonko/tablewriter from 0.0.3 to 0.0.4 (#393)
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 0.0.3 to 0.0.4.
- [Release notes](https://github.com/olekukonko/tablewriter/releases)
- [Commits](https://github.com/olekukonko/tablewriter/compare/v0.0.3...v0.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-06 09:45:27 +08:00
Minghe
d7130c4e28 support a project with multiple files (#392)
* support a project with multiple files
* fix lint issue
* fix test
2019-12-05 17:57:39 +08:00
Minghe
c9630a53c3 should treat CICD code block seperately (#391)
* should treat CICD code block seperately

* auto provision when host is not health

* fix lint
2019-12-05 11:38:54 +08:00
Minghe
0522690472 merge provisioner and deployer into infra (#390) 2019-12-04 19:09:14 +08:00
Minghe
a8a0fbed32 add ping method to check infra is healthy or not (#388)
* add ping method to check infra is healthy or not
* merge k3s and k8s deployer (#389)
* fix typo
2019-12-04 17:28:03 +08:00
Minghe
26ae9585f6 move cli args parsing into middleware level (#387) 2019-12-04 11:56:07 +08:00
Minghe
b69bd699c8 combine coverage and unit test progres (#386) 2019-12-04 09:45:42 +08:00
Minghe
650ee5f63a move progress bar into deploy function (#385) 2019-12-04 08:43:49 +08:00
185 changed files with 6053 additions and 2105 deletions

View File

@@ -26,14 +26,10 @@ jobs:
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: |
export KUBECONFIG="$(kind get kubeconfig-path)"
DEBUG=true go test -v ./...
- name: code cov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: |
./scripts/coverage.sh
export KUBECONFIG="$(kind get kubeconfig-path)"
make unit-test
bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}
- name: build fx

View File

@@ -35,10 +35,10 @@ jobs:
docker build -t metrue/fx-d-base:latest -f ./assets/dockerfiles/base/d/Dockerfile ./assets/dockerfiles/base/d
docker push metrue/fx-d-base:latest
# - name: build and publish fx java image
# run: |
# docker build -t metrue/fx-go-base:latest -f ./assets/dockerfiles/base/java/Dockerfile ./assets/dockerfiles/base/java
# docker push metrue/fx-java-base:latest
- name: build and publish fx go image
run: |
docker build -t metrue/fx-go-base:latest -f ./assets/dockerfiles/base/go/Dockerfile ./assets/dockerfiles/base/go
docker push metrue/fx-go-base:latest
- name: build and publish fx node image
if: always()
@@ -57,11 +57,11 @@ jobs:
run: |
docker push metrue/fx-python-base:latest
# - name: build and publish fx rust image
# if: always()
# run: |
# docker build -t metrue/fx-rust-base:latest -f ./assets/dockerfiles/base/rust/Dockerfile ./assets/dockerfiles/base/python
# docker push metrue/fx-rust-base:latest
- name: build and publish fx perl image
if: always()
run: |
docker build -t metrue/fx-perl-base:latest -f ./assets/dockerfiles/base/perl/Dockerfile ./assets/dockerfiles/base/perl
docker push metrue/fx-perl-base:latest
- name: build and publish fx julia image
if: always()

View File

@@ -33,7 +33,7 @@ jobs:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: |
export KUBECONFIG="$(kind get kubeconfig-path)"
DEBUG=true go test -v ./container_runtimes/... ./deploy/...
make unit-test
- name: build fx
run: |

View File

@@ -1,6 +1,6 @@
run:
deadline: 10m
timeout: 10m
deadline: 20m
timeout: 20m
issues-exit-code: 1
tests: true
skip-dirs:

View File

@@ -32,3 +32,5 @@ brews:
caveats: ""
homepage: "https://github.com/metrue/fx"
description: "fx, a simple but powerful Function as a Service build tools"
dependencies:
- docker

View File

@@ -10,10 +10,10 @@ generate:
packr
b:
go build -o ${OUTPUT_DIR}/fx fx.go
go build -ldflags="-s -w" -o ${OUTPUT_DIR}/fx fx.go
build:
go build -o ${OUTPUT_DIR}/fx fx.go
go build -ldflags="-s -w" -o ${OUTPUT_DIR}/fx fx.go
pull:
./scripts/pull.sh
@@ -26,13 +26,13 @@ clean:
rm -rf ${DIST_DIR}
unit-test:
./scripts/coverage.sh
CI=true ./scripts/coverage.sh
cli-test-ci:
./scripts/test_cli.sh 'js'
cli-test:
./scripts/test_cli.sh 'js rb py go php java d'
./scripts/test_cli.sh 'js rb py go php java d rs pl'
http-test:
./scripts/http_test.sh

113
README.md
View File

@@ -1,8 +1,10 @@
fx
------
Poor man's function as a service.
<br/>
![CI](https://github.com/metrue/fx/workflows/ci/badge.svg)
![GitHub contributors](https://img.shields.io/github/contributors/metrue/fx)
[![CodeCov](https://codecov.io/gh/metrue/fx/branch/master/graph/badge.svg)](https://codecov.io/gh/metrue/fx)
[![Go Report Card](https://goreportcard.com/badge/github.com/metrue/fx?style=flat-square)](https://goreportcard.com/report/github.com/metrue/fx)
[![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/metrue/fx)
@@ -13,13 +15,12 @@ Poor man's function as a service.
- [Introduction](#introduction)
- [Installation](#installation)
- [Usage](#usage)
- [Manage Infrastructure](#manage-infrastructure)
- [Contribute](#contribute)
## Introduction
![workflow](https://raw.githubusercontent.com/metrue/fx/master/docs/fx-workflow.png)
fx is a tool to help you do Function as a Service on your own server, fx can make your stateless function a service in seconds, both Docker host and Kubernetes cluster supported. The most exciting thing is that you can write your functions with most programming languages.
Feel free hacking fx to support the languages not listed. Welcome to tweet me [@_metrue](https://twitter.com/_metrue) on Twitter, [@metrue](https://www.weibo.com/u/2165714507) on Weibo.
@@ -36,6 +37,7 @@ Feel free hacking fx to support the languages not listed. Welcome to tweet me [@
| PHP | Supported | [@chlins](https://github.com/chlins)| [/examples/PHP](https://github.com/metrue/fx/tree/master/examples/functions/PHP) |
| Julia | Supported | [@matbesancon](https://github.com/matbesancon)| [/examples/Julia](https://github.com/metrue/fx/tree/master/examples/functions/Julia) |
| D | Supported | [@andre2007](https://github.com/andre2007)| [/examples/D](https://github.com/metrue/fx/tree/master/examples/functions/D) |
| Perl | Supported | fx | [/examples/Perl](https://github.com/metrue/fx/tree/master/examples/functions/Perl) |
| R | Working on [need your help](https://github.com/metrue/fx/issues/31) | ||
# Installation
@@ -79,7 +81,7 @@ USAGE:
fx [global options] command [command options] [arguments...]
VERSION:
0.8.4
0.8.7
COMMANDS:
infra manage infrastructure
@@ -96,44 +98,36 @@ GLOBAL OPTIONS:
--version, -v print the version
```
1. Write a function
### Deploy your function to Docker
You can check out [examples](https://github.com/metrue/fx/tree/master/examples/functions) for reference. Let's write a function as an example, it calculates the sum of two numbers then returns:
```js
module.exports = (ctx) => {
ctx.body = 'hello world'
}
```
Then save it to a file `func.js`.
$ fx up --name hello-fx ./examples/functions/JavaScript/func.js
2. Deploy your function as a service
Give your service a port with `--port`, and name with `--name`, heath checking with `--healthcheck` if you want.
```shell
$ fx up -name fx_service_name -p 10001 --healthcheck func.js
2019/08/10 13:26:37 info Pack Service: ✓
2019/08/10 13:26:39 info Build Service: ✓
2019/08/10 13:26:39 info Run Service: ✓
2019/08/10 13:26:39 info Service (fx_service_name) is running on: 0.0.0.0:10001
2019/08/10 13:26:39 info up function fx_service_name(func.js) to machine localhost: ✓
+------------------------------------------------------------------+-----------+---------------+
| ID | NAME | ENDPOINT |
+------------------------------------------------------------------+-----------+---------------+
| 5b24d36608ee392c937a61a530805f74551ddec304aea3aca2ffa0fabcf98cf3 | /hello-fx | 0.0.0.0:58328 |
+------------------------------------------------------------------+-----------+---------------+
```
if you want see what the source code of your service looks like, you can export it into a dirctory,
### Deploy your function to Kubernetes
```shell
$ fx image export -o <path of dir> func.js
2019/09/25 19:31:19 info exported to <path of dir>: ✓
```
$ KUBECONFIG=~/.kube/config ./build/fx up examples/functions/JavaScript/func.js --name hello-fx
+-------------------------------+------+----------------+
| ID | NAME | ENDPOINT |
+----+--------------------------+-----------------------+
| 5b24d36608ee392c937a | hello-fx | 10.0.242.75:80 |
+------------------------+-------------+----------------+
```
3. Test your service
### Test your service
then you can test your service:
```shell
$ curl -v 0.0.0.0:10001
$ curl -v 0.0.0.0:58328
GET / HTTP/1.1
@@ -155,39 +149,32 @@ hello world
```
## Docker
## Manage Infrastructure
**fx** is originally designed to turn a function into a runnable Docker container in a easiest way, on a host with Docker running, you can just deploy your function with `fx up` command,
**fx** is originally designed to turn a function into a runnable Docker container in a easiest way, on a host with Docker running, you can just deploy your function with `fx up` command, and now **fx** supports deploy function to be a service onto Kubernetes cluster infrasture, and we encourage you to do that other than on bare Docker environment, there are lots of advantage to run your function on Kubernetes like self-healing, load balancing, easy horizontal scaling, etc. It's pretty simple to deploy your function onto Kubernetes with **fx**, you just set KUBECONFIG in your enviroment.
By default. **fx** use localhost as target infrastructure to run your service, and you can also setup your remote virtual machines as **fx**'s infrastructure and deploy your functions onto it.
### `fx infra create`
You can create types (docker and k8s) of infrastructures for **fx** to deploy functions
```shell
fx up --name hello-svc --port 7777 hello.js # onto localhost
DOCKER_REMOTE_HOST_ADDR=xx.xx.xx.xx DOCKER_REMOTE_HOST_USER=xxxx DOCKER_REMOTE_HOST_PASSWORD=xxxx fx up --name hello-svc --port 7777 hello.js # onto remote host
$ fx infra create --name infra_us --type docker --host <user>@<ip> ## create docker type infrasture on <ip>
$ fx infra create --name infra_bj --type k8s --master <user>@<ip> --agents '<user1>@<ip1>,<user2>@<ip2>' ## create k8s type infrasture use <ip> as master node, and <ip1> and <ip2> as agents nodes
```
## Kubernetes
### `fx infra use`
**fx** supports deploy function to be a service onto Kubernetes cluster infrasture, and we encourage you to do that other than on bare Docker environment, there are lots of advantage to run your function on Kubernetes like self-healing, load balancing, easy horizontal scaling, etc. It's pretty simple to deploy your function onto Kubernetes with **fx**, you just set KUBECONFIG in your enviroment.
To use a infrastructure, you can use `fx infra use` command to activate it.
```shell
KUBECONFIG=<Your KUBECONFIG> fx deploy -n fx-service-abc_js -p 12349 examples/functions/JavaScript/func.js # function will be deploy to your Kubernetes cluster and expose a IP address of your loadbalencer
fx infra use <infrastructure name>
```
or
and you can list your infrastructure with `fx infra list`
```shell
$ export KUBECONFIG=<Your KUBECONFIG>
$ fx deploy -n fx-service-abc_js -p 12349 examples/functions/JavaScript/func.js # function will be deploy to your Kubernetes cluster and expose a IP address of your loadbalencer
```
* Local Kubernetes Cluster
Docker for Mac and Docker for Windows already support Kubernetes with single node cluster, we can use it directly, and the default `KUBECONFIG` is `~/.kube/config`.
```shell
$ export KUBECONFIG=~/.kube/config # then fx will take the config to deloy function
```
if you have multiple Kubernetes clusters configured, you have to set context correctly. FYI [configure-access-multiple-clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/)
## Use Public Cloud Kubernetes Service as infrastructure to run your functions
* Azure Kubernetes Service (AKS)
@@ -219,13 +206,29 @@ But we would suggest you run `kubectl config current-context` to check if the cu
* Amazon Elastic Kubernetes Service (EKS)
TODO
* Google Kubernetes Engine (GKET)
TODO
* Google Kubernetes Engine (GKE)
First you should create a Kubernetes cluster in your GKE, then make sure your KUBECONFIG is ready in `~/.kube/config`, if not, you can run following commands,
``` shell
$ gcloud auth login
$ gcloud container clusters get-credentials <your cluster> --zone <zone> --project <project>
```
Then make sure you current context is GKE cluster, you can check it with command,
``` shell
$ kubectl config current-context
```
Then you can deploy your function onto GKE cluster with,
```shell
$ KUBECONFIG=~/.kube/config fx up examples/functions/JavaScript/func.js --name hellojs
```
* Setup your own Kubernetes cluster
![init workflow](https://raw.githubusercontent.com/metrue/fx/master/docs/fx-init-cluster.png)
```shell
fx infra create --type k3s --name fx-cluster-1 --master root@123.11.2.3 --agents 'root@1.1.1.1,root@2.2.2.2'
```

4
assets/dockerfiles/base/go/Dockerfile vendored Normal file
View File

@@ -0,0 +1,4 @@
FROM golang:latest
# dependency management
RUN go get github.com/gin-gonic/gin

26
assets/dockerfiles/base/node/package-lock.json generated vendored Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "fx-node-base",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@koa/cors": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@koa/cors/-/cors-2.2.3.tgz",
"integrity": "sha512-tCVVXa39ETsit5kGBtEWWimjLn1sDaeu8+0phgb8kT3GmBDZOykkI3ZO8nMjV2p3MGkJI4K5P+bxR8Ztq0bwsA==",
"requires": {
"vary": "^1.1.2"
}
},
"node-fetch": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
}
}
}

View File

@@ -1,5 +1,5 @@
{
"name": "aok",
"name": "fx-node-base",
"version": "1.0.0",
"description": "",
"main": "index.js",
@@ -10,12 +10,11 @@
"author": "",
"license": "ISC",
"dependencies": {
"@koa/cors": "^2.2.3",
"get-port": "^3.2.0",
"is-generator-function": "^1.0.6",
"koa": "^2.3.0",
"koa-bodyparser": "^4.2.0"
},
"devDependencies": {
"get-port-cli": "^1.1.0"
"koa-bodyparser": "^4.2.0",
"node-fetch": "^2.6.0"
}
}

10
assets/dockerfiles/base/perl/Dockerfile vendored Normal file
View File

@@ -0,0 +1,10 @@
FROM alpine:3.4
MAINTAINER Mojolicious
ADD . .
COPY cpanfile /
ENV EV_EXTRA_DEFS -DEV_NO_ATFORK
RUN apk update && \
apk add perl perl-io-socket-ssl perl-dbd-pg perl-dev g++ make wget curl && \
curl -L https://cpanmin.us | perl - App::cpanminus && cpanm --installdeps . -M https://cpan.metacpan.org

3
assets/dockerfiles/base/perl/cpanfile vendored Normal file
View File

@@ -0,0 +1,3 @@
requires "EV";
requires "JSON";
requires "Mojolicious::Lite";

View File

@@ -0,0 +1,3 @@
FROM ruby:latest
RUN gem install sinatra

44
bundle/bundle.go Normal file
View File

@@ -0,0 +1,44 @@
package bundle
import (
"fmt"
"github.com/metrue/fx/bundler"
"github.com/metrue/fx/bundler/d"
golang "github.com/metrue/fx/bundler/go"
"github.com/metrue/fx/bundler/java"
"github.com/metrue/fx/bundler/julia"
"github.com/metrue/fx/bundler/node"
"github.com/metrue/fx/bundler/perl"
"github.com/metrue/fx/bundler/python"
"github.com/metrue/fx/bundler/ruby"
"github.com/metrue/fx/bundler/rust"
)
// Bundle function to project
func Bundle(workdir string, language string, fn string, deps ...string) error {
var bundler bundler.Bundler
switch language {
case "d":
bundler = d.New()
case "node":
bundler = node.New()
case "go":
bundler = golang.New()
case "java":
bundler = java.New()
case "julia":
bundler = julia.New()
case "perl":
bundler = perl.New()
case "python":
bundler = python.New()
case "ruby":
bundler = ruby.New()
case "rust":
bundler = rust.New()
default:
return fmt.Errorf("%s not suppported yet", language)
}
return bundler.Bundle(workdir, fn, deps...)
}

117
bundle/bundler_test.go Normal file
View File

@@ -0,0 +1,117 @@
package bundle
import (
"io/ioutil"
"os"
"testing"
)
func createFn(content string, t *testing.T) string {
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(fd.Name(), []byte(content), 0666)
if err != nil {
t.Fatal(err)
}
return fd.Name()
}
func TestBundle(t *testing.T) {
workdir, err := ioutil.TempDir("", "fx-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(workdir)
cases := []struct {
workdir string
language string
fn string
deps []string
}{
{
workdir: workdir,
language: "d",
fn: `
module.exports = (ctx) => {
ctx.body = 'hello fx'
}
`,
},
{
workdir: workdir,
language: "go",
fn: `
module.exports = (ctx) => {
ctx.body = 'hello fx'
}
`,
},
{
workdir: workdir,
language: "java",
fn: `
module.exports = (ctx) => {
ctx.body = 'hello fx'
}
`,
},
{
workdir: workdir,
language: "julia",
fn: `
module.exports = (ctx) => {
ctx.body = 'hello fx'
}
`,
},
{
workdir: workdir,
language: "perl",
fn: `
module.exports = (ctx) => {
ctx.body = 'hello fx'
}
`,
},
{
workdir: workdir,
language: "python",
fn: `
module.exports = (ctx) => {
ctx.body = 'hello fx'
}
`,
},
{
workdir: workdir,
language: "ruby",
fn: `
module.exports = (ctx) => {
ctx.body = 'hello fx'
}
`,
},
{
workdir: workdir,
language: "rust",
fn: `
module.exports = (ctx) => {
ctx.body = 'hello fx'
}
`,
},
}
for _, c := range cases {
fn := createFn(c.fn, t)
defer os.Remove(fn)
if err := Bundle(c.workdir, c.language, fn, c.deps...); err != nil {
t.Fatal(err)
}
}
}

100
bundler/bundler.go Normal file
View File

@@ -0,0 +1,100 @@
package bundler
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/gobuffalo/packd"
"github.com/gobuffalo/packr/v2"
"github.com/metrue/fx/utils"
)
// Bundler defines interface
type Bundler interface {
Scaffold(output string) error
Bundle(output string, fn string, deps ...string) error
}
// IsHandler check if it's handle file
func IsHandler(name string, lang string) bool {
extLangMapping := map[string]string{
".js": "node",
".go": "go",
".rb": "ruby",
".py": "python",
".php": "php",
".jl": "julia",
".java": "java",
".d": "d",
".rs": "rust",
".pl": "perl",
}
basename := filepath.Base(name)
nameWithoutExt := strings.TrimSuffix(basename, filepath.Ext(basename))
if extLangMapping[filepath.Ext(basename)] != lang {
return false
}
return (nameWithoutExt == "fx" ||
// Fx is for Java
nameWithoutExt == "Fx" ||
// mod.rs is for Rust)
nameWithoutExt == "mod")
}
// Restore directory from packr box
func Restore(box *packr.Box, output string) error {
if err := box.Walk(func(name string, fd packd.File) error {
content, err := box.Find(name)
if err != nil {
return err
}
dest := filepath.Join(output, name)
if err := utils.EnsureFile(dest); err != nil {
return err
}
if err := ioutil.WriteFile(dest, content, 0644); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}
// Bundle bundle a function
func Bundle(box *packr.Box, output string, language string, fn string, deps ...string) error {
if err := Restore(box, output); err != nil {
return err
}
if err := utils.Merge(output, deps...); err != nil {
return err
}
// Replace the default handler source file with given function source file
if err := filepath.Walk(output, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if IsHandler(path, language) {
if err := utils.CopyFile(fn, path); err != nil {
return err
}
}
return nil
}); err != nil {
return err
}
return nil
}

51
bundler/bundler_test.go Normal file
View File

@@ -0,0 +1,51 @@
package bundler
import (
"io/ioutil"
"log"
"os"
"testing"
"github.com/gobuffalo/packr/v2"
)
func TestBundler(t *testing.T) {
t.Run("Restore", func(t *testing.T) {
box := packr.New("", "./node/assets")
outputDir, err := ioutil.TempDir("", "fx_koa")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(outputDir)
if err := Restore(box, outputDir); err != nil {
t.Fatal(err)
}
})
t.Run("Bundle", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content := `
module.exports = (ctx) => {
ctx.body = 'hello fx'
}`
if err = ioutil.WriteFile(fd.Name(), []byte(content), 0666); err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_koa")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
box := packr.New("", "./node/assets")
if err := Bundle(box, outputDir, "node", fd.Name()); err != nil {
t.Fatal(err)
}
})
}

32
bundler/d/d.go Normal file
View File

@@ -0,0 +1,32 @@
package d
import (
"github.com/gobuffalo/packr/v2"
"github.com/metrue/fx/bundler"
)
// D defines d bundler
type D struct {
assets *packr.Box
}
// New a koa bundler
func New() *D {
return &D{
assets: packr.New("", "./assets"),
}
}
// Scaffold a koa app
func (d *D) Scaffold(output string) error {
return bundler.Restore(d.assets, output)
}
// Bundle a function into a koa project
func (d *D) Bundle(output string, fn string, deps ...string) error {
return bundler.Bundle(d.assets, output, "d", fn, deps...)
}
var (
_ bundler.Bundler = &D{}
)

View File

@@ -1,11 +1,8 @@
FROM golang:latest
FROM metrue/fx-go-base
COPY . /go/src/github.com/metrue/fx
WORKDIR /go/src/github.com/metrue/fx
# dependency management
RUN go get github.com/gin-gonic/gin
RUN go build -ldflags "-w -s" -o fx fx.go app.go
EXPOSE 3000

32
bundler/go/go.go Normal file
View File

@@ -0,0 +1,32 @@
package golang
import (
"github.com/gobuffalo/packr/v2"
"github.com/metrue/fx/bundler"
)
// Gin defines javascript bundler
type Gin struct {
assets *packr.Box
}
// New a koa bundler
func New() *Gin {
return &Gin{
assets: packr.New("", "./assets"),
}
}
// Scaffold a koa app
func (g *Gin) Scaffold(output string) error {
return bundler.Restore(g.assets, output)
}
// Bundle a function into a koa project
func (g *Gin) Bundle(output string, fn string, deps ...string) error {
return bundler.Bundle(g.assets, output, "go", fn, deps...)
}
var (
_ bundler.Bundler = &Gin{}
)

143
bundler/go/go_test.go Normal file
View File

@@ -0,0 +1,143 @@
package golang
import (
"io/ioutil"
"log"
"os"
"reflect"
"testing"
"github.com/metrue/fx/utils"
)
func TestKoaBundler(t *testing.T) {
t.Run("Scaffold", func(t *testing.T) {
outputDir, err := ioutil.TempDir("", "fx_koa")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
koa := New()
if err := koa.Scaffold(outputDir); err != nil {
t.Fatal(err)
}
diff, _, _, err := utils.Diff(outputDir, "./assets")
if err != nil {
t.Fatal(err)
}
if diff {
t.Fatalf("%s is not equal with %s", outputDir, "./assets")
}
})
t.Run("BundleSingleFunc", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content := `
module.exports = (ctx) => {
ctx.body = 'hello fx'
}`
err = ioutil.WriteFile(fd.Name(), []byte(content), 0666)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_koa")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
koa := New()
if err := koa.Bundle(outputDir, fd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle functino should be changed")
}
if !reflect.DeepEqual(cur, []byte(content)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
preHandleFunc, err := ioutil.ReadFile("./assets/fx.go")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(pre, preHandleFunc) {
{
}
t.Fatalf("it should get %s but got %s", preHandleFunc, pre)
}
})
t.Run("BundleFuncAndDeps", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content, err := ioutil.ReadFile("./assets/fx.go")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(fd.Name(), content, 0666)
if err != nil {
t.Fatal(err)
}
addFunc := `
module.exports = (a, b) => a+b
`
addFd, err := ioutil.TempFile("", "fx_add_func_*.js")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(addFd.Name(), []byte(addFunc), 0644)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_koa")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
koa := New()
if err := koa.Bundle(outputDir, fd.Name(), addFd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle functino should be changed")
}
if !reflect.DeepEqual(cur, []byte(addFunc)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
if pre != nil {
t.Fatal(pre)
}
})
}

32
bundler/java/java.go Normal file
View File

@@ -0,0 +1,32 @@
package java
import (
"github.com/gobuffalo/packr/v2"
"github.com/metrue/fx/bundler"
)
// Java defines javascript bundler
type Java struct {
assets *packr.Box
}
// New a koa bundler
func New() *Java {
return &Java{
assets: packr.New("", "./assets"),
}
}
// Scaffold a koa app
func (k *Java) Scaffold(output string) error {
return bundler.Restore(k.assets, output)
}
// Bundle a function into a koa project
func (k *Java) Bundle(output string, fn string, deps ...string) error {
return bundler.Bundle(k.assets, output, "java", fn, deps...)
}
var (
_ bundler.Bundler = &Java{}
)

155
bundler/java/java_test.go Normal file
View File

@@ -0,0 +1,155 @@
package java
import (
"io/ioutil"
"log"
"os"
"reflect"
"testing"
"github.com/metrue/fx/utils"
)
func TestJavaBundler(t *testing.T) {
t.Run("Scaffold", func(t *testing.T) {
outputDir, err := ioutil.TempDir("", "fx_java")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
java := New()
if err := java.Scaffold(outputDir); err != nil {
t.Fatal(err)
}
diff, _, _, err := utils.Diff(outputDir, "./assets")
if err != nil {
t.Fatal(err)
}
if diff {
t.Fatalf("%s is not equal with %s", outputDir, "./assets")
}
})
t.Run("BundleSingleFunc", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.java")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content := `package fx;
import io.javalin.Javalin;
import org.json.JSONObject;
public class app {
public static void main(String[] args) {
Javalin app = Javalin.start(3000);
Fx handler = new Fx();
app.post("/", ctx -> {
JSONObject obj = new JSONObject(ctx.body());
ctx.result(""+handler.handle(obj));
});
}
}
`
err = ioutil.WriteFile(fd.Name(), []byte(content), 0666)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_java")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
java := New()
if err := java.Bundle(outputDir, fd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle functino should be changed")
}
if !reflect.DeepEqual(cur, []byte(content)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
preHandleFunc, err := ioutil.ReadFile("./assets/src/main/java/fx/Fx.java")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(pre, preHandleFunc) {
{
}
t.Fatalf("it should get %s but got %s", preHandleFunc, pre)
}
})
t.Run("BundleFuncAndDeps", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content, err := ioutil.ReadFile("./assets/src/main/java/fx/Fx.java")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(fd.Name(), content, 0666)
if err != nil {
t.Fatal(err)
}
addFunc := `
module.exports = (a, b) => a+b
`
addFd, err := ioutil.TempFile("", "fx_add_func_*.js")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(addFd.Name(), []byte(addFunc), 0644)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_java")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
java := New()
if err := java.Bundle(outputDir, fd.Name(), addFd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle functino should be changed")
}
if !reflect.DeepEqual(cur, []byte(addFunc)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
if pre != nil {
t.Fatal(pre)
}
})
}

View File

@@ -1,4 +1,3 @@
struct Input
a::Number
b::Number

32
bundler/julia/julia.go Normal file
View File

@@ -0,0 +1,32 @@
package julia
import (
"github.com/gobuffalo/packr/v2"
"github.com/metrue/fx/bundler"
)
// Julia defines javascript bundler
type Julia struct {
assets *packr.Box
}
// New a koa bundler
func New() *Julia {
return &Julia{
assets: packr.New("", "./assets"),
}
}
// Scaffold a koa app
func (k *Julia) Scaffold(output string) error {
return bundler.Restore(k.assets, output)
}
// Bundle a function into a koa project
func (k *Julia) Bundle(output string, fn string, deps ...string) error {
return bundler.Bundle(k.assets, output, "julia", fn, deps...)
}
var (
_ bundler.Bundler = &Julia{}
)

148
bundler/julia/julia_test.go Normal file
View File

@@ -0,0 +1,148 @@
package julia
import (
"io/ioutil"
"log"
"os"
"reflect"
"testing"
"github.com/metrue/fx/utils"
)
func TestJuliaBundler(t *testing.T) {
t.Run("Scaffold", func(t *testing.T) {
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Scaffold(outputDir); err != nil {
t.Fatal(err)
}
diff, _, _, err := utils.Diff(outputDir, "./assets")
if err != nil {
t.Fatal(err)
}
if diff {
t.Fatalf("%s is not equal with %s", outputDir, "./assets")
}
})
t.Run("BundleSingleFunc", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.julia")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content := `struct Input
a::Number
b::Number
end
fx = function(input::Input)
return input.a + input.b
end
`
err = ioutil.WriteFile(fd.Name(), []byte(content), 0666)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Bundle(outputDir, fd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle function should be changed")
}
if !reflect.DeepEqual(cur, []byte(content)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
preHandleFunc, err := ioutil.ReadFile("./assets/fx.jl")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(pre, preHandleFunc) {
{
}
t.Fatalf("it should get %s but got %s", preHandleFunc, pre)
}
})
t.Run("BundleFuncAndDeps", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content, err := ioutil.ReadFile("./assets/fx.jl")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(fd.Name(), content, 0666)
if err != nil {
t.Fatal(err)
}
addFunc := `
module.exports = (a, b) => a+b
`
addFd, err := ioutil.TempFile("", "fx_add_func_*.js")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(addFd.Name(), []byte(addFunc), 0644)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Bundle(outputDir, fd.Name(), addFd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle functino should be changed")
}
if !reflect.DeepEqual(cur, []byte(addFunc)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
if pre != nil {
t.Fatal(pre)
}
})
}

View File

@@ -0,0 +1,13 @@
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const cors = require('@koa/cors');
const fx = require('./fx');
const app = new Koa();
app.use(cors({
origin: '*',
}));
app.use(bodyParser());
app.use(fx);
app.listen(3000);

34
bundler/node/node.go Normal file
View File

@@ -0,0 +1,34 @@
package node
import (
"github.com/gobuffalo/packr/v2"
"github.com/metrue/fx/bundler"
)
const language = "node"
// Node defines node bundler
type Node struct {
assets *packr.Box
}
// New a koa bundler
func New() *Node {
return &Node{
assets: packr.New("", "./assets"),
}
}
// Scaffold a koa app
func (k *Node) Scaffold(output string) error {
return bundler.Restore(k.assets, output)
}
// Bundle a function into a koa project
func (k *Node) Bundle(output string, fn string, deps ...string) error {
return bundler.Bundle(k.assets, output, language, fn, deps...)
}
var (
_ bundler.Bundler = &Node{}
)

143
bundler/node/node_test.go Normal file
View File

@@ -0,0 +1,143 @@
package node
import (
"io/ioutil"
"log"
"os"
"reflect"
"testing"
"github.com/metrue/fx/utils"
)
func TestNodeBundler(t *testing.T) {
t.Run("Scaffold", func(t *testing.T) {
outputDir, err := ioutil.TempDir("", "fx_koa")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
koa := New()
if err := koa.Scaffold(outputDir); err != nil {
t.Fatal(err)
}
diff, _, _, err := utils.Diff(outputDir, "./assets")
if err != nil {
t.Fatal(err)
}
if diff {
t.Fatalf("%s is not equal with %s", outputDir, "./assets")
}
})
t.Run("BundleSingleFunc", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content := `
module.exports = (ctx) => {
ctx.body = 'hello fx'
}`
err = ioutil.WriteFile(fd.Name(), []byte(content), 0666)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_koa")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
koa := New()
if err := koa.Bundle(outputDir, fd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle functino should be changed")
}
if !reflect.DeepEqual(cur, []byte(content)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
preHandleFunc, err := ioutil.ReadFile("./assets/fx.js")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(pre, preHandleFunc) {
{
}
t.Fatalf("it should get %s but got %s", preHandleFunc, pre)
}
})
t.Run("BundleFuncAndDeps", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content, err := ioutil.ReadFile("./assets/fx.js")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(fd.Name(), content, 0666)
if err != nil {
t.Fatal(err)
}
addFunc := `
module.exports = (a, b) => a+b
`
addFd, err := ioutil.TempFile("", "fx_add_func_*.js")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(addFd.Name(), []byte(addFunc), 0644)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_koa")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
koa := New()
if err := koa.Bundle(outputDir, fd.Name(), addFd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle functino should be changed")
}
if !reflect.DeepEqual(cur, []byte(addFunc)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
if pre != nil {
t.Fatal(pre)
}
})
}

View File

@@ -0,0 +1,6 @@
FROM metrue/fx-perl-base
ADD . .
EXPOSE 3000
CMD ["perl", "app.pl", "daemon"]

View File

@@ -0,0 +1,17 @@
use Mojolicious::Lite;
require "./fx.pl";
get '/' => sub {
my $ctx = shift;
my $res = fx($ctx);
$ctx->render(json => $res);
};
post '/' => sub {
my $ctx = shift;
my $res = fx($ctx);
$ctx->render(json => $res);
};
app->start;

View File

@@ -0,0 +1,6 @@
sub fx {
my $ctx = shift;
return 'hello fx'
}
1;

32
bundler/perl/perl.go Normal file
View File

@@ -0,0 +1,32 @@
package perl
import (
"github.com/gobuffalo/packr/v2"
"github.com/metrue/fx/bundler"
)
// Julia defines javascript bundler
type Julia struct {
assets *packr.Box
}
// New a koa bundler
func New() *Julia {
return &Julia{
assets: packr.New("", "./assets"),
}
}
// Scaffold a koa app
func (k *Julia) Scaffold(output string) error {
return bundler.Restore(k.assets, output)
}
// Bundle a function into a koa project
func (k *Julia) Bundle(output string, fn string, deps ...string) error {
return bundler.Bundle(k.assets, output, "perl", fn, deps...)
}
var (
_ bundler.Bundler = &Julia{}
)

148
bundler/perl/perl_test.go Normal file
View File

@@ -0,0 +1,148 @@
package perl
import (
"io/ioutil"
"log"
"os"
"reflect"
"testing"
"github.com/metrue/fx/utils"
)
func TestJuliaBundler(t *testing.T) {
t.Run("Scaffold", func(t *testing.T) {
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Scaffold(outputDir); err != nil {
t.Fatal(err)
}
diff, _, _, err := utils.Diff(outputDir, "./assets")
if err != nil {
t.Fatal(err)
}
if diff {
t.Fatalf("%s is not equal with %s", outputDir, "./assets")
}
})
t.Run("BundleSingleFunc", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.julia")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content := `struct Input
a::Number
b::Number
end
fx = function(input::Input)
return input.a + input.b
end
`
err = ioutil.WriteFile(fd.Name(), []byte(content), 0666)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Bundle(outputDir, fd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle function should be changed")
}
if !reflect.DeepEqual(cur, []byte(content)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
preHandleFunc, err := ioutil.ReadFile("./assets/fx.pl")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(pre, preHandleFunc) {
{
}
t.Fatalf("it should get %s but got %s", preHandleFunc, pre)
}
})
t.Run("BundleFuncAndDeps", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content, err := ioutil.ReadFile("./assets/fx.pl")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(fd.Name(), content, 0666)
if err != nil {
t.Fatal(err)
}
addFunc := `
module.exports = (a, b) => a+b
`
addFd, err := ioutil.TempFile("", "fx_add_func_*.js")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(addFd.Name(), []byte(addFunc), 0644)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Bundle(outputDir, fd.Name(), addFd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle functino should be changed")
}
if !reflect.DeepEqual(cur, []byte(addFunc)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
if pre != nil {
t.Fatal(pre)
}
})
}

32
bundler/python/python.go Normal file
View File

@@ -0,0 +1,32 @@
package python
import (
"github.com/gobuffalo/packr/v2"
"github.com/metrue/fx/bundler"
)
// Julia defines javascript bundler
type Julia struct {
assets *packr.Box
}
// New a koa bundler
func New() *Julia {
return &Julia{
assets: packr.New("", "./assets"),
}
}
// Scaffold a koa app
func (k *Julia) Scaffold(output string) error {
return bundler.Restore(k.assets, output)
}
// Bundle a function into a koa project
func (k *Julia) Bundle(output string, fn string, deps ...string) error {
return bundler.Bundle(k.assets, output, "python", fn, deps...)
}
var (
_ bundler.Bundler = &Julia{}
)

View File

@@ -0,0 +1,148 @@
package python
import (
"io/ioutil"
"log"
"os"
"reflect"
"testing"
"github.com/metrue/fx/utils"
)
func TestJuliaBundler(t *testing.T) {
t.Run("Scaffold", func(t *testing.T) {
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Scaffold(outputDir); err != nil {
t.Fatal(err)
}
diff, _, _, err := utils.Diff(outputDir, "./assets")
if err != nil {
t.Fatal(err)
}
if diff {
t.Fatalf("%s is not equal with %s", outputDir, "./assets")
}
})
t.Run("BundleSingleFunc", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.julia")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content := `struct Input
a::Number
b::Number
end
fx = function(input::Input)
return input.a + input.b
end
`
err = ioutil.WriteFile(fd.Name(), []byte(content), 0666)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Bundle(outputDir, fd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle function should be changed")
}
if !reflect.DeepEqual(cur, []byte(content)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
preHandleFunc, err := ioutil.ReadFile("./assets/fx.py")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(pre, preHandleFunc) {
{
}
t.Fatalf("it should get %s but got %s", preHandleFunc, pre)
}
})
t.Run("BundleFuncAndDeps", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content, err := ioutil.ReadFile("./assets/fx.py")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(fd.Name(), content, 0666)
if err != nil {
t.Fatal(err)
}
addFunc := `
module.exports = (a, b) => a+b
`
addFd, err := ioutil.TempFile("", "fx_add_func_*.js")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(addFd.Name(), []byte(addFunc), 0644)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Bundle(outputDir, fd.Name(), addFd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle functino should be changed")
}
if !reflect.DeepEqual(cur, []byte(addFunc)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
if pre != nil {
t.Fatal(pre)
}
})
}

View File

@@ -1,6 +1,4 @@
FROM ruby:latest
RUN gem install sinatra
FROM metrue/fx-ruby-base
COPY . .
EXPOSE 3000

32
bundler/ruby/ruby.go Normal file
View File

@@ -0,0 +1,32 @@
package ruby
import (
"github.com/gobuffalo/packr/v2"
"github.com/metrue/fx/bundler"
)
// Julia defines javascript bundler
type Julia struct {
assets *packr.Box
}
// New a koa bundler
func New() *Julia {
return &Julia{
assets: packr.New("", "./assets"),
}
}
// Scaffold a koa app
func (k *Julia) Scaffold(output string) error {
return bundler.Restore(k.assets, output)
}
// Bundle a function into a koa project
func (k *Julia) Bundle(output string, fn string, deps ...string) error {
return bundler.Bundle(k.assets, output, "ruby", fn, deps...)
}
var (
_ bundler.Bundler = &Julia{}
)

148
bundler/ruby/ruby_test.go Normal file
View File

@@ -0,0 +1,148 @@
package ruby
import (
"io/ioutil"
"log"
"os"
"reflect"
"testing"
"github.com/metrue/fx/utils"
)
func TestJuliaBundler(t *testing.T) {
t.Run("Scaffold", func(t *testing.T) {
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Scaffold(outputDir); err != nil {
t.Fatal(err)
}
diff, _, _, err := utils.Diff(outputDir, "./assets")
if err != nil {
t.Fatal(err)
}
if diff {
t.Fatalf("%s is not equal with %s", outputDir, "./assets")
}
})
t.Run("BundleSingleFunc", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.julia")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content := `struct Input
a::Number
b::Number
end
fx = function(input::Input)
return input.a + input.b
end
`
err = ioutil.WriteFile(fd.Name(), []byte(content), 0666)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Bundle(outputDir, fd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle function should be changed")
}
if !reflect.DeepEqual(cur, []byte(content)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
preHandleFunc, err := ioutil.ReadFile("./assets/fx.rb")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(pre, preHandleFunc) {
{
}
t.Fatalf("it should get %s but got %s", preHandleFunc, pre)
}
})
t.Run("BundleFuncAndDeps", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content, err := ioutil.ReadFile("./assets/fx.rb")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(fd.Name(), content, 0666)
if err != nil {
t.Fatal(err)
}
addFunc := `
module.exports = (a, b) => a+b
`
addFd, err := ioutil.TempFile("", "fx_add_func_*.js")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(addFd.Name(), []byte(addFunc), 0644)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Bundle(outputDir, fd.Name(), addFd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle functino should be changed")
}
if !reflect.DeepEqual(cur, []byte(addFunc)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
if pre != nil {
t.Fatal(pre)
}
})
}

32
bundler/rust/rust.go Normal file
View File

@@ -0,0 +1,32 @@
package rust
import (
"github.com/gobuffalo/packr/v2"
"github.com/metrue/fx/bundler"
)
// Julia defines javascript bundler
type Julia struct {
assets *packr.Box
}
// New a koa bundler
func New() *Julia {
return &Julia{
assets: packr.New("", "./assets"),
}
}
// Scaffold a koa app
func (k *Julia) Scaffold(output string) error {
return bundler.Restore(k.assets, output)
}
// Bundle a function into a koa project
func (k *Julia) Bundle(output string, fn string, deps ...string) error {
return bundler.Bundle(k.assets, output, "rust", fn, deps...)
}
var (
_ bundler.Bundler = &Julia{}
)

148
bundler/rust/rust_test.go Normal file
View File

@@ -0,0 +1,148 @@
package rust
import (
"io/ioutil"
"log"
"os"
"reflect"
"testing"
"github.com/metrue/fx/utils"
)
func TestJuliaBundler(t *testing.T) {
t.Run("Scaffold", func(t *testing.T) {
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Scaffold(outputDir); err != nil {
t.Fatal(err)
}
diff, _, _, err := utils.Diff(outputDir, "./assets")
if err != nil {
t.Fatal(err)
}
if diff {
t.Fatalf("%s is not equal with %s", outputDir, "./assets")
}
})
t.Run("BundleSingleFunc", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.julia")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content := `struct Input
a::Number
b::Number
end
fx = function(input::Input)
return input.a + input.b
end
`
err = ioutil.WriteFile(fd.Name(), []byte(content), 0666)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Bundle(outputDir, fd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle function should be changed")
}
if !reflect.DeepEqual(cur, []byte(content)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
preHandleFunc, err := ioutil.ReadFile("./assets/src/fns/mod.rs")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(pre, preHandleFunc) {
{
}
t.Fatalf("it should get %s but got %s", preHandleFunc, pre)
}
})
t.Run("BundleFuncAndDeps", func(t *testing.T) {
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
t.Fatal(err)
}
defer os.Remove(fd.Name())
content, err := ioutil.ReadFile("./assets/src/fns/mod.rs")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(fd.Name(), content, 0666)
if err != nil {
t.Fatal(err)
}
addFunc := `
module.exports = (a, b) => a+b
`
addFd, err := ioutil.TempFile("", "fx_add_func_*.js")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(addFd.Name(), []byte(addFunc), 0644)
if err != nil {
t.Fatal(err)
}
outputDir, err := ioutil.TempDir("", "fx_julia")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(outputDir)
julia := New()
if err := julia.Bundle(outputDir, fd.Name(), addFd.Name()); err != nil {
t.Fatal(err)
}
diff, pre, cur, err := utils.Diff("./assets", outputDir)
if err != nil {
t.Fatal(err)
}
if !diff {
t.Fatalf("handle functino should be changed")
}
if !reflect.DeepEqual(cur, []byte(addFunc)) {
t.Fatalf("it should be %s but got %s", content, cur)
}
if pre != nil {
t.Fatal(pre)
}
})
}

View File

@@ -7,45 +7,67 @@ import (
"os"
"os/user"
"path"
"sync"
"path/filepath"
dockerInfra "github.com/metrue/fx/infra/docker"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
"github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v2"
)
// Items data of config file
type Items struct {
Clouds map[string]map[string]string `json:"clouds"`
CurrentCloud string `json:"current_cloud"`
// Configer manage fx config
type Configer interface {
GetCurrentCloud() ([]byte, error)
GetCurrentCloudType() (string, error)
GetKubeConfig() (string, error)
UseCloud(name string) error
View() ([]byte, error)
AddCloud(name string, meta []byte) error
Dir() (string, error)
}
// Config config of fx
type Config struct {
mux sync.Mutex
configFile string
Items
container *Container
}
const defaultFxConfig = "~/.fx/config.yml"
// LoadDefault load default config
func LoadDefault() (*Config, error) {
configFile, err := homedir.Expand("~/.fx/config.yml")
configFile, err := homedir.Expand(defaultFxConfig)
if err != nil {
return nil, err
}
if os.Getenv("FX_CONFIG") != "" {
configFile = os.Getenv("FX_CONFIG")
}
if _, err := os.Stat(configFile); os.IsNotExist(err) {
if err := utils.EnsureFile(configFile); err != nil {
return nil, err
}
if err := writeDefaultConfig(configFile); err != nil {
}
return load(configFile)
}
func load(configFile string) (*Config, error) {
container, err := CreateContainer(configFile)
if err != nil {
return nil, err
}
config := &Config{
configFile: configFile,
container: container,
}
if container.get("clouds") == nil {
if err := config.writeDefaultConfig(); err != nil {
return nil, err
}
}
return load(configFile)
return config, nil
}
// Load config
@@ -58,138 +80,128 @@ func Load(configFile string) (*Config, error) {
if err := utils.EnsureFile(configFile); err != nil {
return nil, err
}
if err := writeDefaultConfig(configFile); err != nil {
return nil, err
}
}
return load(configFile)
}
// AddCloud add a cloud
func (c *Config) addCloud(name string, cloud map[string]string) error {
c.Items.Clouds[name] = cloud
return save(c)
}
// AddDockerCloud add docker cloud
func (c *Config) AddDockerCloud(name string, config []byte) error {
c.mux.Lock()
defer c.mux.Unlock()
var conf map[string]string
err := json.Unmarshal(config, &conf)
if err != nil {
// AddCloud add k8s cloud
func (c *Config) AddCloud(name string, meta []byte) error {
var cloudMeta map[string]interface{}
if err := json.Unmarshal(meta, &cloudMeta); err != nil {
return err
}
cloud := map[string]string{
"type": "docker",
"host": conf["ip"],
"user": conf["user"],
}
return c.addCloud(name, cloud)
}
// AddK8SCloud add k8s cloud
func (c *Config) AddK8SCloud(name string, kubeconfig []byte) error {
c.mux.Lock()
defer c.mux.Unlock()
dir := path.Dir(c.configFile)
kubecfg := path.Join(dir, name+".kubeconfig")
if err := utils.EnsureFile(kubecfg); err != nil {
return err
}
if err := ioutil.WriteFile(kubecfg, kubeconfig, 0666); err != nil {
return err
cloudType, ok := cloudMeta["type"].(string)
if !ok || cloudType == "" {
return fmt.Errorf("unknown cloud type")
}
cloud := map[string]string{
"type": "k8s",
"kubeConfig": kubecfg,
}
return c.addCloud(name, cloud)
}
// Use set cloud instance with name as current context
func (c *Config) Use(name string) error {
c.mux.Lock()
defer c.mux.Unlock()
has := false
for n := range c.Clouds {
if n == name {
has = true
break
if cloudType == types.CloudTypeK8S {
dir := path.Dir(c.configFile)
kubecfg := path.Join(dir, name+".kubeconfig")
if err := utils.EnsureFile(kubecfg); err != nil {
return err
}
config, ok := cloudMeta["config"].(string)
if !ok {
return fmt.Errorf("invalid k8s config")
}
if err := ioutil.WriteFile(kubecfg, []byte(config), 0666); err != nil {
return err
}
}
if !has {
return fmt.Errorf("no cloud with name = %s", name)
if err := c.container.set("clouds."+name, cloudMeta); err != nil {
return err
}
c.Items.CurrentCloud = name
return save(c)
return nil
}
// UseCloud set cloud instance with name as current context
func (c *Config) UseCloud(name string) error {
if name == "" {
return fmt.Errorf("could not use empty name")
}
if c.container.get("clouds."+name) == nil {
return fmt.Errorf("no such cloud with name: %s", name)
}
return c.container.set("current_cloud", name)
}
// View view current config
func (c *Config) View() ([]byte, error) {
c.mux.Lock()
defer c.mux.Unlock()
return ioutil.ReadFile(c.configFile)
}
func load(configFile string) (*Config, error) {
conf, err := ioutil.ReadFile(configFile)
if err != nil {
return nil, err
// GetCurrentCloud get current using cloud's meta
func (c *Config) GetCurrentCloud() ([]byte, error) {
name, ok := c.container.get("current_cloud").(string)
if !ok {
return nil, fmt.Errorf("no active cloud")
}
var items Items
if err := yaml.Unmarshal(conf, &items); err != nil {
return nil, err
meta := c.container.get("clouds." + name)
if meta == nil {
return nil, fmt.Errorf("invalid config")
}
var c = Config{
configFile: configFile,
Items: items,
}
return &c, nil
return json.Marshal(meta)
}
func save(c *Config) error {
conf, err := yaml.Marshal(c.Items)
if err != nil {
return err
// GetCurrentCloudType get current cloud type
func (c *Config) GetCurrentCloudType() (string, error) {
name, ok := c.container.get("current_cloud").(string)
if !ok {
return "", fmt.Errorf("no active cloud")
}
if err := ioutil.WriteFile(c.configFile, conf, 0666); err != nil {
return err
}
return nil
return c.container.get("clouds." + name + ".type").(string), nil
}
func writeDefaultConfig(configFile string) error {
// GetKubeConfig get kubeconfig
func (c *Config) GetKubeConfig() (string, error) {
name, ok := c.container.get("current_cloud").(string)
if !ok {
return "", fmt.Errorf("no active cloud")
}
dir := path.Dir(c.configFile)
kubecfg := path.Join(dir, name+".kubeconfig")
return kubecfg, nil
}
func (c *Config) writeDefaultConfig() error {
me, err := user.Current()
if err != nil {
return err
}
items := Items{
Clouds: map[string]map[string]string{
"default": map[string]string{
"type": "docker",
"host": "127.0.0.1",
"user": me.Username,
},
},
CurrentCloud: "default",
}
body, err := yaml.Marshal(items)
defaultCloud := &dockerInfra.Cloud{
IP: "127.0.0.1",
User: me.Username,
Name: "default",
Type: types.CloudTypeDocker,
}
meta, err := defaultCloud.Dump()
if err != nil {
return err
}
if err := ioutil.WriteFile(configFile, body, 0666); err != nil {
if err := c.container.set("clouds", map[string]interface{}{}); err != nil {
return err
}
return nil
if err := c.AddCloud("default", meta); err != nil {
return err
}
return c.UseCloud("default")
}
// Dir get directory of config
func (c *Config) Dir() (string, error) {
p, err := filepath.Abs(c.configFile)
if err != nil {
return "", err
}
return path.Dir(p), nil
}
var (
_ Configer = &Config{}
)

View File

@@ -4,57 +4,111 @@ import (
"encoding/json"
"fmt"
"os"
"os/user"
"path/filepath"
"reflect"
"testing"
k8sInfra "github.com/metrue/fx/infra/k8s"
"github.com/metrue/fx/types"
)
func TestConfig(t *testing.T) {
configPath := "./tmp/config.yml"
defer func() {
if err := os.RemoveAll("./tmp"); err != nil {
if err := os.RemoveAll("./tmp/config.yml"); err != nil {
t.Fatal(err)
}
}()
// default cloud
c, err := Load(configPath)
if err != nil {
t.Fatal(err)
}
if len(c.Clouds) != 1 {
t.Fatal("should contain default cloud")
}
name := "fx_cluster_1"
if err := c.Use(name); err == nil {
t.Fatal("should get no such cloud error")
}
if err := c.AddK8SCloud(name, []byte("sampe kubeconfg")); err != nil {
t.Fatal(err)
}
config := map[string]string{
"ip": "127.0.0.1",
"user": "use1",
}
configData, _ := json.Marshal(config)
if err := c.AddDockerCloud("docker-1", configData); err != nil {
t.Fatal(err)
}
if err := c.Use(name); err != nil {
t.Fatal(err)
}
if c.CurrentCloud != name {
t.Fatalf("should get %s but got %s", name, c.CurrentCloud)
}
conf, err := Load(configPath)
defaultMeta, err := c.GetCurrentCloud()
if err != nil {
t.Fatal(err)
}
if conf.CurrentCloud != name {
t.Fatalf("should get %s but got %s", name, c.CurrentCloud)
var cloudMeta map[string]string
if err := json.Unmarshal(defaultMeta, &cloudMeta); err != nil {
t.Fatal(err)
}
if cloudMeta["ip"] != "127.0.0.1" {
t.Fatalf("should get %s but got %s", "127.0.0.1", cloudMeta["ip"])
}
me, _ := user.Current()
if cloudMeta["user"] != me.Username {
t.Fatalf("should get %s but got %s", me.Username, cloudMeta["user"])
}
if cloudMeta["type"] != types.CloudTypeDocker {
t.Fatalf("should get %s but got %s", types.CloudTypeDocker, cloudMeta["type"])
}
if cloudMeta["name"] != "default" {
t.Fatalf("should get %s but got %s", "default", cloudMeta["name"])
}
n1, err := k8sInfra.CreateNode(
"1.1.1.1",
"user-1",
"k3s-master",
"master-node",
)
if err != nil {
t.Fatal(err)
}
n2, err := k8sInfra.CreateNode(
"1.1.1.1",
"user-1",
"k3s-agent",
"agent-node-1",
)
if err != nil {
t.Fatal(err)
}
kName := "k8s-1"
kubeconf := "./tmp/" + kName + "config.yml"
defer func() {
if err := os.RemoveAll(kubeconf); err != nil {
t.Fatal(err)
}
}()
// add k8s cloud
kCloud := k8sInfra.NewCloud(kubeconf, n1, n2)
kMeta, err := kCloud.Dump()
if err != nil {
t.Fatal(err)
}
if err := c.AddCloud(kName, kMeta); err != nil {
t.Fatal(err)
}
curMeta, err := c.GetCurrentCloud()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(curMeta, defaultMeta) {
t.Fatalf("should get %v but got %v", defaultMeta, curMeta)
}
if err := c.UseCloud("cloud-not-existed"); err == nil {
t.Fatalf("should get error when there is not given cloud name")
}
if err := c.UseCloud(kName); err != nil {
t.Fatal(err)
}
curMeta, err = c.GetCurrentCloud()
if err != nil {
t.Fatal(err)
}
if reflect.DeepEqual(curMeta, kMeta) {
t.Fatalf("should get %v but got %v", kMeta, curMeta)
}
body, err := c.View()
@@ -62,4 +116,16 @@ func TestConfig(t *testing.T) {
t.Fatal(err)
}
fmt.Println(string(body))
dir, err := c.Dir()
if err != nil {
t.Fatal(err)
}
here, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
if dir != filepath.Join(here, "./tmp") {
t.Fatalf("should get %s but got %s", "./tmp", dir)
}
}

73
config/container.go Normal file
View File

@@ -0,0 +1,73 @@
package config
import (
"fmt"
"path/filepath"
"strings"
"sync"
"github.com/metrue/fx/utils"
"github.com/spf13/viper"
)
// Container config container, wrap viper as a key-value store with lock
type Container struct {
mux sync.Mutex
store string
}
// CreateContainer new a container
func CreateContainer(storeFile string) (*Container, error) {
if err := utils.EnsureFile(storeFile); err != nil {
return nil, err
}
dir := filepath.Dir(storeFile)
ext := filepath.Ext(storeFile)
name := filepath.Base(storeFile)
viper.AddConfigPath(dir)
viper.SetConfigName(strings.Replace(name, ext, "", 1))
viper.SetConfigType(strings.Replace(ext, ".", "", 1))
if err := viper.ReadInConfig(); err != nil {
return nil, err
}
return &Container{
store: storeFile,
}, nil
}
func (c *Container) set(key string, value interface{}) error {
c.mux.Lock()
defer c.mux.Unlock()
if key == "" {
return fmt.Errorf("empty key not allowed")
}
keys := strings.Split(key, ".")
if len(keys) == 1 {
viper.Set(key, value)
} else {
prePath := keys[0]
for i := 1; i < len(keys)-2; i++ {
prePath += "." + keys[i]
}
if viper.Get(prePath) == nil {
return fmt.Errorf("%s not existed", prePath)
}
viper.Set(key, value)
}
// viper.Set(key, value)
if err := viper.WriteConfig(); err != nil {
return err
}
return nil
}
func (c *Container) get(key string) interface{} {
c.mux.Lock()
defer c.mux.Unlock()
return viper.Get(key)
}

84
config/container_test.go Normal file
View File

@@ -0,0 +1,84 @@
package config
import (
"os"
"testing"
)
func TestContainer(t *testing.T) {
configPath := "./tmp/container.yml"
defer func() {
if err := os.RemoveAll("./tmp/container.yml"); err != nil {
t.Fatal(err)
}
}()
c, err := CreateContainer(configPath)
if err != nil {
t.Fatal(err)
}
if err := c.set("", ""); err == nil {
t.Fatalf("should get error when key is empty")
}
if c.get("1") != nil {
t.Fatalf("should get %v but got %v", nil, c.get("key"))
}
// create
if err := c.set("1", "1"); err != nil {
t.Fatal(err)
}
// read
if c.get("1").(string) != "1" {
t.Fatalf("should get %s but got %s", "val-1", c.get("key"))
}
// invaliad set
if err := c.set("1.1", "1.1"); err != nil {
t.Fatal(err)
}
if c.get("1.1").(string) != "1.1" {
t.Fatalf("should get 1.1 but got %s", c.get("1.1"))
}
// update
if err := c.set("1", "11"); err != nil {
t.Fatal(err)
}
if c.get("1").(string) != "11" {
t.Fatalf("should get 11 but got %s", c.get("1").(string))
}
// nested set
if err := c.set("2.2.2.2", "2222"); err == nil {
t.Fatalf("should throw error since 2.2.2 not ready yet")
}
if err := c.set("2", map[string]interface{}{
"2": map[string]interface{}{
"2": "2",
},
}); err != nil {
t.Fatal(err)
}
if c.get("2.2.2").(string) != "2" {
t.Fatalf("should get 2 but got %s", c.get("2.2.2"))
}
if err := c.set("2.2.2.2", "2222"); err != nil {
t.Fatal(err)
}
if c.get("2.2.2.2").(string) != "2222" {
t.Fatalf("should get 2222 but got %s", c.get("2.2.2.2"))
}
if err := c.set("2.2.2.1", "1111"); err != nil {
t.Fatal(err)
}
if c.get("2.2.2.1").(string) != "1111" {
t.Fatalf("should get 1111 but got %s", c.get("2.2.2.1"))
}
}

14
config/env.go Normal file
View File

@@ -0,0 +1,14 @@
package config
import (
"os"
)
// DisableContainerAutoremove to tell if to run container with --rm
var DisableContainerAutoremove = false
func init() {
if os.Getenv("DISABLE_CONTAINER_AUTOREMOVE") == "true" {
DisableContainerAutoremove = true
}
}

17
config/env_test.go Normal file
View File

@@ -0,0 +1,17 @@
package config
import (
"os"
"testing"
)
var _ = func() (_ struct{}) {
os.Setenv("DISABLE_CONTAINER_AUTOREMOVE", "true")
return
}()
func TestEnvLoad(t *testing.T) {
if !DisableContainerAutoremove {
t.Fatalf("should be true after set")
}
}

View File

@@ -1,7 +0,0 @@
package config
// CloudTypeDocker docker type
const CloudTypeDocker = "docker"
// CloudTypeK8S k8s type
const CloudTypeK8S = "k8s"

View File

@@ -23,6 +23,7 @@ import (
"github.com/docker/go-connections/nat"
"github.com/google/go-querystring/query"
"github.com/google/uuid"
fxConfig "github.com/metrue/fx/config"
containerruntimes "github.com/metrue/fx/container_runtimes"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
@@ -37,27 +38,29 @@ type API struct {
// Create a API
func Create(host string, port string) (*API, error) {
version, err := utils.DockerVersion(host, port)
addr := host + ":" + port
v, err := version(addr)
if err != nil {
return nil, err
}
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, version)
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, v)
return &API{
endpoint: endpoint,
version: version,
version: v,
}, nil
}
// MustCreate a api object, panic if not
func MustCreate(host string, port string) *API {
version, err := utils.DockerVersion(host, port)
addr := host + ":" + port
v, err := version(addr)
if err != nil {
panic(err)
}
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, version)
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, v)
return &API{
endpoint: endpoint,
version: version,
version: v,
}
}
@@ -129,6 +132,45 @@ func (api *API) post(path string, body []byte, expectStatus int, v interface{})
return nil
}
// Version get version of docker engine
func (api *API) Version(ctx context.Context) (string, error) {
return version(api.endpoint)
}
func version(endpoint string) (string, error) {
path := endpoint + "/version"
if !strings.HasPrefix(path, "http") {
path = "http://" + path
}
req, err := http.NewRequest("GET", path, nil)
if err != nil {
return "", err
}
client := &http.Client{Timeout: 20 * time.Second}
resp, err := client.Do(req)
if err != nil {
return "", err
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("request %s failed: %d - %s", path, resp.StatusCode, resp.Status)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
var res dockerTypes.Version
err = json.Unmarshal(body, &res)
if err != nil {
return "", err
}
return res.APIVersion, nil
}
// ListContainer list service
func (api *API) ListContainer(ctx context.Context, name string) ([]types.Service, error) {
if name != "" {
@@ -186,17 +228,27 @@ func (api *API) ListContainer(ctx context.Context, name string) ([]types.Service
svs := make(map[string]types.Service)
for _, container := range containers {
name := "UNKNOWN"
if len(container.Names) > 0 {
name = container.Names[0]
}
port := -1
ip := "UNKNOWN"
if len(container.Ports) > 0 {
ip = container.Ports[0].IP
port = int(container.Ports[0].PublicPort)
}
// container name have extra forward slash
// https://github.com/moby/moby/issues/6705
if strings.HasPrefix(container.Names[0], fmt.Sprintf("/%s", name)) {
svs[container.Image] = types.Service{
Name: container.Names[0],
Image: container.Image,
ID: container.ID,
Host: container.Ports[0].IP,
Port: int(container.Ports[0].PublicPort),
State: container.State,
}
svs[container.Image] = types.Service{
Name: name,
Image: container.Image,
ID: container.ID,
Host: ip,
Port: port,
State: container.State,
}
}
services := []types.Service{}
@@ -351,7 +403,7 @@ func (api *API) StartContainer(ctx context.Context, name string, image string, b
}
hostConfig := &dockerTypesContainer.HostConfig{
AutoRemove: true,
AutoRemove: !fxConfig.DisableContainerAutoremove,
PortBindings: portMap,
}

View File

@@ -18,6 +18,7 @@ import (
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/google/uuid"
fxConfig "github.com/metrue/fx/config"
containerruntimes "github.com/metrue/fx/container_runtimes"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
@@ -161,7 +162,7 @@ func (d *Docker) StartContainer(ctx context.Context, name string, image string,
}
hostConfig := &dockerTypesContainer.HostConfig{
AutoRemove: true,
AutoRemove: !fxConfig.DisableContainerAutoremove,
PortBindings: portMap,
}
resp, err := d.ContainerCreate(ctx, config, hostConfig, nil, name)
@@ -235,6 +236,15 @@ func (d *Docker) ListContainer(ctx context.Context, name string) ([]types.Servic
return services, nil
}
// Version get version of docker engine
func (d *Docker) Version(ctx context.Context) (string, error) {
ping, err := d.Ping(ctx)
if err != nil {
return "", err
}
return ping.APIVersion, nil
}
var (
_ containerruntimes.ContainerRuntime = &Docker{}
)

View File

@@ -16,4 +16,5 @@ type ContainerRuntime interface {
StopContainer(ctx context.Context, name string) error
InspectContainer(ctx context.Context, name string, container interface{}) error
ListContainer(ctx context.Context, filter string) ([]types.Service, error)
Version(ctx context.Context) (string, error)
}

View File

@@ -1,16 +0,0 @@
package deploy
import (
"context"
types "github.com/metrue/fx/types"
)
// Deployer make a image a service
type Deployer interface {
Deploy(ctx context.Context, fn types.Func, name string, image string, bindings []types.PortBinding) error
Destroy(ctx context.Context, name string) error
Update(ctx context.Context, name string) error
GetStatus(ctx context.Context, name string) (types.Service, error)
List(ctx context.Context, name string) ([]types.Service, error)
}

View File

@@ -1,8 +0,0 @@
package k3s
// ConfigMap is the key to function docker project source code in configmap
var ConfigMap = struct {
AppMetaEnvName string
}{
AppMetaEnvName: "APP_META",
}

View File

@@ -1,102 +0,0 @@
package k3s
import (
"fmt"
"github.com/metrue/fx/types"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func generateDeploymentSpec(
name string,
image string,
bindPorts []types.PortBinding,
replicas int32,
selector map[string]string,
) *appsv1.Deployment {
ports := []apiv1.ContainerPort{}
for index, binding := range bindPorts {
ports = append(ports, apiv1.ContainerPort{
Name: fmt.Sprintf("fx-container-%d", index),
ContainerPort: binding.ContainerExposePort,
})
}
container := apiv1.Container{
Name: "fx-placeholder-container-name",
Image: image,
Ports: ports,
ImagePullPolicy: v1.PullIfNotPresent,
}
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: selector,
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: selector,
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{container},
},
},
},
}
}
// GetDeployment get a deployment
func (k *K3S) GetDeployment(namespace string, name string) (*appsv1.Deployment, error) {
return k.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{})
}
// CreateDeployment create a deployment
func (k *K3S) CreateDeployment(
namespace string,
name string,
image string,
ports []types.PortBinding,
replicas int32,
selector map[string]string,
) (*appsv1.Deployment, error) {
deployment := generateDeploymentSpec(name, image, ports, replicas, selector)
return k.AppsV1().Deployments(namespace).Create(deployment)
}
// UpdateDeployment update a deployment
func (k *K3S) UpdateDeployment(
namespace string,
name string,
image string,
ports []types.PortBinding,
replicas int32,
selector map[string]string,
) (*appsv1.Deployment, error) {
deployment := generateDeploymentSpec(name, image, ports, replicas, selector)
return k.AppsV1().Deployments(namespace).Update(deployment)
}
// DeleteDeployment delete a deployment
func (k *K3S) DeleteDeployment(namespace string, name string) error {
return k.AppsV1().Deployments(namespace).Delete(name, &metav1.DeleteOptions{})
}
// CreateDeploymentWithInitContainer create a deployment which will wait InitContainer to do the image build before function container start
func (k *K3S) CreateDeploymentWithInitContainer(
namespace string,
name string,
ports []types.PortBinding,
replicas int32,
selector map[string]string,
) (*appsv1.Deployment, error) {
deployment := generateDeploymentSpec(name, name, ports, replicas, selector)
updatedDeployment := injectInitContainer(name, deployment)
return k.AppsV1().Deployments(namespace).Create(updatedDeployment)
}

View File

@@ -1,61 +0,0 @@
package k3s
import (
"os"
"testing"
"github.com/metrue/fx/types"
)
func TestDeployment(t *testing.T) {
namespace := "default"
name := "fx-hello-world"
image := "metrue/kube-hello"
selector := map[string]string{
"app": "fx-app",
}
kubeconfig := os.Getenv("KUBECONFIG")
if kubeconfig == "" {
t.Skip("skip test since no KUBECONFIG given in environment variable")
}
k8s, err := Create("")
if err != nil {
t.Fatal(err)
}
if _, err := k8s.GetDeployment(namespace, name); err == nil {
t.Fatalf("should get not found error")
}
replicas := int32(2)
bindings := []types.PortBinding{
types.PortBinding{
ServiceBindingPort: 80,
ContainerExposePort: 3000,
},
types.PortBinding{
ServiceBindingPort: 443,
ContainerExposePort: 3000,
},
}
deployment, err := k8s.CreateDeployment(namespace, name, image, bindings, replicas, selector)
if err != nil {
t.Fatal(err)
}
if deployment == nil {
t.Fatalf("deploymetn should not be %v", nil)
}
if deployment.Name != name {
t.Fatalf("should get %s but got %s", name, deployment.Name)
}
if *deployment.Spec.Replicas != replicas {
t.Fatalf("should get %v but got %v", replicas, deployment.Spec.Replicas)
}
if err := k8s.DeleteDeployment(namespace, name); err != nil {
t.Fatal(err)
}
}

View File

@@ -1,70 +0,0 @@
package k3s
import (
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
)
// This is docker image provided by fx/contrib/docker_packer
// it can build a Docker image with give Docker project source codes encoded with base64
// check the detail fx/contrib/docker_packer/main.go
const image = "metrue/fx-docker"
func injectInitContainer(name string, deployment *appsv1.Deployment) *appsv1.Deployment {
configMapHasToBeReady := true
valueInConfigMapHasToBeReady := true
initContainer := v1.Container{
Name: "fx-docker-build-c",
Image: image,
ImagePullPolicy: v1.PullAlways,
Command: []string{
"/bin/sh",
"-c",
"/usr/bin/docker_packer $(APP_META) " + name,
}, // Maybe it can be passed by Binary data from config map
// Args: []string{"${APP_META}"}, // function source codes and name
VolumeMounts: []v1.VolumeMount{
v1.VolumeMount{
Name: "dockersock",
MountPath: "/var/run/docker.sock",
},
},
Env: []v1.EnvVar{
v1.EnvVar{
Name: ConfigMap.AppMetaEnvName,
ValueFrom: &v1.EnvVarSource{
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{Name: name},
Key: ConfigMap.AppMetaEnvName,
Optional: &valueInConfigMapHasToBeReady,
},
},
},
},
EnvFrom: []v1.EnvFromSource{
v1.EnvFromSource{
ConfigMapRef: &v1.ConfigMapEnvSource{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Optional: &configMapHasToBeReady,
},
},
},
}
volumes := []v1.Volume{
v1.Volume{
Name: "dockersock",
VolumeSource: v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: "/var/run/docker.sock",
},
},
},
}
deployment.Spec.Template.Spec.InitContainers = []apiv1.Container{initContainer}
deployment.Spec.Template.Spec.Volumes = volumes
return deployment
}

View File

@@ -1,151 +0,0 @@
package k3s
import (
"context"
"os"
"github.com/metrue/fx/deploy"
"github.com/metrue/fx/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
// K3S client
type K3S struct {
*kubernetes.Clientset
}
const namespace = "default"
// Create a k8s cluster client
func Create(kubeconfig string) (*K3S, error) {
if os.Getenv("KUBECONFIG") != "" {
kubeconfig = os.Getenv("KUBECONFIG")
}
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, err
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return &K3S{clientset}, nil
}
// Deploy a image to be a service
func (k *K3S) Deploy(
ctx context.Context,
fn types.Func,
name string,
image string,
ports []types.PortBinding,
) error {
selector := map[string]string{
"app": "fx-app-" + name,
}
const replicas = int32(3)
if _, err := k.GetDeployment(namespace, name); err != nil {
// TODO enable passing replica from fx CLI
if _, err := k.CreateDeployment(
namespace,
name,
image,
ports,
replicas,
selector,
); err != nil {
return err
}
} else {
if _, err := k.UpdateDeployment(
namespace,
name,
image,
ports,
replicas,
selector,
); err != nil {
return err
}
}
// TODO fx should be able to know what's the target Kubernetes service platform
// it's going to deploy to
typ := "LoadBalancer"
if os.Getenv("SERVICE_TYPE") != "" {
typ = os.Getenv("SERVICE_TYPE")
}
if _, err := k.GetService(namespace, name); err != nil {
if _, err := k.CreateService(
namespace,
name,
typ,
ports,
selector,
); err != nil {
return err
}
} else {
if _, err := k.UpdateService(
namespace,
name,
typ,
ports,
selector,
); err != nil {
return err
}
}
return nil
}
// Update a service
func (k *K3S) Update(ctx context.Context, name string) error {
return nil
}
// Destroy a service
func (k *K3S) Destroy(ctx context.Context, name string) error {
if err := k.DeleteService(namespace, name); err != nil {
return err
}
if err := k.DeleteDeployment(namespace, name); err != nil {
return err
}
return nil
}
// GetStatus get status of a service
func (k *K3S) GetStatus(ctx context.Context, name string) (types.Service, error) {
svc, err := k.GetService(namespace, name)
service := types.Service{}
if err != nil {
return service, err
}
service.Host = svc.Spec.ClusterIP
if len(svc.Spec.ExternalIPs) > 0 {
service.Host = svc.Spec.ExternalIPs[0]
}
for _, port := range svc.Spec.Ports {
// TODO should clearify which port (target port, node port) should use
service.Port = int(port.Port)
break
}
return service, nil
}
// List services
func (k *K3S) List(ctx context.Context, name string) ([]types.Service, error) {
return []types.Service{}, nil
}
var (
_ deploy.Deployer = &K3S{}
)

View File

@@ -1,87 +0,0 @@
package k3s
import (
"strconv"
"github.com/metrue/fx/types"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
intstr "k8s.io/apimachinery/pkg/util/intstr"
)
func generateServiceSpec(
namespace string,
name string,
typ string,
bindings []types.PortBinding,
selector map[string]string,
) *apiv1.Service {
servicePorts := []apiv1.ServicePort{}
for index, binding := range bindings {
servicePorts = append(servicePorts, apiv1.ServicePort{
Name: "port-" + strconv.Itoa(index),
Protocol: apiv1.ProtocolTCP,
Port: binding.ServiceBindingPort,
TargetPort: intstr.FromInt(int(binding.ContainerExposePort)),
})
}
return &apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
ClusterName: namespace,
},
Spec: apiv1.ServiceSpec{
Ports: servicePorts,
Type: apiv1.ServiceType(typ),
Selector: selector,
},
}
}
// CreateService create a service
func (k *K3S) CreateService(
namespace string,
name string,
typ string,
bindings []types.PortBinding,
selector map[string]string,
) (*apiv1.Service, error) {
service := generateServiceSpec(namespace, name, typ, bindings, selector)
createdService, err := k.CoreV1().Services(namespace).Create(service)
if err != nil {
return nil, err
}
return createdService, nil
}
// UpdateService update a service
// TODO this method is not perfect yet, should refactor later
func (k *K3S) UpdateService(
namespace string,
name string,
typ string,
bindings []types.PortBinding,
selector map[string]string,
) (*apiv1.Service, error) {
svc, err := k.GetService(namespace, name)
if err != nil {
return nil, err
}
svc.Spec.Selector = selector
svc.Spec.Type = apiv1.ServiceType(typ)
return k.CoreV1().Services(namespace).Update(svc)
}
// DeleteService a service
func (k *K3S) DeleteService(namespace string, name string) error {
// TODO figure out the elegant way to delete a service
options := &metav1.DeleteOptions{}
return k.CoreV1().Services(namespace).Delete(name, options)
}
// GetService get a service
func (k *K3S) GetService(namespace string, name string) (*apiv1.Service, error) {
return k.CoreV1().Services(namespace).Get(name, metav1.GetOptions{})
}

View File

@@ -1,107 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: deployer.go
// Package mock_deploy is a generated GoMock package.
package mock_deploy
import (
context "context"
gomock "github.com/golang/mock/gomock"
types "github.com/metrue/fx/types"
reflect "reflect"
)
// MockDeployer is a mock of Deployer interface
type MockDeployer struct {
ctrl *gomock.Controller
recorder *MockDeployerMockRecorder
}
// MockDeployerMockRecorder is the mock recorder for MockDeployer
type MockDeployerMockRecorder struct {
mock *MockDeployer
}
// NewMockDeployer creates a new mock instance
func NewMockDeployer(ctrl *gomock.Controller) *MockDeployer {
mock := &MockDeployer{ctrl: ctrl}
mock.recorder = &MockDeployerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockDeployer) EXPECT() *MockDeployerMockRecorder {
return m.recorder
}
// Deploy mocks base method
func (m *MockDeployer) Deploy(ctx context.Context, fn types.Func, name, image string, bindings []types.PortBinding) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Deploy", ctx, fn, name, image, bindings)
ret0, _ := ret[0].(error)
return ret0
}
// Deploy indicates an expected call of Deploy
func (mr *MockDeployerMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockDeployer)(nil).Deploy), ctx, fn, name, image, bindings)
}
// Destroy mocks base method
func (m *MockDeployer) Destroy(ctx context.Context, name string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Destroy", ctx, name)
ret0, _ := ret[0].(error)
return ret0
}
// Destroy indicates an expected call of Destroy
func (mr *MockDeployerMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockDeployer)(nil).Destroy), ctx, name)
}
// Update mocks base method
func (m *MockDeployer) Update(ctx context.Context, name string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, name)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update
func (mr *MockDeployerMockRecorder) Update(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDeployer)(nil).Update), ctx, name)
}
// GetStatus mocks base method
func (m *MockDeployer) GetStatus(ctx context.Context, name string) (types.Service, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetStatus", ctx, name)
ret0, _ := ret[0].(types.Service)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetStatus indicates an expected call of GetStatus
func (mr *MockDeployerMockRecorder) GetStatus(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockDeployer)(nil).GetStatus), ctx, name)
}
// List mocks base method
func (m *MockDeployer) List(ctx context.Context, name string) ([]types.Service, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, name)
ret0, _ := ret[0].([]types.Service)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// List indicates an expected call of List
func (mr *MockDeployerMockRecorder) List(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockDeployer)(nil).List), ctx, name)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

68
examples/functions/Perl/README.md vendored Normal file
View File

@@ -0,0 +1,68 @@
# Make a Perl function a service with fx
[![asciicast](https://asciinema.org/a/aXpr0jquwhhwhghiDCdC7nY8r.svg)](https://asciinema.org/a/aXpr0jquwhhwhghiDCdC7nY8r)
### Hello World
```perl
sub fx {
return 'hello fx'
}
1;
```
then deploy it with `fx up` command,
```shell
$ fx up -p 8080 --name helloworld func.pl
```
test it using `curl`
```shell
$ curl 127.0.0.1:8080
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 11
Content-Type: text/plain; charset=utf-8
Date: Tue, 06 Aug 2019 15:58:41 GMT
hello fx
```
### Sum
```perl
sub fx {
my $ctx = shift;
my $a = $ctx->req->json->{"a"};
my $b = $ctx->req->json->{"b"};
return int($a) + int($b)
}
1;
```
```shell
fx up --name add --port 40002 --force add.pl
```
Then test it with httpie.
```shell
$ http post 0.0.0.0:40002 a=1 b=2
HTTP/1.1 200 OK
Content-Length: 1
Content-Type: application/json;charset=UTF-8
Date: Thu, 02 Jan 2020 15:39:49 GMT
Server: Mojolicious (Perl)
3
```
### ctx
The `ctx` object is exactly the [Controller](https://mojolicious.org/perldoc/Mojolicious/Controller) of [Mojolicious](https://mojolicious.org/perldoc/Mojolicious) framework.

8
examples/functions/Perl/add.pl vendored Normal file
View File

@@ -0,0 +1,8 @@
sub fx {
my $ctx = shift;
my $a = $ctx->req->json->{"a"};
my $b = $ctx->req->json->{"b"};
return int($a) + int($b)
}
1;

417
examples/functions/Perl/demo.cast vendored Normal file
View File

@@ -0,0 +1,417 @@
{"version": 2, "width": 204, "height": 47, "timestamp": 1577978477, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}}
[1.14954, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[1.150447, "o", "\u001b]2;minhuang@C02ZL0RJLVDN: ~/Codes/fx/examples/functions/Perl\u0007\u001b]1;..unctions/Perl\u0007"]
[1.198859, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;31m➜ \u001b[36mPerl\u001b[00m \u001b[01;34mgit:(\u001b[31mperl\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"]
[1.199061, "o", "\u001b[?1h\u001b=\u001b[?2004h"]
[2.253827, "o", "l"]
[2.400431, "o", "\bls"]
[2.55552, "o", "\u001b[?1l\u001b>"]
[2.555606, "o", "\u001b[?2004l\r\r\n"]
[2.557935, "o", "\u001b]2;ls -G\u0007\u001b]1;ls\u0007"]
[2.565597, "o", "README.md add.pl demo.cast hello.pl\r\n"]
[2.566169, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[2.566464, "o", "\u001b]2;minhuang@C02ZL0RJLVDN: ~/Codes/fx/examples/functions/Perl\u0007\u001b]1;..unctions/Perl\u0007"]
[2.616843, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;32m➜ \u001b[36mPerl\u001b[00m \u001b[01;34mgit:(\u001b[31mperl\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"]
[2.617034, "o", "\u001b[?1h\u001b=\u001b[?2004h"]
[2.915162, "o", "v"]
[2.988711, "o", "\bvi"]
[3.202168, "o", "m"]
[3.350667, "o", " "]
[4.12655, "o", "h"]
[4.274974, "o", "e"]
[4.416048, "o", "llo.pl\u001b[1m \u001b[0m"]
[4.787927, "o", "\b\u001b[0m \b"]
[4.788014, "o", "\u001b[?1l\u001b>\u001b[?2004l"]
[4.788292, "o", "\r\r\n"]
[4.789415, "o", "\u001b]2;/usr/local/Cellar/vim/8.2.0/bin/vim hello.pl\u0007\u001b]1;vim\u0007"]
[4.946445, "o", "\u001b[?1000h\u001b[?1049h\u001b[>4;2m\u001b[?1h\u001b=\u001b[?2004h\u001b[1;47r\u001b[?12h\u001b[?12l\u001b[22;2t\u001b[22;1t"]
[4.947253, "o", "\u001b[27m\u001b[29m\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[H\u001b[2J\u001b[?25l\u001b[47;1H\"hello.pl\""]
[4.947367, "o", " 5L, 35C"]
[4.955849, "o", "\u001b[?1000l\u001b[?2004l"]
[4.974498, "o", "\u001b[?2004h\u001b[?1000h"]
[5.109471, "o", "\u001b[?1000l\u001b[?2004l"]
[5.11056, "o", "\u001b[?2004h"]
[5.110849, "o", "\u001b[?1000h\u001b[?1000l\u001b[?2004l"]
[5.113119, "o", "\u001b[?2004h\u001b[?1000h"]
[5.113304, "o", "\u001b[?1000l\u001b[?2004l"]
[5.116222, "o", "\u001b[?2004h\u001b[?1000h"]
[5.116418, "o", "\u001b[?1000l\u001b[?2004l"]
[5.12038, "o", "\u001b[?2004h\u001b[?1000h\u001b[?1000l\u001b[?2004l"]
[5.125489, "o", "\u001b[?2004h\u001b[?1000h\u001b[?1000l\u001b[?2004l"]
[5.132998, "o", "\u001b[?2004h"]
[5.133158, "o", "\u001b[?1000h\u001b[?2004h"]
[5.134064, "o", "\u001b[?1000l\u001b[?2004l"]
[5.151895, "o", "\u001b[?2004h\u001b[?1000h"]
[5.172471, "o", "\u001b[2;1H▽\u001b[6n\u001b[2;1H \u001b[1;1H\u001b[>c"]
[5.172688, "o", "\u001b]10;?\u0007\u001b]11;?\u0007"]
[5.176877, "o", "\u001b[1;1H\u001b[38;2;75;82;99m 1 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;255;83;112msub \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;130;177;255mfx \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m{\r\n\u001b[38;2;75;82;99m 2 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m \u001b[38;2;199;146;234mreturn\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m \u001b[38;2;195;232;141m'hello fx'\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\r\n\u001b[38;2;75;82;99m 3 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m}\r\n\u001b[38;2;75;82;99m 4 \r\n 5 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;247;140;108m1\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m;\r\n\u001b[38;2;59;64;72m~ \u001b[7;1H~ \u001b[8;1H~ "]
[5.177078, "o", " \u001b[9;1H~ \u001b[10;1H~ \u001b[11;1H~ \u001b[12;1H~ "]
[5.177184, "o", " \u001b[13;1H~ \u001b[14;1H~ \u001b[15;1H~ \u001b[16;1H~ \u001b[17;1H~ "]
[5.177303, "o", " \u001b[18;1H~ \u001b[19;1H~ \u001b[20;1H~ \u001b[21;1H~ \u001b[22;1H~ "]
[5.17742, "o", " \u001b[23;1H~ \u001b[24;1H~ \u001b[25;1H~ \u001b[26;1H~ \u001b[27;1H~ "]
[5.177516, "o", " \u001b[28;1H~ \u001b[29;1H~ \u001b[30;1H~ \u001b[31;1H~ \u001b[32;1H~ "]
[5.177622, "o", " \u001b[33;1H~ \u001b[34;1H~ \u001b[35;1H~ \u001b[36;1H~ \u001b[37;1H~ "]
[5.177732, "o", " \u001b[38;1H~ \u001b[39;1H~ \u001b[40;1H~ \u001b[41;1H~ "]
[5.190961, "o", "\u001b[42;1H~ \u001b[43;1H~ \u001b[44;1H~ \u001b[45;1H~ \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;1H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222mNORMAL\u001b[m\u001b[38;2;191;19"]
[5.192792, "o", "9;213m\u001b[48;2;41;45;62m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;191;199;213m\u001b[48;2;71;75;89m ᚠ perl \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;191;199;213m\u001b[48;2;51;55;71m hello.pl perl \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;191;199;213m\u001b[48;2;71;75;89m utf-8[unix] \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m 20% \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m☰ 1/5 ㏑\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m : 1 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;10H\u001b[38;2;191;199;213m\u001b[48;2;71;75;89m+0 ~0 -0 ᚠ perl \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1C\u001b[38;2;191;199;213m\u001b[48;2;51;55;71mhello.pl\u001b[1;5H\u001b[?25h\u001b[?12$p"]
[5.459655, "o", "\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;10H\u001b[38;2;191;199;213m\u001b[48;2;71;75;89mᚠ perl! \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;191;199;213m\u001b[48;2;51;55;71m hello.pl \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[149C\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m4\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[8C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m2/\u001b[2;5H"]
[5.885479, "o", "\u001b[?25l\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1;12H\u001b[38;2;130;177;255m{\u001b[3;5H}\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;10H\u001b[38;2;191;199;213m\u001b[48;2;71;75;89m+0 ~0 -0 ᚠ perl! \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1C\u001b[38;2;191;199;213m\u001b[48;2;51;55;71mhello.pl\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[148C\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m6\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[8C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m3/\u001b[3;5H\u001b[?25h"]
[6.061856, "o", "\u001b[?25l\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1;12H{\u001b[3;5H}\u001b[46;184H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m8\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[8C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m4/\u001b[4;5H\u001b[?25h"]
[6.227967, "o", "\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;183H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m10\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[8C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m5/\u001b[5;5H"]
[6.440828, "o", "\u0007"]
[6.999501, "o", "\u001b[?25l\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[47;1H\u001b[K\u001b[47;1H:\u001b[?1000l\u001b[?2004h\u001b[?25h"]
[7.282638, "o", "q"]
[7.580486, "o", "w"]
[8.042814, "o", "\u001b[?25l\u001b[47;3H\u001b[K\u001b[47;3H\u001b[?25h"]
[8.209127, "o", "\u001b[?25l\u001b[47;2H\u001b[K\u001b[47;2H\u001b[?25h"]
[8.29389, "o", "w"]
[8.364523, "o", "q"]
[8.552089, "o", "\r"]
[8.554758, "o", "\u001b[?1000h\u001b[?25l\u001b[?1000l\u001b[?2004l"]
[8.555496, "o", "\"hello.pl\""]
[8.564609, "o", " 5L, 35C written"]
[8.595134, "o", "\r\u001b[23;2t\u001b[23;1t\r\r\n\u001b[39;49m\u001b[?2004l\u001b[?1l\u001b>\u001b[?25h\u001b[>4;m\u001b[?1049l"]
[8.599757, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[8.600003, "o", "\u001b]2;minhuang@C02ZL0RJLVDN: ~/Codes/fx/examples/functions/Perl\u0007\u001b]1;..unctions/Perl\u0007"]
[8.683359, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;32m➜ \u001b[36mPerl\u001b[00m \u001b[01;34mgit:(\u001b[31mperl\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"]
[8.683591, "o", "\u001b[?1h\u001b="]
[8.683745, "o", "\u001b[?2004h"]
[9.988453, "o", "f"]
[10.203643, "o", "\bfx"]
[10.533603, "o", " up --name add --port 40001 hello.pl --force"]
[11.146588, "o", "\u001b[?1l\u001b>"]
[11.146941, "o", "\u001b[?2004l\r\r\n"]
[11.148205, "o", "\u001b]2;fx up --name add --port 40001 hello.pl --force\u0007"]
[11.148443, "o", "\u001b]1;fx\u0007"]
[11.24007, "o", "building \u001b[32m[ ]\u001b[0m "]
[11.340717, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[11.341114, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[11.341521, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kbuilding \u001b[32m[=> ]\u001b[0m "]
[11.411368, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[11.411537, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[11.445379, "o", "destroying add \u001b[34m[===> ]\u001b[0m "]
[11.5467, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[11.547266, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[11.54763, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[=====> ]\u001b[0m "]
[11.652602, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[11.653023, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[11.653426, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[======> ]\u001b[0m "]
[11.756859, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[11.75701, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[========> ]\u001b[0m "]
[11.857969, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[11.858251, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[11.858445, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[==========> ]\u001b[0m "]
[11.962964, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[11.963505, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[11.963792, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[11.964263, "o", "\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[============> ]\u001b[0m "]
[12.066808, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[12.067166, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[12.067532, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[12.067729, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[==============> ]\u001b[0m "]
[12.11908, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[12.119302, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[12.167966, "o", "deploying add \u001b[36m[================> ]\u001b[0m "]
[12.271321, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[12.271512, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[==================> ]\u001b[0m "]
[12.373093, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[12.373377, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[12.373483, "o", "deploying add \u001b[36m[===================>]\u001b[0m "]
[12.475496, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[12.475887, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[ ]\u001b[0m "]
[12.576106, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[12.576491, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[=> ]\u001b[0m "]
[12.681062, "o", "\b\b\b\b\b\b\b\b"]
[12.681348, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[12.681596, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[===> ]\u001b[0m "]
[12.785704, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[12.786212, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[12.7866, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[=====> ]\u001b[0m "]
[12.889932, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[12.890131, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[======> ]\u001b[0m "]
[12.993483, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[12.993626, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[========> ]\u001b[0m "]
[13.09445, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[13.094945, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[13.095276, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[==========> ]\u001b[0m "]
[13.195321, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[13.195719, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[13.195845, "o", "\u001b[K\u001b[Kdeploying add \u001b[36m[============> ]\u001b[0m "]
[13.299457, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[13.299971, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[==============> ]\u001b[0m "]
[13.403608, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[13.404077, "o", "\b\b\b\b\b\b\b\b\b"]
[13.404501, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[================> ]\u001b[0m "]
[13.505661, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[13.506245, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[13.506398, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[13.506526, "o", "\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[13.506799, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[==================> ]\u001b[0m "]
[13.608501, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[13.608887, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[13.609235, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[===================>]\u001b[0m "]
[13.709097, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[13.709286, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[13.709322, "o", "deploying add \u001b[36m[ ]\u001b[0m "]
[13.812622, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[13.813279, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[13.813713, "o", "\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[=> ]\u001b[0m "]
[13.917566, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[13.917907, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[===> ]\u001b[0m "]
[14.02233, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[14.022513, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[=====> ]\u001b[0m "]
[14.125472, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[14.125627, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[======> ]\u001b[0m "]
[14.225728, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[14.226074, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[========> ]\u001b[0m "]
[14.326329, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[14.326708, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[14.327001, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[14.327291, "o", "deploying add \u001b[36m[==========> ]\u001b[0m "]
[14.430275, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[14.43045, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[36m[============> ]\u001b[0m "]
[14.477509, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[14.477667, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[14.480551, "o", "+------------------------------------------------------------------+------+---------------+\r\n| ID | NAME | ENDPOINT |\r\n+------------------------------------------------------------------+------+---------------+"]
[14.480737, "o", "\r\n| dc546007a6b7c8738a3107efe18041da27ccc0f1dfd8e992bc0fb4b0514c9ba0 | /add | 0.0.0.0:40001 |\r\n+------------------------------------------------------------------+------+---------------+\r\n"]
[14.48264, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[14.482857, "o", "\u001b]2;minhuang@C02ZL0RJLVDN: ~/Codes/fx/examples/functions/Perl\u0007\u001b]1;..unctions/Perl\u0007"]
[14.538028, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;32m➜ \u001b[36mPerl\u001b[00m \u001b[01;34mgit:(\u001b[31mperl\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"]
[14.538161, "o", "\u001b[?1h\u001b="]
[14.538226, "o", "\u001b[?2004h"]
[15.721709, "o", "h"]
[15.856214, "o", "\bht"]
[16.027529, "o", "t"]
[17.184631, "o", "p localhost:40001"]
[17.671003, "o", "\u001b[?1l\u001b>"]
[17.671086, "o", "\u001b[?2004l\r\r\n"]
[17.672363, "o", "\u001b]2;http localhost:40001\u0007\u001b]1;http\u0007"]
[17.96737, "o", "\u001b[34mHTTP\u001b[39;49;00m/\u001b[34m1.1\u001b[39;49;00m \u001b[34m200\u001b[39;49;00m \u001b[36mOK\u001b[39;49;00m\r\n\u001b[36mContent-Length\u001b[39;49;00m: 10\r\n\u001b[36mContent-Type\u001b[39;49;00m: application/json;charset=UTF-8\r\n\u001b[36mDate\u001b[39;49;00m: Thu, 02 Jan 2020 15:21:35 GMT\r\n\u001b[36mServer\u001b[39;49;00m: Mojolicious (Perl)\r\r\n\r\r\n"]
[17.968554, "o", "\u001b[33m\"hello fx\"\u001b[39;49;00m\r\n\r\n"]
[17.993945, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[17.994105, "o", "\u001b]2;minhuang@C02ZL0RJLVDN: ~/Codes/fx/examples/functions/Perl\u0007\u001b]1;..unctions/Perl\u0007"]
[18.046585, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;32m➜ \u001b[36mPerl\u001b[00m \u001b[01;34mgit:(\u001b[31mperl\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"]
[18.046727, "o", "\u001b[?1h\u001b=\u001b[?2004h"]
[19.712512, "o", "v"]
[19.834568, "o", "\bvi"]
[20.03562, "o", "m"]
[20.145382, "o", " "]
[20.224959, "o", "a"]
[20.458989, "o", "."]
[20.85012, "o", "\b \b"]
[21.149959, "o", "dd.pl\u001b[1m \u001b[0m"]
[21.640042, "o", "\b\u001b[0m \b"]
[21.640125, "o", "\u001b[?1l\u001b>"]
[21.640405, "o", "\u001b[?2004l\r\r\n"]
[21.641586, "o", "\u001b]2;/usr/local/Cellar/vim/8.2.0/bin/vim add.pl\u0007\u001b]1;vim\u0007"]
[21.809875, "o", "\u001b[?1000h\u001b[?1049h\u001b[>4;2m\u001b[?1h\u001b=\u001b[?2004h\u001b[1;47r\u001b[?12h\u001b[?12l\u001b[22;2t\u001b[22;1t"]
[21.810887, "o", "\u001b[27m\u001b[29m\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[H\u001b[2J\u001b[?25l\u001b[47;1H\"add.pl\""]
[21.811558, "o", " 8L, 129C"]
[21.819874, "o", "\u001b[?1000l\u001b[?2004l"]
[21.836929, "o", "\u001b[?2004h"]
[21.837071, "o", "\u001b[?1000h"]
[21.966477, "o", "\u001b[?1000l\u001b[?2004l"]
[21.967556, "o", "\u001b[?2004h"]
[21.967761, "o", "\u001b[?1000h\u001b[?1000l\u001b[?2004l"]
[21.969825, "o", "\u001b[?2004h"]
[21.969988, "o", "\u001b[?1000h\u001b[?1000l\u001b[?2004l"]
[21.972986, "o", "\u001b[?2004h\u001b[?1000h"]
[21.973119, "o", "\u001b[?1000l\u001b[?2004l"]
[21.977246, "o", "\u001b[?2004h\u001b[?1000h\u001b[?1000l\u001b[?2004l"]
[21.982549, "o", "\u001b[?2004h\u001b[?1000h"]
[21.982683, "o", "\u001b[?1000l\u001b[?2004l"]
[21.990047, "o", "\u001b[?2004h\u001b[?1000h\u001b[?2004h"]
[21.991254, "o", "\u001b[?1000l\u001b[?2004l"]
[22.007821, "o", "\u001b[?2004h\u001b[?1000h"]
[22.024663, "o", "\u001b[2;1H▽\u001b[6n\u001b[2;1H \u001b[1;1H\u001b[>c"]
[22.02487, "o", "\u001b]10;?\u0007\u001b]11;?\u0007"]
[22.032552, "o", "\u001b[1;1H\u001b[38;2;75;82;99m 1 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;255;83;112msub \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;130;177;255mfx \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m{\r\n\u001b[38;2;75;82;99m 2 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m \u001b[38;2;199;146;234mmy\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m \u001b[38;2;255;83;112m$ctx\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m = \u001b[38;2;199;146;234mshift\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m;\r\n\u001b[38;2;75;82;99m 3 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m \u001b[38;2;199;146;234mmy\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m \u001b[38;2;255;83;112m$a\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m = \u001b[38;2;255;83;112m$ctx->req->json->{\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;195;232;141m\"a\"\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;255;83;112m}\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m;\r\n\u001b[38;2;75;82;99m 4 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m \u001b[38;2;199;146;234mmy\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m \u001b[38;2;255;83;112m$b\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m = \u001b[38;2;255;83;112m$ctx->req->json"]
[22.032746, "o", "->{\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;195;232;141m\"b\"\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;255;83;112m}\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m;\r\n\u001b[38;2;75;82;99m 5 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m \u001b[38;2;199;146;234mreturn\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m \u001b[38;2;199;146;234mint\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m(\u001b[38;2;255;83;112m$a\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m) + \u001b[38;2;199;146;234mint\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m(\u001b[38;2;255;83;112m$b\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m)\r\n\u001b[38;2;75;82;99m 6 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m}\r\n\u001b[38;2;75;82;99m 7 \r\n 8 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;247;140;108m1\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m;\r\n\u001b[38;2;59;64;72m~ \u001b[10;1H~ "]
[22.032843, "o", " \u001b[11;1H~ \u001b[12;1H~ \u001b[13;1H~ \u001b[14;1H~ \u001b[15;1H~ "]
[22.03293, "o", " \u001b[16;1H~ \u001b[17;1H~ \u001b[18;1H~ \u001b[19;1H~ \u001b[20;1H~ "]
[22.033025, "o", " \u001b[21;1H~ \u001b[22;1H~ \u001b[23;1H~ \u001b[24;1H~ "]
[22.03311, "o", " \u001b[25;1H~ \u001b[26;1H~ \u001b[27;1H~ \u001b[28;1H~ \u001b[29;1H~ "]
[22.033229, "o", " \u001b[30;1H~ \u001b[31;1H~ \u001b[32;1H~ \u001b[33;1H~ \u001b[34;1H~ "]
[22.033337, "o", " \u001b[35;1H~ \u001b[36;1H~ \u001b[37;1H~ \u001b[38;1H~ \u001b[39;1H~ "]
[22.042308, "o", " \u001b[40;1H~ \u001b[41;1H~ \u001b[42;1H~ \u001b[43;1H~ \u001b[44;1H~ "]
[22.042505, "o", " \u001b[45;1H~ \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;1H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222mNORMAL\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;191;199;213m\u001b[48;2;71;75;89m ᚠ perl \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;191;199;213m\u001b[48;2;51;55;71m add.pl perl \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m"]
[22.048105, "o", "\u001b[38;2;191;199;213m\u001b[48;2;71;75;89m utf-8[unix] \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m 12% \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m☰ 1/8 ㏑\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m : 1 \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;10H\u001b[38;2;191;199;213m\u001b[48;2;71;75;89m+0 ~0 -0 ᚠ perl \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1C\u001b[38;2;191;199;213m\u001b[48;2;51;55;71madd.pl\u001b[1;5H\u001b[?25h"]
[22.048832, "o", "\u001b[?12$p"]
[22.111039, "o", "\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;10H\u001b[38;2;191;199;213m\u001b[48;2;71;75;89mᚠ perl! \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[38;2;191;199;213m\u001b[48;2;51;55;71m add.pl \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[151C\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m25\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[7C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m2/\u001b[2;5H"]
[22.700162, "o", "\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;10H\u001b[38;2;191;199;213m\u001b[48;2;71;75;89m+0 ~0 -0 ᚠ perl! \u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1C\u001b[38;2;191;199;213m\u001b[48;2;51;55;71madd.pl\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[150C\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m37\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[7C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m3/\u001b[3;5H"]
[22.894195, "o", "\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;184H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m50\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[7C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m4/\u001b[4;5H"]
[23.081045, "o", "\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;184H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m62\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[7C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m5/\u001b[5;5H"]
[23.313711, "o", "\u001b[?25l\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1;12H\u001b[38;2;130;177;255m{\u001b[6;5H}\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;184H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m75\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[7C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m6/\u001b[6;5H\u001b[?25h"]
[23.602087, "o", "\u001b[?25l\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1;12H{\u001b[6;5H}\u001b[46;184H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m87\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[7C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m7/\u001b[7;5H\u001b[?25h"]
[23.962955, "o", "\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;183H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m100\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[7C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m8/\u001b[8;5H"]
[24.549961, "o", "\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;183H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m 87\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[7C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m7/\u001b[7;5H"]
[24.793843, "o", "\u001b[?25l\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1;12H\u001b[38;2;130;177;255m{\u001b[6;5H}\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;184H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m75\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[7C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m6/\u001b[6;5H\u001b[?25h"]
[24.99262, "o", "\u001b[?25l\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[1;12H{\u001b[6;5H}\u001b[46;184H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m62\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[7C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m5/\u001b[5;5H\u001b[?25h"]
[25.362512, "o", "\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[46;184H\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m50\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[7C\u001b[1m\u001b[38;2;41;45;62m\u001b[48;2;147;158;222m4/\u001b[4;5H"]
[25.77714, "o", "\u001b[?25l\u001b[m\u001b[38;2;191;199;213m\u001b[48;2;41;45;62m\u001b[47;1H\u001b[K\u001b[47;1H:\u001b[?1000l\u001b[?2004h\u001b[?25h"]
[25.945572, "o", "q"]
[26.47528, "o", "\r"]
[26.490539, "o", "\u001b[?1000h\u001b[?25l\u001b[?1000l\u001b[?2004l\u001b[23;2t\u001b[23;1t"]
[26.490711, "o", "\u001b[47;1H\u001b[K\u001b[47;1H\u001b[?2004l\u001b[?1l\u001b>\u001b[?25h\u001b[>4;m\u001b[?1049l"]
[26.493893, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[26.494036, "o", "\u001b]2;minhuang@C02ZL0RJLVDN: ~/Codes/fx/examples/functions/Perl\u0007\u001b]1;..unctions/Perl\u0007"]
[26.55072, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;32m➜ \u001b[36mPerl\u001b[00m \u001b[01;34mgit:(\u001b[31mperl\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"]
[26.5509, "o", "\u001b[?1h\u001b=\u001b[?2004h"]
[27.310856, "o", "f"]
[27.553944, "o", "\bfx"]
[28.138222, "o", " "]
[28.326337, "o", "u"]
[28.436765, "o", "p"]
[28.894128, "o", " --name add --port 40001 hello.pl --force"]
[29.129425, "o", "\u001b[18D2 add\u001b[P\u001b[P\u001b[11C \b\b"]
[32.439129, "o", "\u001b[?1l\u001b>\u001b[?2004l\r\r\n"]
[32.440378, "o", "\u001b]2;fx up --name add --port 40002 add.pl --force\u0007"]
[32.440597, "o", "\u001b]1;fx\u0007"]
[32.556138, "o", "building \u001b[35m[ ]\u001b[0m "]
[32.660991, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[32.661456, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kbuilding \u001b[35m[=> ]\u001b[0m "]
[32.762575, "o", "\b"]
[32.763291, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[32.763534, "o", "building \u001b[35m[===> ]\u001b[0m "]
[32.783314, "o", "\b\b\b\b\b\b\b\b\b\b\b\b"]
[32.783556, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[32.865981, "o", "destroying add \u001b[34m[=====> ]\u001b[0m "]
[32.966633, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[32.967025, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[32.967261, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[======> ]\u001b[0m "]
[33.069946, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[33.070613, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[33.070982, "o", "destroying add \u001b[34m[========> ]\u001b[0m "]
[33.170881, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[33.171194, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[33.171417, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[==========> ]\u001b[0m "]
[33.275611, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[33.276073, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[============> ]\u001b[0m "]
[33.380623, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[33.381102, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[33.381339, "o", "\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[==============> ]\u001b[0m "]
[33.48262, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[33.483143, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[33.483701, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[33.483998, "o", "destroying add \u001b[34m[================> ]\u001b[0m "]
[33.586747, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[33.58728, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[33.587416, "o", "destroying add \u001b[34m[==================> ]\u001b[0m "]
[33.691358, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[33.691957, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[33.692362, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[===================>]\u001b[0m "]
[33.792488, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[33.792889, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[33.793331, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[ ]\u001b[0m "]
[33.898149, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[33.89864, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[33.899119, "o", "\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[=> ]\u001b[0m "]
[34.002146, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.002324, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[===> ]\u001b[0m "]
[34.106599, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.107121, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.107633, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[=====> ]\u001b[0m "]
[34.210556, "o", "\b"]
[34.211072, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.21122, "o", "\b\b\b\b\b\b"]
[34.211895, "o", "\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[======> ]\u001b[0m "]
[34.315191, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.315454, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[34.315605, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdestroying add \u001b[34m[========> ]\u001b[0m "]
[34.320443, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.320609, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K"]
[34.320709, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[34.420121, "o", "deploying add \u001b[34m[==========> ]\u001b[0m "]
[34.523078, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.523295, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[============> ]\u001b[0m "]
[34.624895, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.62532, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.625574, "o", "\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[==============> ]\u001b[0m "]
[34.727629, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.728192, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.7286, "o", "\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[================> ]\u001b[0m "]
[34.831119, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.831739, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[==================> ]\u001b[0m "]
[34.932013, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[34.932102, "o", ""]
[34.932296, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[===================>]\u001b[0m "]
[35.036361, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.036569, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.036826, "o", "\b\b\b\b\b\b\b\b\b\b\b"]
[35.036903, "o", "\b\b\b\b\b\b\b\b\b"]
[35.037301, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[35.037897, "o", "\u001b[Kdeploying add \u001b[34m[ ]\u001b[0m "]
[35.140429, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.14113, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[35.141475, "o", "deploying add \u001b[34m[=> ]\u001b[0m "]
[35.241427, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.241811, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.242177, "o", "\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[===> ]\u001b[0m "]
[35.344792, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.345114, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[35.345431, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[=====> ]\u001b[0m "]
[35.44826, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.448627, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[35.448919, "o", "deploying add \u001b[34m[======> ]\u001b[0m "]
[35.55305, "o", "\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.55356, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.55388, "o", "\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[========> ]\u001b[0m "]
[35.65903, "o", "\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.65939, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[35.659725, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[==========> ]\u001b[0m "]
[35.75989, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.760206, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[35.760447, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[============> ]\u001b[0m "]
[35.86524, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.865663, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[35.866145, "o", "\u001b[K\u001b[Kdeploying add \u001b[34m[==============> ]\u001b[0m "]
[35.970426, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[35.970849, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[35.971211, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[================> ]\u001b[0m "]
[36.074506, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[36.07483, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[36.0751, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[==================> ]\u001b[0m "]
[36.175231, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[36.175434, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[===================>]\u001b[0m "]
[36.278976, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[36.279143, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[36.279243, "o", "deploying add \u001b[34m[ ]\u001b[0m "]
[36.380487, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[36.380963, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[36.381454, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[=> ]\u001b[0m "]
[36.486341, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[36.487048, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[===> ]\u001b[0m "]
[36.588875, "o", "\b\b\b\b\b\b\b"]
[36.589343, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[36.589578, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[36.589795, "o", "\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[=====> ]\u001b[0m "]
[36.691472, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[36.691721, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[Kdeploying add \u001b[34m[======> ]\u001b[0m "]
[36.711906, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"]
[36.712114, "o", "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K\u001b[K"]
[36.714878, "o", "+------------------------------------------------------------------+------+---------------+\r\n| ID | NAME | ENDPOINT |"]
[36.715104, "o", "\r\n+------------------------------------------------------------------+------+---------------+\r\n| 9ccf40c247b8cd6a82292fc526f6e1139432953b231ba4f51a1f18d4c13f6458 | /add | 0.0.0.0:40002 |\r\n+------------------------------------------------------------------+------+---------------+\r\n"]
[36.716932, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[36.717114, "o", "\u001b]2;minhuang@C02ZL0RJLVDN: ~/Codes/fx/examples/functions/Perl\u0007"]
[36.717147, "o", "\u001b]1;..unctions/Perl\u0007"]
[36.779836, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;32m➜ \u001b[36mPerl\u001b[00m \u001b[01;34mgit:(\u001b[31mperl\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"]
[36.779976, "o", "\u001b[?1h\u001b=\u001b[?2004h"]
[37.398193, "o", "h"]
[37.594295, "o", "\bht"]
[37.71095, "o", "t"]
[37.85487, "o", "p"]
[38.075195, "o", " "]
[38.247123, "o", "p"]
[38.426405, "o", "o"]
[39.448868, "o", "st 0.0.0.0:40002 a=1 b=2"]
[40.51774, "o", "\u001b[?1l\u001b>\u001b[?2004l"]
[40.517813, "o", "\r\r\n"]
[40.518991, "o", "\u001b]2;http post 0.0.0.0:40002 a=1 b=2\u0007"]
[40.51914, "o", "\u001b]1;http\u0007"]
[40.811478, "o", "\u001b[34mHTTP\u001b[39;49;00m/\u001b[34m1.1\u001b[39;49;00m \u001b[34m200\u001b[39;49;00m \u001b[36mOK\u001b[39;49;00m\r\n\u001b[36mContent-Length\u001b[39;49;00m: 1\r\n\u001b[36mContent-Type\u001b[39;49;00m: application/json;charset=UTF-8\r\n\u001b[36mDate\u001b[39;49;00m: Thu, 02 Jan 2020 15:21:57 GMT\r\n\u001b[36mServer\u001b[39;49;00m: Mojolicious (Perl)\r\r\n\r\r\n"]
[40.812798, "o", "\u001b[34m3\u001b[39;49;00m\r\n\r\n"]
[40.837697, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[40.837901, "o", "\u001b]2;minhuang@C02ZL0RJLVDN: ~/Codes/fx/examples/functions/Perl\u0007\u001b]1;..unctions/Perl\u0007"]
[40.890525, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;32m➜ \u001b[36mPerl\u001b[00m \u001b[01;34mgit:(\u001b[31mperl\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"]
[40.890681, "o", "\u001b[?1h\u001b=\u001b[?2004h"]
[43.215524, "o", "\u001b[?2004l\r\r\n"]

5
examples/functions/Perl/hello.pl vendored Normal file
View File

@@ -0,0 +1,5 @@
sub fx {
return 'hello fx'
}
1;

47
examples/functions/Rust/README.md vendored Normal file
View File

@@ -0,0 +1,47 @@
# Make a Rust function a service with fx
Write a function like,
```rust
pub mod fns {
#[derive(Serialize)]
pub struct Response {
pub result: i32,
}
#[derive(Deserialize)]
pub struct Request {
pub a: i32,
pub b: i32,
}
pub fn func(req: Request) -> Response {
Response {
result: req.a + req.b,
}
}
}
```
then deploy it with `fx up` command,
```shell
$ fx up -p 8080 func.rs
```
test it using `curl`
```shell
$ curl -X 'POST' --header 'Content-Type: application/json' --data '{"a":1,"b":1}' '0.0.0.0:3000'
HTTP/1.1 200 OK
Content-Length: 12
Content-Type: application/json
Date: Fri, 06 Dec 2019 06:45:14 GMT
Server: Rocket
{
"result": 2
}
```

29
fx.go
View File

@@ -14,9 +14,10 @@ import (
"github.com/metrue/fx/handlers"
"github.com/metrue/fx/middlewares"
"github.com/urfave/cli"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
)
const version = "0.8.6"
const version = "0.9.2"
func init() {
go checkForUpdate()
@@ -156,8 +157,9 @@ func main() {
},
Action: handle(
middlewares.LoadConfig,
middlewares.Setup,
middlewares.Parse,
middlewares.Provision,
middlewares.Parse("up"),
middlewares.Language(),
middlewares.Binding,
middlewares.Build,
handlers.Up,
@@ -168,8 +170,9 @@ func main() {
Usage: "destroy a service",
ArgsUsage: "[service 1, service 2, ....]",
Action: handle(
middlewares.Parse("down"),
middlewares.LoadConfig,
middlewares.Setup,
middlewares.Provision,
handlers.Down,
),
},
@@ -177,9 +180,17 @@ func main() {
Name: "list",
Aliases: []string{"ls"},
Usage: "list deployed services",
Flags: []cli.Flag{
cli.StringFlag{
Name: "format, f",
Value: "table",
Usage: "output format, 'table' and 'JSON' supported",
},
},
Action: handle(
middlewares.Parse("list"),
middlewares.LoadConfig,
middlewares.Setup,
middlewares.Provision,
handlers.List,
),
},
@@ -209,7 +220,9 @@ func main() {
},
Action: handle(
middlewares.LoadConfig,
middlewares.Setup,
middlewares.Provision,
middlewares.Parse("image_build"),
middlewares.Language(),
handlers.BuildImage,
),
},
@@ -224,7 +237,9 @@ func main() {
},
Action: handle(
middlewares.LoadConfig,
middlewares.Setup,
middlewares.Provision,
middlewares.Parse("image_export"),
middlewares.Language(),
handlers.ExportImage,
),
},

29
go.mod
View File

@@ -5,16 +5,18 @@ go 1.12
require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/apex/log v1.1.1
github.com/briandowns/spinner v1.7.0
github.com/apex/log v1.1.2
github.com/briandowns/spinner v1.9.0
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v0.0.0-20190313072916-46036c230805
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.3.3 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/gin-gonic/gin v1.4.0
github.com/gobuffalo/packr v1.30.1
github.com/golang/mock v1.3.1
github.com/gobuffalo/envy v1.8.1 // indirect
github.com/gobuffalo/packd v0.3.0
github.com/gobuffalo/packr/v2 v2.5.1
github.com/golang/mock v1.4.1
github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-querystring v1.0.0
github.com/google/uuid v1.1.1
@@ -22,26 +24,29 @@ require (
github.com/gorilla/mux v1.7.3 // indirect
github.com/imdario/mergo v0.3.7 // indirect
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b
github.com/metrue/go-ssh-client v0.0.0-20191219103445-1f07b67e2b29
github.com/mholt/archiver v3.1.1+incompatible
github.com/mitchellh/go-homedir v1.1.0
github.com/morikuni/aec v1.0.0 // indirect
github.com/nwaples/rardecode v1.0.0 // indirect
github.com/olekukonko/tablewriter v0.0.3
github.com/olekukonko/tablewriter v0.0.4
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/otiai10/copy v1.0.2
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf // indirect
github.com/pkg/errors v0.8.1
github.com/stretchr/testify v1.4.0
github.com/pkg/errors v0.9.1
github.com/rogpeppe/go-internal v1.5.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.6.2
github.com/stretchr/testify v1.5.1
github.com/ugorji/go v1.1.7 // indirect
github.com/urfave/cli v1.22.2
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
google.golang.org/grpc v1.21.0 // indirect
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915 // indirect
golang.org/x/sys v0.0.0-20191223224216-5a3cf8467b4e // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.7
gopkg.in/yaml.v2 v2.2.7 // indirect
gotest.tools v2.2.0+incompatible // indirect
k8s.io/api v0.0.0-20190925180651-d58b53da08f5
k8s.io/apimachinery v0.0.0-20190925235427-62598f38f24e

138
go.sum
View File

@@ -1,5 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
@@ -15,21 +16,31 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/apex/log v1.1.1 h1:BwhRZ0qbjYtTob0I+2M+smavV0kOC8XgcnGZcyL9liA=
github.com/apex/log v1.1.1/go.mod h1:Ls949n1HFtXfbDcjiTTFQqkVUrte0puoIBfO3SVgwOA=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apex/log v1.1.2 h1:bnDuVoi+o98wOdVqfEzNDlY0tcmBia7r4YkjS9EqGYk=
github.com/apex/log v1.1.2/go.mod h1:SyfRweFO+TlkIJ3DVizTSeI1xk7jOIIqOnUPZQTTsww=
github.com/apex/logs v0.0.3/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/briandowns/spinner v1.7.0 h1:aan1hBBOoscry2TXAkgtxkJiq7Se0+9pt+TUWaPrB4g=
github.com/briandowns/spinner v1.7.0/go.mod h1://Zf9tMcxfRUA36V23M6YGEAv+kECGfvpnLTnb8n4XQ=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/briandowns/spinner v1.9.0 h1:+OMAisemaHar1hjuJ3Z2hIvNhQl9Y7GLPWUwwz2Pxo8=
github.com/briandowns/spinner v1.9.0/go.mod h1://Zf9tMcxfRUA36V23M6YGEAv+kECGfvpnLTnb8n4XQ=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
@@ -39,6 +50,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v0.0.0-20190313072916-46036c230805 h1:Imk7y5LY4ljn+DhwaPVj9d8kvAxiZw8DQGwNmivIom0=
@@ -61,33 +73,44 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.8.1 h1:RUr68liRvs0TS1D5qdW3mQv2SjAsu1QWMCx1tG4kDjs=
github.com/gobuffalo/envy v1.8.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
github.com/gobuffalo/logger v1.0.0 h1:xw9Ko9EcC5iAFprrjJ6oZco9UpzS5MQ4jAwghsLHdy4=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1 h1:TFOeY2VoGamPjQLiNDT3mn//ytzk236VMO2j7iHxJR4=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.1 h1:ocYkMQY5RrXTYgXl7ICpV0IXwlEQGwKIsery4gyXa1U=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -96,6 +119,7 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -113,9 +137,15 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@@ -124,18 +154,24 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
@@ -151,7 +187,10 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434 h1:im9kkmH0WWwxzegiv18gSUJbuXR9y028rXrWuPp6Jug=
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
@@ -161,10 +200,11 @@ github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b h1:JGD0sJ44XzhsT1voOg00zji4ubuMNcVNK3m7d9GI88k=
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b/go.mod h1:ERHOEBrDy6+8vfoJjjmhdmBpOzdvvP7bLtwYTTK6LOs=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/metrue/go-ssh-client v0.0.0-20191219103445-1f07b67e2b29 h1:ENoMPMVc24XbBuVZ7guZmTB/7MSd+vqOkImSu9UUiJw=
github.com/metrue/go-ssh-client v0.0.0-20191219103445-1f07b67e2b29/go.mod h1:aPG/JtXTyLliKDDlkv+nzHbSbz2p2CBMAjNJRK4uhzY=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
@@ -182,11 +222,13 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/olekukonko/tablewriter v0.0.3 h1:i0LBnzgiChAWHJYTQAZJDOgf8MNxAVYZJ2m63SIDimI=
github.com/olekukonko/tablewriter v0.0.3/go.mod h1:YZeBtGzYYEsCHp2LST/u/0NDwGkRoBtmn1cIWCJiS6M=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -196,22 +238,45 @@ github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2i
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/otiai10/copy v1.0.2 h1:DDNipYy6RkIkjMwy+AWzgKiNTyj2RUI9yEMeETEpVyc=
github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95 h1:+OLn68pqasWca0z5ryit9KGfp3sUsW4Lqg32iRMJyzs=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/mint v1.3.0 h1:Ady6MKVezQwHBkGzLFbrsywyp09Ah7rkmfjV3Bcr5uc=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf h1:0d7SseXGaeqFXfRTLbiCkuLhSGEHZyKpz1XD3e5lbSo=
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.5.1 h1:asQ0uD7BN9RU5Im41SEEZTwCi/zAXdMOLS3npYaos2g=
github.com/rogpeppe/go-internal v1.5.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
@@ -219,12 +284,19 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
@@ -232,11 +304,17 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -246,10 +324,15 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
@@ -258,14 +341,18 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -274,6 +361,8 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915 h1:aJ0ex187qoXrJHPo8ZasVTASQB7llQP6YeNzgDALPRk=
golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -283,11 +372,14 @@ golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
@@ -307,6 +399,8 @@ golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -316,9 +410,10 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191223224216-5a3cf8467b4e h1:z2Flw7sLy7DxaQi3zDOvI9X+Kb06+G9iZJlkEyHvujE=
golang.org/x/sys v0.0.0-20191223224216-5a3cf8467b4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
@@ -326,6 +421,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -333,6 +429,7 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c h1:KfpJVdWhuRqNk4XVXzjXf2KAV4TBEP77SYdFGjeGuIE=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@@ -348,6 +445,7 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -361,7 +459,11 @@ gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -390,6 +492,10 @@ k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKf
k8s.io/utils v0.0.0-20190920012459-5008bf6f8cd6/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20190923111123-69764acb6e8e h1:BXSmdH6S3YGLlhC89DZp+sNdYSmwNeDU6Xu5ZpzGOlM=
k8s.io/utils v0.0.0-20190923111123-69764acb6e8e/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@@ -1,43 +1,11 @@
package handlers
import (
"io/ioutil"
"strings"
"github.com/apex/log"
"github.com/metrue/fx/context"
"github.com/metrue/fx/packer"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
)
// Call command handle
func Call(ctx context.Contexter) error {
cli := ctx.GetCliContext()
_ = strings.Join(cli.Args()[1:], " ")
file := cli.Args().First()
src, err := ioutil.ReadFile(file)
if err != nil {
log.Fatalf("Read Source: %v", err)
return err
}
log.Info("Read Source: \u2713")
lang := utils.GetLangFromFileName(file)
fn := types.Func{
Language: lang,
Source: string(src),
}
if _, err := packer.Pack(file, fn); err != nil {
panic(err)
}
// TODO not supported
// if err := api.MustCreate(host.Host, constants.AgentPort).
// Call(file, params, project); err != nil {
// log.Fatalf("call functions on machine %s with %v failed: %v", name, params, err)
// }
return nil
}

View File

@@ -2,21 +2,13 @@ package handlers
import (
"github.com/metrue/fx/context"
"github.com/metrue/fx/deploy"
"github.com/metrue/fx/pkg/spinner"
"github.com/metrue/fx/infra"
)
// Down command handle
func Down(ctx context.Contexter) (err error) {
const task = "destroying"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
cli := ctx.GetCliContext()
services := cli.Args()
runner := ctx.Get("deployer").(deploy.Deployer)
services := ctx.Get("services").([]string)
runner := ctx.Get("deployer").(infra.Deployer)
for _, svc := range services {
if err := runner.Destroy(ctx.GetContext(), svc); err != nil {
return err

27
handlers/down_test.go Normal file
View File

@@ -0,0 +1,27 @@
package handlers
import (
"context"
"testing"
"github.com/golang/mock/gomock"
mockCtx "github.com/metrue/fx/context/mocks"
mockDeployer "github.com/metrue/fx/infra/mocks"
)
func TestDown(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := mockCtx.NewMockContexter(ctrl)
deployer := mockDeployer.NewMockDeployer(ctrl)
services := []string{"sample-name"}
ctx.EXPECT().Get("services").Return(services)
ctx.EXPECT().Get("deployer").Return(deployer)
ctx.EXPECT().GetContext().Return(context.Background())
deployer.EXPECT().Destroy(gomock.Any(), services[0]).Return(nil)
if err := Down(ctx); err != nil {
t.Fatal(err)
}
}

Some files were not shown because too many files have changed in this diff Show More