Compare commits
96 Commits
0.8.0-alph
...
0.9.2-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7326325c19 | ||
|
|
59e195fa94 | ||
|
|
5ed4f8795a | ||
|
|
a02e7e66af | ||
|
|
9c1d093bb8 | ||
|
|
302877d4b4 | ||
|
|
871bb29dbe | ||
|
|
3be144f644 | ||
|
|
2560dc23fc | ||
|
|
3d6c3d10bf | ||
|
|
5f811693f1 | ||
|
|
0068fb92eb | ||
|
|
71174ead45 | ||
|
|
43c18caceb | ||
|
|
7b4c9c3154 | ||
|
|
9d2649433d | ||
|
|
6353fa7dd3 | ||
|
|
bfa837c88d | ||
|
|
bdc454e7e5 | ||
|
|
9b3e85754c | ||
|
|
af3dcc5f31 | ||
|
|
c375fb9eaf | ||
|
|
70c314229f | ||
|
|
66e23ead00 | ||
|
|
2e5666c2b6 | ||
|
|
7675656a54 | ||
|
|
3d7f7b0ad1 | ||
|
|
a1ccbd6cab | ||
|
|
33cb4ce63c | ||
|
|
aefb4497e2 | ||
|
|
0047e66f10 | ||
|
|
6bae4254af | ||
|
|
a9689993b0 | ||
|
|
8c0182b29f | ||
|
|
02d55c7143 | ||
|
|
f343b537f1 | ||
|
|
a84e7da65f | ||
|
|
f3b64387cb | ||
|
|
e132435ff8 | ||
|
|
fb492fa6f7 | ||
|
|
159714491d | ||
|
|
64cbbc70bb | ||
|
|
d0559f627e | ||
|
|
0a6784e270 | ||
|
|
b6fd3c7e98 | ||
|
|
1c05534071 | ||
|
|
3627d5bb40 | ||
|
|
1f7714c1e9 | ||
|
|
d868ebf4a1 | ||
|
|
4640379b06 | ||
|
|
922120efbb | ||
|
|
91fec99b00 | ||
|
|
2f89c1fe1f | ||
|
|
2298f39cca | ||
|
|
23d68bc27b | ||
|
|
74c0423f0d | ||
|
|
06f87c4d8e | ||
|
|
35262de828 | ||
|
|
34a495984c | ||
|
|
d7130c4e28 | ||
|
|
c9630a53c3 | ||
|
|
0522690472 | ||
|
|
a8a0fbed32 | ||
|
|
26ae9585f6 | ||
|
|
b69bd699c8 | ||
|
|
650ee5f63a | ||
|
|
e3c60cbb77 | ||
|
|
0daca43d10 | ||
|
|
d3c239dc54 | ||
|
|
05ac2441da | ||
|
|
c0009b1b64 | ||
|
|
82960824ef | ||
|
|
64b63cbd0f | ||
|
|
05771fb07f | ||
|
|
d1f680dacd | ||
|
|
14c9397b70 | ||
|
|
eb5e724899 | ||
|
|
80619bd800 | ||
|
|
8e2cdfc607 | ||
|
|
58f416b7b2 | ||
|
|
b6cf39e3e5 | ||
|
|
41bc98ab64 | ||
|
|
b007ac315a | ||
|
|
940f6b8f72 | ||
|
|
f9690b74a5 | ||
|
|
f2c58d545a | ||
|
|
4732426629 | ||
|
|
d4af4f67b2 | ||
|
|
6420e8b6c6 | ||
|
|
15c59fa31f | ||
|
|
294131b48f | ||
|
|
48413abaa1 | ||
|
|
d36b2b935b | ||
|
|
f493749689 | ||
|
|
9de10bc885 | ||
|
|
2d5446686a |
@@ -1,73 +0,0 @@
|
||||
defaults: &defaults
|
||||
machine: true
|
||||
environment:
|
||||
IMPORT_PATH: "github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME"
|
||||
OUTPUT_DIR: "./build"
|
||||
DIST_DIR: "./dist"
|
||||
|
||||
install_golang: &install_golang
|
||||
run:
|
||||
name: install Golang 1.11
|
||||
command: |
|
||||
sudo add-apt-repository ppa:gophers/archive
|
||||
sudo apt-get update
|
||||
sudo apt-get install golang-1.11-go
|
||||
alias go="/usr/lib/go-1.11/bin/go"
|
||||
go version
|
||||
|
||||
install_deps: &install_deps
|
||||
run:
|
||||
name: Install deps
|
||||
command: |
|
||||
/usr/lib/go-1.11/bin/go mod vendor
|
||||
/usr/lib/go-1.11/bin/go get -u github.com/gobuffalo/packr/packr
|
||||
|
||||
install_httpie: &install_httpie
|
||||
run:
|
||||
name: install httpie
|
||||
command: |
|
||||
sudo apt-get -y update && sudo apt-get -y install httpie
|
||||
|
||||
install_jq: &install_jq
|
||||
run:
|
||||
name: install jq
|
||||
command: |
|
||||
sudo apt-get update && sudo apt-get -y install jq
|
||||
|
||||
build_binary: &build_binary
|
||||
run:
|
||||
name: build binary
|
||||
command: |
|
||||
/usr/lib/go-1.11/bin/go build -o ${OUTPUT_DIR}/fx fx.go
|
||||
|
||||
unit_test: &unit_test
|
||||
run:
|
||||
name: unit test
|
||||
command: |
|
||||
make unit-test
|
||||
bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}
|
||||
cli_test: &cli_test
|
||||
run:
|
||||
name: cli test
|
||||
command: make cli-test
|
||||
|
||||
version: 2
|
||||
jobs:
|
||||
test:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- *install_golang
|
||||
- *install_deps
|
||||
- *unit_test
|
||||
- *build_binary
|
||||
- run:
|
||||
name: Pull images
|
||||
command: make pull
|
||||
- *cli_test
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
workflow:
|
||||
jobs:
|
||||
- test
|
||||
57
.github/workflows/ci.yml
vendored
57
.github/workflows/ci.yml
vendored
@@ -13,27 +13,24 @@ jobs:
|
||||
- name: check out
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: setup docker
|
||||
- name: kind create a k8s cluster
|
||||
run: |
|
||||
./scripts/provision.sh
|
||||
kind create cluster
|
||||
|
||||
- name: setup k8s and kind
|
||||
- name: lint
|
||||
run: |
|
||||
export GOBIN=$(go env GOPATH)/bin
|
||||
export PATH=$PATH:$GOBIN
|
||||
mkdir -p $GOBIN
|
||||
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
|
||||
chmod +x kubectl && mv kubectl $GOBIN
|
||||
wget https://github.com/kubernetes-sigs/kind/releases/download/v0.5.0/kind-linux-amd64 && chmod +x kind-linux-amd64 && mv kind-linux-amd64 $GOBIN/kind
|
||||
./scripts/setup_kind.sh
|
||||
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint \
|
||||
golangci-lint run -v
|
||||
|
||||
- name: unit test
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: |
|
||||
export KUBECONFIG=/home/runner/.kube/kind-config-fx-test
|
||||
DEBUG=true go test -v ./container_runtimes/... ./deploy/...
|
||||
export KUBECONFIG="$(kind get kubeconfig-path)"
|
||||
make unit-test
|
||||
bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}
|
||||
|
||||
- name: build fx
|
||||
run: |
|
||||
@@ -46,34 +43,36 @@ jobs:
|
||||
make docker-build
|
||||
make test
|
||||
# make docker-publish #TODO in release workflow
|
||||
|
||||
- name: lint
|
||||
- name: test fx docker cloud
|
||||
run: |
|
||||
export GOBIN=$(go env GOPATH)/bin
|
||||
export PATH=$PATH:$GOBIN
|
||||
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
golangci-lint run
|
||||
|
||||
make start_docker_infra
|
||||
make test_docker_infra
|
||||
make stop_docker_infra
|
||||
|
||||
- name: test fx cli
|
||||
env:
|
||||
REMOTE_HOST_ADDR: ${{secrets.DOCKER_REMOTE_HOST_ADDR}}
|
||||
REMOTE_HOST_USER: ${{secrets.DOCKER_REMOTE_HOST_USER}}
|
||||
REMOTE_HOST_PASSWORD: ${{secrets.DOCKER_REMOTE_HOST_PASSWORD}}
|
||||
run: |
|
||||
echo $KUBECONFIG
|
||||
unset KUBECONFIG
|
||||
make cli-test
|
||||
make cli-test-ci
|
||||
|
||||
- name: test AKS
|
||||
env:
|
||||
AKS_KUBECONFIG: ${{ secrets.AKS_KUBECONFIG }}
|
||||
run: |
|
||||
export KUBECONFIG=${HOME}/.kube/aks
|
||||
echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
|
||||
if [[ -z "$AKS_KUBECONFIG" ]];then
|
||||
echo "skip deploy test since no valid KUBECONFIG"
|
||||
else
|
||||
DEBUG=true ./build/fx up -n hello -p 12345 examples/functions/JavaScript/func.js
|
||||
./build/fx down hello
|
||||
rm ${KUBECONFIG}
|
||||
fi
|
||||
echo "skip since aks environment not ready yet"
|
||||
# export KUBECONFIG=${HOME}/.kube/aks
|
||||
# echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
|
||||
# if [[ -z "$AKS_KUBECONFIG" ]];then
|
||||
# echo "skip deploy test since no valid KUBECONFIG"
|
||||
# else
|
||||
# DEBUG=true ./build/fx up -n hello -p 12345 examples/functions/JavaScript/func.js
|
||||
# ./build/fx down hello
|
||||
# rm ${KUBECONFIG}
|
||||
# fi
|
||||
|
||||
Installation:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
18
.github/workflows/docker.yml
vendored
18
.github/workflows/docker.yml
vendored
@@ -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()
|
||||
|
||||
38
.github/workflows/release.yml
vendored
38
.github/workflows/release.yml
vendored
@@ -18,40 +18,27 @@ jobs:
|
||||
- name: check out
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: setup docker
|
||||
- name: kind create a k8s cluster
|
||||
run: |
|
||||
./scripts/provision.sh
|
||||
kind create cluster
|
||||
|
||||
- name: setup k8s and kind
|
||||
- name: lint
|
||||
run: |
|
||||
export GOBIN=$(go env GOPATH)/bin
|
||||
export PATH=$PATH:$GOBIN
|
||||
mkdir -p $GOBIN
|
||||
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
|
||||
chmod +x kubectl && mv kubectl $GOBIN
|
||||
wget https://github.com/kubernetes-sigs/kind/releases/download/v0.5.0/kind-linux-amd64 && chmod +x kind-linux-amd64 && mv kind-linux-amd64 $GOBIN/kind
|
||||
./scripts/setup_kind.sh
|
||||
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint \
|
||||
golangci-lint run -v
|
||||
|
||||
- name: unit test
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
run: |
|
||||
export KUBECONFIG=/home/runner/.kube/kind-config-fx-test
|
||||
DEBUG=true go test -v ./container_runtimes/... ./deploy/...
|
||||
export KUBECONFIG="$(kind get kubeconfig-path)"
|
||||
make unit-test
|
||||
|
||||
- name: build fx
|
||||
run: |
|
||||
make build
|
||||
|
||||
- name: lint
|
||||
run: |
|
||||
export GOBIN=$(go env GOPATH)/bin
|
||||
export PATH=$PATH:$GOBIN
|
||||
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
golangci-lint run
|
||||
|
||||
|
||||
- name: test fx cli
|
||||
run: |
|
||||
echo $KUBECONFIG
|
||||
@@ -64,11 +51,12 @@ jobs:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: |
|
||||
export KUBECONFIG=${HOME}/.kube/aks
|
||||
echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
|
||||
DEBUG=true ./build/fx up -n hello -p 12345 examples/functions/JavaScript/func.js
|
||||
./build/fx down hello
|
||||
rm ${KUBECONFIG}
|
||||
echo "skip since aks environment not ready yet"
|
||||
# export KUBECONFIG=${HOME}/.kube/aks
|
||||
# echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
|
||||
# DEBUG=true ./build/fx up -n hello -p 12345 examples/functions/JavaScript/func.js
|
||||
# ./build/fx down hello
|
||||
# rm ${KUBECONFIG}
|
||||
Release:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [Test]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
run:
|
||||
deadline: 10m
|
||||
timeout: 10m
|
||||
deadline: 20m
|
||||
timeout: 20m
|
||||
issues-exit-code: 1
|
||||
tests: true
|
||||
skip-dirs:
|
||||
|
||||
@@ -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
|
||||
|
||||
42
Makefile
42
Makefile
@@ -1,5 +1,7 @@
|
||||
OUTPUT_DIR=./build
|
||||
DIST_DIR=./dist
|
||||
OUTPUT_DIR ?=./build
|
||||
DIST_DIR ?=./dist
|
||||
DOCKER_REMOTE_HOST_ADDR ?= "127.0.0.1"
|
||||
DOCKER_REMOTE_HOST_USER ?= $(whoami)
|
||||
|
||||
lint:
|
||||
golangci-lint run
|
||||
@@ -7,8 +9,11 @@ lint:
|
||||
generate:
|
||||
packr
|
||||
|
||||
b:
|
||||
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
|
||||
@@ -21,10 +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
|
||||
./scripts/test_cli.sh 'js rb py go php java d rs pl'
|
||||
|
||||
http-test:
|
||||
./scripts/http_test.sh
|
||||
@@ -32,3 +40,27 @@ http-test:
|
||||
zip:
|
||||
zip -r images.zip images/
|
||||
.PHONY: test build start list clean generate
|
||||
|
||||
start_docker_infra:
|
||||
docker build -t fx-docker-infra -f test/Dockerfile ./test
|
||||
docker run --rm --name fx-docker-infra -p 2222:22 -v /var/run/docker.sock:/var/run/docker.sock -d fx-docker-infra
|
||||
|
||||
test_docker_infra:
|
||||
CICD=true SSH_PORT=2222 SSH_KEY_FILE=./test/id_rsa ./build/fx infra create --name docker-local -t docker --host root@127.0.0.1
|
||||
|
||||
stop_docker_infra:
|
||||
docker stop fx-docker-infra
|
||||
|
||||
start_k3s_infra:
|
||||
multipass launch --name k3s-master --cpus 1 --mem 512M --disk 3G --cloud-init ./test/k3s/ssh-cloud-init.yaml
|
||||
multipass launch --name k3s-worker1 --cpus 1 --mem 512M --disk 3G --cloud-init ./test/k3s/ssh-cloud-init.yaml
|
||||
multipass launch --name k3s-worker2 --cpus 1 --mem 512M --disk 3G --cloud-init ./test/k3s/ssh-cloud-init.yaml
|
||||
|
||||
test_k3s_infra:
|
||||
./scripts/test_k3s_infra.sh
|
||||
|
||||
stop_k3s_infra:
|
||||
multipass delete k3s-master
|
||||
multipass delete k3s-worker1
|
||||
multipass delete k3s-worker2
|
||||
multipass purge
|
||||
|
||||
185
README.md
185
README.md
@@ -1,10 +1,11 @@
|
||||
fx
|
||||
------
|
||||
|
||||
Poor man's function as a service.
|
||||
<br/>
|
||||

|
||||

|
||||
[](https://codecov.io/gh/metrue/fx)
|
||||

|
||||

|
||||
[](https://codecov.io/gh/metrue/fx)
|
||||
[](https://goreportcard.com/report/github.com/metrue/fx)
|
||||
[](http://godoc.org/github.com/metrue/fx)
|
||||

|
||||
@@ -14,12 +15,13 @@ Poor man's function as a service.
|
||||
- [Introduction](#introduction)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Manage Infrastructure](#manage-infrastructure)
|
||||
- [Contribute](#contribute)
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
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. The most exciting thing is that you can write your functions with most programming languages.
|
||||
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.
|
||||
|
||||
@@ -35,10 +37,13 @@ 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
|
||||
|
||||
Binaries are available for Windows, MacOS and Linux/Unix on x86. For other architectures and platforms, follow instructions to [build fx from source](#buildtest).
|
||||
|
||||
* MacOS
|
||||
|
||||
```
|
||||
@@ -58,9 +63,9 @@ curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh |
|
||||
curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | sudo bash
|
||||
```
|
||||
|
||||
fx will be installed into /usr/local/bin, sometimes you may need `source ~/.zshrc` or `source ~/.bashrc` to make fx available in `$PAHT`.
|
||||
fx will be installed into /usr/local/bin, sometimes you may need `source ~/.zshrc` or `source ~/.bashrc` to make fx available in `$PATH`.
|
||||
|
||||
* Window
|
||||
* Windows
|
||||
|
||||
You can go the release page to [download](https://github.com/metrue/fx/releases) fx manually;
|
||||
|
||||
@@ -76,16 +81,16 @@ USAGE:
|
||||
fx [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
0.6.0
|
||||
0.8.7
|
||||
|
||||
COMMANDS:
|
||||
infra manage infrastructure of fx
|
||||
image manage image of service
|
||||
doctor health check for fx
|
||||
up deploy a function or a group of functions
|
||||
infra manage infrastructure
|
||||
up deploy a function
|
||||
down destroy a service
|
||||
list, ls list deployed services
|
||||
call run a function instantly
|
||||
image manage image of service
|
||||
doctor health check for fx
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
@@ -93,99 +98,36 @@ GLOBAL OPTIONS:
|
||||
--version, -v print the version
|
||||
```
|
||||
|
||||
1. List your current machines and activate you machine
|
||||
|
||||
```shell
|
||||
$ fx infra ls # list machines
|
||||
|
||||
{
|
||||
"localhost": {
|
||||
"Host": "localhost",
|
||||
"User": "",
|
||||
"Password": "",
|
||||
"Enabled": true,
|
||||
"Provisioned": false
|
||||
}
|
||||
}
|
||||
|
||||
$ fx infra activate localhost # activate 'localhost'
|
||||
|
||||
2019/08/10 13:21:20 info Provision:pull python Docker base iamge: ✓
|
||||
2019/08/10 13:21:21 info Provision:pull d Docker base image: ✓
|
||||
2019/08/10 13:21:23 info Provision:pull java Docker base image: ✓
|
||||
2019/08/10 13:21:28 info Provision:pull julia Docker base image: ✓
|
||||
2019/08/10 13:21:31 info Provision:pull node Docker base image: ✓
|
||||
2019/08/10 13:22:09 info Provision:pull go Docker base image: ✓
|
||||
2019/08/10 13:22:09 info provision machine localhost: ✓
|
||||
2019/08/10 13:22:09 info enble machine localhost: ✓
|
||||
```
|
||||
It may take seconds since `fx` needs to download some basic resources
|
||||
|
||||
*Note* you can add a remote host as fx machine also,
|
||||
```
|
||||
$ fx infra add --name my_aws_vm --host 13.121.202.227 --user root --password yourpassword
|
||||
|
||||
$ fx infra list
|
||||
{
|
||||
"my_aws_vm": {
|
||||
"Host": "13.121.202.227",
|
||||
"User": "root",
|
||||
"Password": "yourpassword",
|
||||
"Enabled": false,
|
||||
"Provisioned": false
|
||||
},
|
||||
"localhost": {
|
||||
"Host": "localhost",
|
||||
"User": "",
|
||||
"Password": "",
|
||||
"Enabled": true,
|
||||
"Provisioned": true
|
||||
}
|
||||
}
|
||||
|
||||
$ fx infra activate my_aws_vm
|
||||
### Deploy your function to Docker
|
||||
|
||||
```
|
||||
then your function will be deployed onto remote host also.
|
||||
$ fx up --name hello-fx ./examples/functions/JavaScript/func.js
|
||||
|
||||
2. Write a function
|
||||
|
||||
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`.
|
||||
|
||||
3. 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 |
|
||||
+------------------------+-------------+----------------+
|
||||
```
|
||||
|
||||
4. 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
|
||||
@@ -207,34 +149,32 @@ hello world
|
||||
|
||||
```
|
||||
|
||||
## Docker
|
||||
## Manage Infrastructure
|
||||
|
||||
TODO
|
||||
**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.
|
||||
|
||||
## Kubernetes
|
||||
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** 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.
|
||||
### `fx infra create`
|
||||
|
||||
You can create types (docker and k8s) of infrastructures for **fx** to deploy functions
|
||||
|
||||
```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 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
|
||||
```
|
||||
|
||||
or
|
||||
### `fx infra use`
|
||||
|
||||
To use a infrastructure, you can use `fx infra use` command to activate it.
|
||||
|
||||
```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
|
||||
fx infra use <infrastructure name>
|
||||
```
|
||||
|
||||
* Local Kubernetes Cluster
|
||||
and you can list your infrastructure with `fx infra list`
|
||||
|
||||
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)
|
||||
|
||||
@@ -266,8 +206,32 @@ 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
|
||||
|
||||
```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'
|
||||
```
|
||||
|
||||
## Contribute
|
||||
|
||||
@@ -278,6 +242,7 @@ fx uses [Project](https://github.com/metrue/fx/projects/4) to manage the develop
|
||||
Docker: make sure [Docker](https://docs.docker.com/engine/installation/) installed and running on your server.
|
||||
|
||||
|
||||
<a name="buildtest"></a>
|
||||
#### Build & Test
|
||||
|
||||
```
|
||||
|
||||
4
assets/dockerfiles/base/go/Dockerfile
vendored
Normal file
4
assets/dockerfiles/base/go/Dockerfile
vendored
Normal 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
26
assets/dockerfiles/base/node/package-lock.json
generated
vendored
Normal 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="
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/dockerfiles/base/node/package.json
vendored
9
assets/dockerfiles/base/node/package.json
vendored
@@ -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
10
assets/dockerfiles/base/perl/Dockerfile
vendored
Normal 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
3
assets/dockerfiles/base/perl/cpanfile
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
requires "EV";
|
||||
requires "JSON";
|
||||
requires "Mojolicious::Lite";
|
||||
3
assets/dockerfiles/base/ruby/Dockerfile
vendored
Normal file
3
assets/dockerfiles/base/ruby/Dockerfile
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM ruby:latest
|
||||
|
||||
RUN gem install sinatra
|
||||
44
bundle/bundle.go
Normal file
44
bundle/bundle.go
Normal 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
117
bundle/bundler_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
88
bundler/bundler.go
Normal file
88
bundler/bundler.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package bundler
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gobuffalo/packd"
|
||||
"github.com/gobuffalo/packr/v2"
|
||||
"github.com/metrue/fx/constants"
|
||||
"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 {
|
||||
basename := filepath.Base(name)
|
||||
nameWithoutExt := strings.TrimSuffix(basename, filepath.Ext(basename))
|
||||
if constants.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
51
bundler/bundler_test.go
Normal 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
32
bundler/d/d.go
Normal 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{}
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM metrue/fx-go-base:latest
|
||||
FROM metrue/fx-go-base
|
||||
|
||||
COPY . /go/src/github.com/metrue/fx
|
||||
WORKDIR /go/src/github.com/metrue/fx
|
||||
32
bundler/go/go.go
Normal file
32
bundler/go/go.go
Normal 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
143
bundler/go/go_test.go
Normal 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
32
bundler/java/java.go
Normal 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
155
bundler/java/java_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
struct Input
|
||||
a::Number
|
||||
b::Number
|
||||
32
bundler/julia/julia.go
Normal file
32
bundler/julia/julia.go
Normal 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
148
bundler/julia/julia_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
13
bundler/node/assets/app.js
Normal file
13
bundler/node/assets/app.js
Normal 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
34
bundler/node/node.go
Normal 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
143
bundler/node/node_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
6
bundler/perl/assets/Dockerfile
Normal file
6
bundler/perl/assets/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM metrue/fx-perl-base
|
||||
|
||||
ADD . .
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["perl", "app.pl", "daemon"]
|
||||
17
bundler/perl/assets/app.pl
Normal file
17
bundler/perl/assets/app.pl
Normal 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;
|
||||
6
bundler/perl/assets/fx.pl
Normal file
6
bundler/perl/assets/fx.pl
Normal file
@@ -0,0 +1,6 @@
|
||||
sub fx {
|
||||
my $ctx = shift;
|
||||
return 'hello fx'
|
||||
}
|
||||
|
||||
1;
|
||||
32
bundler/perl/perl.go
Normal file
32
bundler/perl/perl.go
Normal 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
148
bundler/perl/perl_test.go
Normal 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
32
bundler/python/python.go
Normal 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{}
|
||||
)
|
||||
148
bundler/python/python_test.go
Normal file
148
bundler/python/python_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
32
bundler/ruby/ruby.go
Normal 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
148
bundler/ruby/ruby_test.go
Normal 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
32
bundler/rust/rust.go
Normal 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
148
bundler/rust/rust_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
304
config/config.go
304
config/config.go
@@ -1,213 +1,207 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// Configer interface
|
||||
// Configer manage fx config
|
||||
type Configer interface {
|
||||
GetMachine(name string) (Host, error)
|
||||
AddMachine(name string, host Host) error
|
||||
RemoveHost(name string) error
|
||||
ListActiveMachines() (map[string]Host, error)
|
||||
ListMachines() (map[string]Host, error)
|
||||
EnableMachine(name string) error
|
||||
DisableMachine(name string) error
|
||||
UpdateProvisionedStatus(name string, ok bool) error
|
||||
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 {
|
||||
dir string
|
||||
configFile string
|
||||
container *Container
|
||||
}
|
||||
|
||||
// New create a config
|
||||
func New(dir string) *Config {
|
||||
return &Config{dir: dir}
|
||||
const defaultFxConfig = "~/.fx/config.yml"
|
||||
|
||||
// LoadDefault load default config
|
||||
func LoadDefault() (*Config, error) {
|
||||
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
|
||||
}
|
||||
}
|
||||
return load(configFile)
|
||||
}
|
||||
|
||||
// Init config
|
||||
func (c *Config) Init() error {
|
||||
if err := os.MkdirAll(c.dir, os.ModePerm); err != nil {
|
||||
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 config, nil
|
||||
}
|
||||
|
||||
// Load config
|
||||
func Load(configFile string) (*Config, error) {
|
||||
if configFile == "" {
|
||||
return nil, fmt.Errorf("invalid config file")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||
if err := utils.EnsureFile(configFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return load(configFile)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
ext := "yaml"
|
||||
name := "config"
|
||||
viper.SetConfigType(ext)
|
||||
viper.SetConfigName(name)
|
||||
viper.AddConfigPath(c.dir)
|
||||
cloudType, ok := cloudMeta["type"].(string)
|
||||
if !ok || cloudType == "" {
|
||||
return fmt.Errorf("unknown cloud type")
|
||||
}
|
||||
|
||||
// detect if file exists
|
||||
configFilePath := path.Join(c.dir, name+"."+ext)
|
||||
if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
|
||||
fd, err := os.Create(configFilePath)
|
||||
if err != nil {
|
||||
if cloudType == types.CloudTypeK8S {
|
||||
dir := path.Dir(c.configFile)
|
||||
kubecfg := path.Join(dir, name+".kubeconfig")
|
||||
if err := utils.EnsureFile(kubecfg); err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
|
||||
localhost := Host{
|
||||
Host: "localhost",
|
||||
Password: "",
|
||||
User: "",
|
||||
Enabled: true,
|
||||
Provisioned: false,
|
||||
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
|
||||
}
|
||||
viper.Set("hosts", map[string]Host{"localhost": localhost})
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return fmt.Errorf("fatal error config file: %s", err)
|
||||
if err := c.container.set("clouds."+name, cloudMeta); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMachine get host by name
|
||||
func (c *Config) GetMachine(name string) (Host, error) {
|
||||
var hosts map[string]Host
|
||||
if err := viper.UnmarshalKey("hosts", &hosts); err != nil {
|
||||
return Host{}, err
|
||||
// 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")
|
||||
}
|
||||
host, ok := hosts[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) {
|
||||
return ioutil.ReadFile(c.configFile)
|
||||
}
|
||||
|
||||
// GetCurrentCloud get current using cloud's meta
|
||||
func (c *Config) GetCurrentCloud() ([]byte, error) {
|
||||
name, ok := c.container.get("current_cloud").(string)
|
||||
if !ok {
|
||||
return Host{}, fmt.Errorf("no such host %v", name)
|
||||
return nil, fmt.Errorf("no active cloud")
|
||||
}
|
||||
return host, nil
|
||||
meta := c.container.get("clouds." + name)
|
||||
if meta == nil {
|
||||
return nil, fmt.Errorf("invalid config")
|
||||
}
|
||||
return json.Marshal(meta)
|
||||
}
|
||||
|
||||
// ListActiveMachines list enabled machines
|
||||
func (c *Config) ListActiveMachines() (map[string]Host, error) {
|
||||
hosts, err := c.ListMachines()
|
||||
if err != nil {
|
||||
return map[string]Host{}, 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")
|
||||
}
|
||||
lst := map[string]Host{}
|
||||
for name, h := range hosts {
|
||||
if h.Enabled {
|
||||
lst[name] = h
|
||||
}
|
||||
}
|
||||
return lst, nil
|
||||
return c.container.get("clouds." + name + ".type").(string), nil
|
||||
}
|
||||
|
||||
// AddMachine add host
|
||||
func (c *Config) AddMachine(name string, host Host) error {
|
||||
if !viper.IsSet("hosts") {
|
||||
viper.Set("hosts", map[string]Host{})
|
||||
// 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")
|
||||
}
|
||||
|
||||
hosts, err := c.ListMachines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hosts[name] = host
|
||||
viper.Set("hosts", hosts)
|
||||
return viper.WriteConfig()
|
||||
dir := path.Dir(c.configFile)
|
||||
kubecfg := path.Join(dir, name+".kubeconfig")
|
||||
return kubecfg, nil
|
||||
}
|
||||
|
||||
// RemoveHost remote a host
|
||||
func (c *Config) RemoveHost(name string) error {
|
||||
hosts, err := c.ListMachines()
|
||||
func (c *Config) writeDefaultConfig() error {
|
||||
me, err := user.Current()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(hosts) == 1 {
|
||||
return fmt.Errorf("only one host left now, at least one host required by fx")
|
||||
defaultCloud := &dockerInfra.Cloud{
|
||||
IP: "127.0.0.1",
|
||||
User: me.Username,
|
||||
Name: "default",
|
||||
Type: types.CloudTypeDocker,
|
||||
}
|
||||
|
||||
if _, ok := hosts[name]; ok {
|
||||
delete(hosts, name)
|
||||
|
||||
viper.Set("hosts", hosts)
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
return fmt.Errorf("no such host %s", name)
|
||||
}
|
||||
|
||||
// ListMachines list hosts
|
||||
func (c *Config) ListMachines() (map[string]Host, error) {
|
||||
var hosts map[string]Host
|
||||
if err := viper.UnmarshalKey("hosts", &hosts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
// EnableMachine enable a machine, after machine enabled, function will be deployed onto it when ever `fx up` invoked
|
||||
func (c *Config) EnableMachine(name string) error {
|
||||
host, err := c.GetMachine(name)
|
||||
meta, err := defaultCloud.Dump()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
host.Enabled = true
|
||||
|
||||
if !viper.IsSet("hosts") {
|
||||
viper.Set("hosts", map[string]Host{})
|
||||
}
|
||||
|
||||
hosts, err := c.ListMachines()
|
||||
if err != nil {
|
||||
if err := c.container.set("clouds", map[string]interface{}{}); err != nil {
|
||||
return err
|
||||
}
|
||||
hosts[name] = host
|
||||
viper.Set("hosts", hosts)
|
||||
return viper.WriteConfig()
|
||||
if err := c.AddCloud("default", meta); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.UseCloud("default")
|
||||
}
|
||||
|
||||
// DisableMachine disable a machine, after machine disabled, function will not be deployed onto it
|
||||
func (c *Config) DisableMachine(name string) error {
|
||||
host, err := c.GetMachine(name)
|
||||
// Dir get directory of config
|
||||
func (c *Config) Dir() (string, error) {
|
||||
p, err := filepath.Abs(c.configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
host.Enabled = false
|
||||
|
||||
if !viper.IsSet("hosts") {
|
||||
viper.Set("hosts", map[string]Host{})
|
||||
}
|
||||
|
||||
hosts, err := c.ListMachines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hosts[name] = host
|
||||
viper.Set("hosts", hosts)
|
||||
return viper.WriteConfig()
|
||||
return path.Dir(p), nil
|
||||
}
|
||||
|
||||
// UpdateProvisionedStatus update provisioned status
|
||||
func (c *Config) UpdateProvisionedStatus(name string, ok bool) error {
|
||||
host, err := c.GetMachine(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
host.Provisioned = ok
|
||||
|
||||
if !viper.IsSet("hosts") {
|
||||
viper.Set("hosts", map[string]Host{})
|
||||
}
|
||||
|
||||
hosts, err := c.ListMachines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hosts[name] = host
|
||||
viper.Set("hosts", hosts)
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
// IsMachineProvisioned check if machine provisioned
|
||||
func (c *Config) IsMachineProvisioned(name string) bool {
|
||||
host, err := c.GetMachine(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return host.Provisioned
|
||||
}
|
||||
var (
|
||||
_ Configer = &Config{}
|
||||
)
|
||||
|
||||
@@ -1,98 +1,131 @@
|
||||
package config
|
||||
|
||||
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/.fx"
|
||||
configPath := "./tmp/config.yml"
|
||||
defer func() {
|
||||
if err := os.RemoveAll(configPath); err != nil {
|
||||
if err := os.RemoveAll("./tmp/config.yml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
c := New(configPath)
|
||||
if err := c.Init(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hosts, err := c.ListMachines()
|
||||
// default cloud
|
||||
c, err := Load(configPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(hosts) != 1 {
|
||||
t.Fatalf("should have localhost as default machine")
|
||||
}
|
||||
|
||||
host := hosts["localhost"]
|
||||
if !reflect.DeepEqual(host, Host{Host: "localhost", Enabled: true}) {
|
||||
t.Fatalf("should get %v but got %v", Host{Host: "localhost"}, host)
|
||||
}
|
||||
|
||||
name := "remote-a"
|
||||
h := Host{
|
||||
Host: "192.168.1.1",
|
||||
User: "user-a",
|
||||
Password: "password-a",
|
||||
Enabled: false,
|
||||
}
|
||||
if err := c.AddMachine(name, h); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hosts, err = c.ListMachines()
|
||||
defaultMeta, err := c.GetCurrentCloud()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(hosts) != 2 {
|
||||
t.Fatalf("should have %d machines now, but got %d", 2, len(hosts))
|
||||
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"])
|
||||
}
|
||||
|
||||
lst, err := c.ListActiveMachines()
|
||||
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)
|
||||
}
|
||||
|
||||
if len(lst) != 1 {
|
||||
t.Fatalf("should only have %d machine enabled, but got %d", 1, len(lst))
|
||||
}
|
||||
kName := "k8s-1"
|
||||
kubeconf := "./tmp/" + kName + "config.yml"
|
||||
defer func() {
|
||||
if err := os.RemoveAll(kubeconf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := c.EnableMachine(name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
lst, err = c.ListActiveMachines()
|
||||
// add k8s cloud
|
||||
kCloud := k8sInfra.NewCloud(kubeconf, n1, n2)
|
||||
kMeta, err := kCloud.Dump()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(lst) != 2 {
|
||||
t.Fatalf("should only have %d machine enabled, but got %d", 2, len(lst))
|
||||
}
|
||||
|
||||
h.Enabled = true
|
||||
if !reflect.DeepEqual(lst[name], h) {
|
||||
t.Fatalf("should get %v but got %v", h, lst[name])
|
||||
}
|
||||
|
||||
if lst[name].Provisioned != false {
|
||||
t.Fatalf("should get %v but got %v", false, lst[name].Provisioned)
|
||||
}
|
||||
|
||||
if err := c.UpdateProvisionedStatus(name, true); err != nil {
|
||||
if err := c.AddCloud(kName, kMeta); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
updatedHost, err := c.GetMachine(name)
|
||||
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 updatedHost.Provisioned != true {
|
||||
t.Fatalf("should get %v but got %v", true, updatedHost.Provisioned)
|
||||
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()
|
||||
if err != nil {
|
||||
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
73
config/container.go
Normal 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
84
config/container_test.go
Normal 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
14
config/env.go
Normal 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
17
config/env_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package config
|
||||
|
||||
// Host host entity
|
||||
type Host struct {
|
||||
Host string
|
||||
User string
|
||||
Password string
|
||||
Enabled bool
|
||||
Provisioned bool
|
||||
}
|
||||
|
||||
// NewHost new a host
|
||||
func NewHost(addr, user, password string) Host {
|
||||
return Host{
|
||||
Host: addr,
|
||||
User: user,
|
||||
Password: password,
|
||||
Enabled: false,
|
||||
Provisioned: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Valid if host is valid
|
||||
func (h Host) Valid() bool {
|
||||
// TODO stronger check
|
||||
return h.Host != ""
|
||||
}
|
||||
|
||||
// IsLocal if host is localhost
|
||||
func (h Host) IsLocal() bool {
|
||||
if !h.Valid() {
|
||||
return false
|
||||
}
|
||||
return h.Host == "127.0.0.1" || h.Host == "localhost"
|
||||
}
|
||||
|
||||
// IsRemote is host is remote
|
||||
func (h Host) IsRemote() bool {
|
||||
return !h.IsLocal()
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./config.go
|
||||
|
||||
// Package mock_config is a generated GoMock package.
|
||||
package mock_config
|
||||
|
||||
import (
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
config "github.com/metrue/fx/config"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockConfiger is a mock of Configer interface
|
||||
type MockConfiger struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockConfigerMockRecorder
|
||||
}
|
||||
|
||||
// MockConfigerMockRecorder is the mock recorder for MockConfiger
|
||||
type MockConfigerMockRecorder struct {
|
||||
mock *MockConfiger
|
||||
}
|
||||
|
||||
// NewMockConfiger creates a new mock instance
|
||||
func NewMockConfiger(ctrl *gomock.Controller) *MockConfiger {
|
||||
mock := &MockConfiger{ctrl: ctrl}
|
||||
mock.recorder = &MockConfigerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockConfiger) EXPECT() *MockConfigerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetMachine mocks base method
|
||||
func (m *MockConfiger) GetMachine(name string) (config.Host, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetMachine", name)
|
||||
ret0, _ := ret[0].(config.Host)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetMachine indicates an expected call of GetMachine
|
||||
func (mr *MockConfigerMockRecorder) GetMachine(name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMachine", reflect.TypeOf((*MockConfiger)(nil).GetMachine), name)
|
||||
}
|
||||
|
||||
// AddMachine mocks base method
|
||||
func (m *MockConfiger) AddMachine(name string, host config.Host) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddMachine", name, host)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddMachine indicates an expected call of AddMachine
|
||||
func (mr *MockConfigerMockRecorder) AddMachine(name, host interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMachine", reflect.TypeOf((*MockConfiger)(nil).AddMachine), name, host)
|
||||
}
|
||||
|
||||
// RemoveHost mocks base method
|
||||
func (m *MockConfiger) RemoveHost(name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RemoveHost", name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RemoveHost indicates an expected call of RemoveHost
|
||||
func (mr *MockConfigerMockRecorder) RemoveHost(name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveHost", reflect.TypeOf((*MockConfiger)(nil).RemoveHost), name)
|
||||
}
|
||||
|
||||
// ListActiveMachines mocks base method
|
||||
func (m *MockConfiger) ListActiveMachines() (map[string]config.Host, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ListActiveMachines")
|
||||
ret0, _ := ret[0].(map[string]config.Host)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ListActiveMachines indicates an expected call of ListActiveMachines
|
||||
func (mr *MockConfigerMockRecorder) ListActiveMachines() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListActiveMachines", reflect.TypeOf((*MockConfiger)(nil).ListActiveMachines))
|
||||
}
|
||||
|
||||
// ListMachines mocks base method
|
||||
func (m *MockConfiger) ListMachines() (map[string]config.Host, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ListMachines")
|
||||
ret0, _ := ret[0].(map[string]config.Host)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ListMachines indicates an expected call of ListMachines
|
||||
func (mr *MockConfigerMockRecorder) ListMachines() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMachines", reflect.TypeOf((*MockConfiger)(nil).ListMachines))
|
||||
}
|
||||
|
||||
// EnableMachine mocks base method
|
||||
func (m *MockConfiger) EnableMachine(name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EnableMachine", name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// EnableMachine indicates an expected call of EnableMachine
|
||||
func (mr *MockConfigerMockRecorder) EnableMachine(name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableMachine", reflect.TypeOf((*MockConfiger)(nil).EnableMachine), name)
|
||||
}
|
||||
|
||||
// DisableMachine mocks base method
|
||||
func (m *MockConfiger) DisableMachine(name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DisableMachine", name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DisableMachine indicates an expected call of DisableMachine
|
||||
func (mr *MockConfigerMockRecorder) DisableMachine(name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableMachine", reflect.TypeOf((*MockConfiger)(nil).DisableMachine), name)
|
||||
}
|
||||
|
||||
// UpdateProvisionedStatus mocks base method
|
||||
func (m *MockConfiger) UpdateProvisionedStatus(name string, ok bool) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateProvisionedStatus", name, ok)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateProvisionedStatus indicates an expected call of UpdateProvisionedStatus
|
||||
func (mr *MockConfigerMockRecorder) UpdateProvisionedStatus(name, ok interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateProvisionedStatus", reflect.TypeOf((*MockConfiger)(nil).UpdateProvisionedStatus), name, ok)
|
||||
}
|
||||
15
constants/languages.go
Normal file
15
constants/languages.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package constants
|
||||
|
||||
// ExtLangMapping file extension mapping with programming language
|
||||
var ExtLangMapping = map[string]string{
|
||||
".js": "node",
|
||||
".go": "go",
|
||||
".rb": "ruby",
|
||||
".py": "python",
|
||||
".php": "php",
|
||||
".jl": "julia",
|
||||
".java": "java",
|
||||
".d": "d",
|
||||
".rs": "rust",
|
||||
".pl": "perl",
|
||||
}
|
||||
@@ -1,19 +1,33 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
dockerTypesContainer "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"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"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// API interact with dockerd http api
|
||||
@@ -24,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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,8 +132,47 @@ func (api *API) post(path string, body []byte, expectStatus int, v interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// List list service
|
||||
func (api *API) list(name string) ([]types.Service, error) {
|
||||
// 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 != "" {
|
||||
info, err := api.inspect(name)
|
||||
if err != nil {
|
||||
@@ -141,7 +196,7 @@ func (api *API) list(name string) ([]types.Service, error) {
|
||||
}
|
||||
|
||||
type filterItem struct {
|
||||
Status []string `json:"url,omitempty"`
|
||||
Status []string `json:"status,omitempty"`
|
||||
Label []string `json:"label,omitempty"`
|
||||
Name []string `json:"name,omitempty"`
|
||||
}
|
||||
@@ -173,17 +228,27 @@ func (api *API) list(name string) ([]types.Service, error) {
|
||||
|
||||
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{}
|
||||
@@ -193,3 +258,223 @@ func (api *API) list(name string) ([]types.Service, error) {
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// BuildImage build image
|
||||
func (api *API) BuildImage(ctx context.Context, workdir string, name string) error {
|
||||
tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tarDir)
|
||||
|
||||
imageID := uuid.New().String()
|
||||
tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", imageID))
|
||||
|
||||
if err := utils.TarDir(workdir, tarFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dockerBuildContext, err := os.Open(tarFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dockerBuildContext.Close()
|
||||
|
||||
type buildQuery struct {
|
||||
Labels string `url:"labels,omitempty"`
|
||||
Tags string `url:"t,omitempty"`
|
||||
Dockerfile string `url:"dockerfile,omitempty"`
|
||||
}
|
||||
|
||||
// Apply default labels
|
||||
labelsJSON, _ := json.Marshal(map[string]string{
|
||||
"belong-to": "fx",
|
||||
})
|
||||
q := buildQuery{
|
||||
Labels: string(labelsJSON),
|
||||
Dockerfile: "Dockerfile",
|
||||
}
|
||||
|
||||
qs, err := query.Values(q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qs.Add("t", name)
|
||||
qs.Add("t", imageID)
|
||||
|
||||
path := "/build"
|
||||
url := fmt.Sprintf("%s%s?%s", api.endpoint, path, qs.Encode())
|
||||
req, err := http.NewRequest("POST", url, dockerBuildContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-tar")
|
||||
client := &http.Client{Timeout: 600 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
|
||||
for scanner.Scan() {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
log.Infof(scanner.Text())
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushImage push a image
|
||||
func (api *API) PushImage(ctx context.Context, name string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// InspectImage inspect image
|
||||
func (api *API) InspectImage(ctx context.Context, name string, image interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TagImage tag image
|
||||
func (api *API) TagImage(ctx context.Context, name string, tag string) error {
|
||||
query := url.Values{}
|
||||
query.Set("repo", name)
|
||||
query.Set("tag", tag)
|
||||
path := fmt.Sprintf("/images/%s/tag?%s", name, query.Encode())
|
||||
|
||||
url := fmt.Sprintf("%s%s", api.endpoint, path)
|
||||
req, err := http.NewRequest("POST", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
if _, err = client.Do(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartContainer start container
|
||||
func (api *API) StartContainer(ctx context.Context, name string, image string, bindings []types.PortBinding) error {
|
||||
networks, err := api.GetNetwork(fxNetworkName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get network failed: %s", err)
|
||||
}
|
||||
|
||||
if len(networks) == 0 {
|
||||
if err := api.CreateNetwork(fxNetworkName); err != nil {
|
||||
return errors.Wrapf(err, "error create network: %s", err)
|
||||
}
|
||||
}
|
||||
networks, _ = api.GetNetwork(fxNetworkName)
|
||||
|
||||
endpoint := &network.EndpointSettings{
|
||||
NetworkID: networks[0].ID,
|
||||
}
|
||||
networkConfig := &network.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||
"fx-net": endpoint,
|
||||
},
|
||||
}
|
||||
|
||||
portSet := nat.PortSet{}
|
||||
portMap := nat.PortMap{}
|
||||
for _, binding := range bindings {
|
||||
bindings := []nat.PortBinding{
|
||||
nat.PortBinding{
|
||||
HostIP: types.DefaultHost,
|
||||
HostPort: fmt.Sprintf("%d", binding.ServiceBindingPort),
|
||||
},
|
||||
}
|
||||
port := nat.Port(fmt.Sprintf("%d/tcp", binding.ContainerExposePort))
|
||||
portSet[port] = struct{}{}
|
||||
portMap[port] = bindings
|
||||
}
|
||||
config := &dockerTypesContainer.Config{
|
||||
Image: image,
|
||||
ExposedPorts: portSet,
|
||||
}
|
||||
|
||||
hostConfig := &dockerTypesContainer.HostConfig{
|
||||
AutoRemove: !fxConfig.DisableContainerAutoremove,
|
||||
PortBindings: portMap,
|
||||
}
|
||||
|
||||
req := ContainerCreateRequestPayload{
|
||||
Config: config,
|
||||
HostConfig: hostConfig,
|
||||
NetworkingConfig: networkConfig,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error mashal container create req")
|
||||
}
|
||||
|
||||
// create container
|
||||
path := fmt.Sprintf("/containers/create?name=%s", name)
|
||||
var createRes container.ContainerCreateCreatedBody
|
||||
if err := api.post(path, body, 201, &createRes); err != nil {
|
||||
return errors.Wrap(err, "create container request failed")
|
||||
}
|
||||
|
||||
if createRes.ID == "" {
|
||||
return fmt.Errorf("container id is missing")
|
||||
}
|
||||
|
||||
// start container
|
||||
path = fmt.Sprintf("/containers/%s/start", createRes.ID)
|
||||
url := fmt.Sprintf("%s%s", api.endpoint, path)
|
||||
request, err := http.NewRequest("POST", url, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error new container create request")
|
||||
}
|
||||
client := &http.Client{Timeout: 20 * time.Second}
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error do start container request")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(b) != 0 {
|
||||
msg := fmt.Sprintf("start container met issue: %s", string(b))
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
if _, err = api.inspect(createRes.ID); err != nil {
|
||||
msg := fmt.Sprintf("inspect container %s error", name)
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopContainer stop a container
|
||||
func (api *API) StopContainer(ctx context.Context, name string) error {
|
||||
return api.Stop(name)
|
||||
}
|
||||
|
||||
// InspectContainer inspect container
|
||||
func (api *API) InspectContainer(ctx context.Context, name string, container interface{}) error {
|
||||
path := fmt.Sprintf("/containers/%s/json", name)
|
||||
if err := api.get(path, "", &container); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ containerruntimes.ContainerRuntime = &API{}
|
||||
)
|
||||
|
||||
@@ -1,89 +1,31 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func TestDockerHTTP(t *testing.T) {
|
||||
host := config.Host{Host: "127.0.0.1"}
|
||||
api, err := Create(host.Host, constants.AgentPort)
|
||||
host := os.Getenv("DOCKER_ENGINE_HOST")
|
||||
port := os.Getenv("DOCKER_ENGINE_PORT")
|
||||
if host == "" ||
|
||||
port == "" {
|
||||
t.Skip("DOCKER_ENGINE_HOST and DOCKER_ENGINE_PORT required")
|
||||
}
|
||||
|
||||
api, err := Create(host, port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
serviceName := "a-test-service"
|
||||
project := types.Project{
|
||||
Name: serviceName,
|
||||
Language: "node",
|
||||
Files: []types.ProjectSourceFile{
|
||||
types.ProjectSourceFile{
|
||||
Path: "Dockerfile",
|
||||
Body: `
|
||||
FROM metrue/fx-node-base
|
||||
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["node", "app.js"]`,
|
||||
IsHandler: false,
|
||||
},
|
||||
types.ProjectSourceFile{
|
||||
Path: "app.js",
|
||||
Body: `
|
||||
const Koa = require('koa');
|
||||
const bodyParser = require('koa-bodyparser');
|
||||
const func = require('./fx');
|
||||
|
||||
const app = new Koa();
|
||||
app.use(bodyParser());
|
||||
app.use(ctx => {
|
||||
const msg = func(ctx.request.body);
|
||||
ctx.body = msg;
|
||||
});
|
||||
|
||||
app.listen(3000);`,
|
||||
IsHandler: false,
|
||||
},
|
||||
types.ProjectSourceFile{
|
||||
Path: "fx.js",
|
||||
Body: `
|
||||
module.exports = (input) => {
|
||||
return input.a + input.b
|
||||
}
|
||||
`,
|
||||
IsHandler: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
service, err := api.Build(project)
|
||||
if err != nil {
|
||||
name := "fx-agent"
|
||||
var container types.ContainerJSON
|
||||
if err := api.InspectContainer(context.Background(), name, &container); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if service.Name != serviceName {
|
||||
t.Fatalf("should get %s but got %s", serviceName, service.Name)
|
||||
}
|
||||
|
||||
if err := api.Run(9999, &service); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
services, err := api.list(serviceName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(services) != 1 {
|
||||
t.Fatal("service number should be 1")
|
||||
}
|
||||
|
||||
if err := api.Stop(serviceName); err != nil {
|
||||
t.Fatal(err)
|
||||
if container.Name != "/"+name {
|
||||
t.Fatalf("should get %s but got %s", name, container.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,51 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
// Call function directly with given params
|
||||
func (api *API) Call(file string, param string, project types.Project) error {
|
||||
service, err := api.Build(project)
|
||||
if err != nil {
|
||||
log.Fatalf("Build Service: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Build Service: \u2713")
|
||||
|
||||
if err := api.Run(9999, &service); err != nil {
|
||||
log.Fatalf("Run Service: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Run Service: \u2713")
|
||||
|
||||
params := utils.PairsToParams(strings.Fields(param))
|
||||
body, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait 2 seconds for service startup
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
url := fmt.Sprintf("http://%s:%d", service.Host, service.Port)
|
||||
r, err := http.NewRequest("POST", url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
client := &http.Client{Timeout: 20 * time.Second}
|
||||
resp, err := client.Do(r)
|
||||
if err != nil {
|
||||
log.Fatalf("Call Service: %v", err)
|
||||
return err
|
||||
}
|
||||
buf, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("Call Service: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Call Service: \u2713")
|
||||
return utils.OutputJSON(string(buf))
|
||||
return nil
|
||||
// service, err := api.Build(project)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Build Service: %v", err)
|
||||
// return err
|
||||
// }
|
||||
// log.Info("Build Service: \u2713")
|
||||
//
|
||||
// if err := api.Run(9999, &service); err != nil {
|
||||
// log.Fatalf("Run Service: %v", err)
|
||||
// return err
|
||||
// }
|
||||
// log.Info("Run Service: \u2713")
|
||||
//
|
||||
// params := utils.PairsToParams(strings.Fields(param))
|
||||
// body, err := json.Marshal(params)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // Wait 2 seconds for service startup
|
||||
// time.Sleep(time.Second * 2)
|
||||
//
|
||||
// url := fmt.Sprintf("http://%s:%d", service.Host, service.Port)
|
||||
// r, err := http.NewRequest("POST", url, bytes.NewReader(body))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// r.Header.Set("Content-Type", "application/json")
|
||||
// client := &http.Client{Timeout: 20 * time.Second}
|
||||
// resp, err := client.Do(r)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Call Service: %v", err)
|
||||
// return err
|
||||
// }
|
||||
// buf, err := ioutil.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Call Service: %v", err)
|
||||
// return err
|
||||
// }
|
||||
// log.Info("Call Service: \u2713")
|
||||
// return utils.OutputJSON(string(buf))
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
// List services
|
||||
func (api *API) List(name string) error {
|
||||
services, err := api.list(name)
|
||||
if err != nil {
|
||||
log.Fatalf("List Services: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, service := range services {
|
||||
if err := utils.OutputJSON(service); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
gock "gopkg.in/h2non/gock.v1"
|
||||
)
|
||||
|
||||
func TestNetwork(t *testing.T) {
|
||||
defer gock.Off()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
host := config.Host{Host: "127.0.0.1"}
|
||||
api, err := Create(host.Host, constants.AgentPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const network = "fx-net"
|
||||
if err := api.CreateNetwork(network); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nws, err := api.GetNetwork(network)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if nws[0].Name != network {
|
||||
t.Fatalf("should get %s but got %s", network, nws[0].Name)
|
||||
}
|
||||
}
|
||||
@@ -1,119 +1,59 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/google/uuid"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
func makeTar(project types.Project, tarFilePath string) error {
|
||||
dir, err := ioutil.TempDir("/tmp", "fx-build-dir")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
for _, file := range project.Files {
|
||||
tmpfn := filepath.Join(dir, file.Path)
|
||||
if err := utils.EnsureFile(tmpfn); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(tmpfn, []byte(file.Body), 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return utils.TarDir(dir, tarFilePath)
|
||||
}
|
||||
|
||||
// Build build a project
|
||||
func (api *API) Build(project types.Project) (types.Service, error) {
|
||||
tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||
if err != nil {
|
||||
return types.Service{}, err
|
||||
}
|
||||
defer os.RemoveAll(tarDir)
|
||||
|
||||
imageID := uuid.New().String()
|
||||
tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", imageID))
|
||||
if err := makeTar(project, tarFilePath); err != nil {
|
||||
return types.Service{}, err
|
||||
}
|
||||
labels := map[string]string{
|
||||
"belong-to": "fx",
|
||||
}
|
||||
if err := api.BuildImage(tarFilePath, imageID, labels); err != nil {
|
||||
return types.Service{}, err
|
||||
}
|
||||
|
||||
return types.Service{
|
||||
Name: project.Name,
|
||||
Image: imageID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuildImage build docker image
|
||||
func (api *API) BuildImage(tarFile string, tag string, labels map[string]string) error {
|
||||
dockerBuildContext, err := os.Open(tarFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dockerBuildContext.Close()
|
||||
|
||||
type buildQuery struct {
|
||||
Labels string `url:"labels,omitempty"`
|
||||
Tags string `url:"t,omitempty"`
|
||||
Dockerfile string `url:"dockerfile,omitempty"`
|
||||
}
|
||||
|
||||
// Apply default labels
|
||||
labelsJSON, _ := json.Marshal(labels)
|
||||
q := buildQuery{
|
||||
Tags: tag,
|
||||
Labels: string(labelsJSON),
|
||||
Dockerfile: "Dockerfile",
|
||||
}
|
||||
qs, err := query.Values(q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := "/build"
|
||||
url := fmt.Sprintf("%s%s?%s", api.endpoint, path, qs.Encode())
|
||||
req, err := http.NewRequest("POST", url, dockerBuildContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-tar")
|
||||
client := &http.Client{Timeout: 600 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
|
||||
for scanner.Scan() {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
log.Infof(scanner.Text())
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// import (
|
||||
// "fmt"
|
||||
// "io/ioutil"
|
||||
// "os"
|
||||
// "path/filepath"
|
||||
//
|
||||
// "github.com/google/uuid"
|
||||
// "github.com/metrue/fx/types"
|
||||
// "github.com/metrue/fx/utils"
|
||||
// )
|
||||
//
|
||||
// func makeTar(project types.Project, tarFilePath string) error {
|
||||
// dir, err := ioutil.TempDir("/tmp", "fx-build-dir")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// defer os.RemoveAll(dir)
|
||||
//
|
||||
// for _, file := range project.Files {
|
||||
// tmpfn := filepath.Join(dir, file.Path)
|
||||
// if err := utils.EnsureFile(tmpfn); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if err := ioutil.WriteFile(tmpfn, []byte(file.Body), 0666); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return utils.TarDir(dir, tarFilePath)
|
||||
// }
|
||||
//
|
||||
// // Build build a project
|
||||
// func (api *API) Build(project types.Project) (types.Service, error) {
|
||||
// tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||
// if err != nil {
|
||||
// return types.Service{}, err
|
||||
// }
|
||||
// defer os.RemoveAll(tarDir)
|
||||
//
|
||||
// imageID := uuid.New().String()
|
||||
// tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", imageID))
|
||||
// if err := makeTar(project, tarFilePath); err != nil {
|
||||
// return types.Service{}, err
|
||||
// }
|
||||
// labels := map[string]string{
|
||||
// "belong-to": "fx",
|
||||
// }
|
||||
// if err := api.BuildImage(tarFilePath, imageID, labels); err != nil {
|
||||
// return types.Service{}, err
|
||||
// }
|
||||
//
|
||||
// return types.Service{
|
||||
// Name: project.Name,
|
||||
// Image: imageID,
|
||||
// }, nil
|
||||
// }
|
||||
|
||||
@@ -1,166 +1,82 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/types"
|
||||
gock "gopkg.in/h2non/gock.v1"
|
||||
)
|
||||
|
||||
func TestMakeTar(t *testing.T) {
|
||||
serviceName := "mock-service-abc"
|
||||
project := types.Project{
|
||||
Name: serviceName,
|
||||
Language: "node",
|
||||
Files: []types.ProjectSourceFile{
|
||||
types.ProjectSourceFile{
|
||||
Path: "Dockerfile",
|
||||
Body: `
|
||||
FROM metrue/fx-node-base
|
||||
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["node", "app.js"]`,
|
||||
IsHandler: false,
|
||||
},
|
||||
types.ProjectSourceFile{
|
||||
Path: "app.js",
|
||||
Body: `
|
||||
const Koa = require('koa');
|
||||
const bodyParser = require('koa-bodyparser');
|
||||
const func = require('./fx');
|
||||
|
||||
const app = new Koa();
|
||||
app.use(bodyParser());
|
||||
app.use(ctx => {
|
||||
const msg = func(ctx.request.body);
|
||||
ctx.body = msg;
|
||||
});
|
||||
|
||||
app.listen(3000);`,
|
||||
IsHandler: false,
|
||||
},
|
||||
types.ProjectSourceFile{
|
||||
Path: "fx.js",
|
||||
Body: `
|
||||
module.exports = (input) => {
|
||||
return input.a + input.b
|
||||
}
|
||||
`,
|
||||
IsHandler: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tarDir)
|
||||
|
||||
tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", serviceName))
|
||||
if err := makeTar(project, tarFilePath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
file, err := os.Open(tarFilePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stat.Name() != serviceName+".tar" {
|
||||
t.Fatalf("should get %s but got %s", serviceName+".tar", stat.Name())
|
||||
}
|
||||
if stat.Size() <= 0 {
|
||||
t.Fatalf("tarfile invalid: size %d", stat.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
defer gock.Off()
|
||||
|
||||
host := config.Host{Host: "127.0.0.1"}
|
||||
api, err := Create(host.Host, constants.AgentPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
url := "http://" + host.Host + ":" + constants.AgentPort
|
||||
gock.New(url).
|
||||
Post("/v" + api.version + "/build").
|
||||
AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) {
|
||||
if strings.Contains(req.URL.String(), "/v"+api.version+"/build") {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}).
|
||||
Reply(200).
|
||||
JSON(map[string]string{
|
||||
"stream": "Step 1/5...",
|
||||
})
|
||||
|
||||
serviceName := "mock-service-abc"
|
||||
project := types.Project{
|
||||
Name: serviceName,
|
||||
Language: "node",
|
||||
Files: []types.ProjectSourceFile{
|
||||
types.ProjectSourceFile{
|
||||
Path: "Dockerfile",
|
||||
Body: `
|
||||
FROM metrue/fx-node-base
|
||||
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["node", "app.js"]`,
|
||||
IsHandler: false,
|
||||
},
|
||||
types.ProjectSourceFile{
|
||||
Path: "app.js",
|
||||
Body: `
|
||||
const Koa = require('koa');
|
||||
const bodyParser = require('koa-bodyparser');
|
||||
const func = require('./fx');
|
||||
|
||||
const app = new Koa();
|
||||
app.use(bodyParser());
|
||||
app.use(ctx => {
|
||||
const msg = func(ctx.request.body);
|
||||
ctx.body = msg;
|
||||
});
|
||||
|
||||
app.listen(3000);`,
|
||||
IsHandler: false,
|
||||
},
|
||||
types.ProjectSourceFile{
|
||||
Path: "fx.js",
|
||||
Body: `
|
||||
module.exports = (input) => {
|
||||
return input.a + input.b
|
||||
}
|
||||
`,
|
||||
IsHandler: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
service, err := api.Build(project)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if service.Name != serviceName {
|
||||
t.Fatalf("should get %s but got %s", serviceName, service.Name)
|
||||
}
|
||||
if service.Image == "" {
|
||||
t.Fatal("service image should not be empty")
|
||||
}
|
||||
}
|
||||
// import (
|
||||
// "fmt"
|
||||
// "io/ioutil"
|
||||
// "os"
|
||||
// "path/filepath"
|
||||
// "testing"
|
||||
//
|
||||
// "github.com/metrue/fx/types"
|
||||
// )
|
||||
//
|
||||
// func TestMakeTar(t *testing.T) {
|
||||
// serviceName := "mock-service-abc"
|
||||
// project := types.Project{
|
||||
// Name: serviceName,
|
||||
// Language: "node",
|
||||
// Files: []types.ProjectSourceFile{
|
||||
// types.ProjectSourceFile{
|
||||
// Path: "Dockerfile",
|
||||
// Body: `
|
||||
// FROM metrue/fx-node-base
|
||||
//
|
||||
// COPY . .
|
||||
// EXPOSE 3000
|
||||
// CMD ["node", "app.js"]`,
|
||||
// IsHandler: false,
|
||||
// },
|
||||
// types.ProjectSourceFile{
|
||||
// Path: "app.js",
|
||||
// Body: `
|
||||
// const Koa = require('koa');
|
||||
// const bodyParser = require('koa-bodyparser');
|
||||
// const func = require('./fx');
|
||||
//
|
||||
// const app = new Koa();
|
||||
// app.use(bodyParser());
|
||||
// app.use(ctx => {
|
||||
// const msg = func(ctx.request.body);
|
||||
// ctx.body = msg;
|
||||
// });
|
||||
//
|
||||
// app.listen(3000);`,
|
||||
// IsHandler: false,
|
||||
// },
|
||||
// types.ProjectSourceFile{
|
||||
// Path: "fx.js",
|
||||
// Body: `
|
||||
// module.exports = (input) => {
|
||||
// return input.a + input.b
|
||||
// }
|
||||
// `,
|
||||
// IsHandler: true,
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// defer os.RemoveAll(tarDir)
|
||||
//
|
||||
// tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", serviceName))
|
||||
// if err := makeTar(project, tarFilePath); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// file, err := os.Open(tarFilePath)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// stat, err := file.Stat()
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if stat.Name() != serviceName+".tar" {
|
||||
// t.Fatalf("should get %s but got %s", serviceName+".tar", stat.Name())
|
||||
// }
|
||||
// if stat.Size() <= 0 {
|
||||
// t.Fatalf("tarfile invalid: size %d", stat.Size())
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/types"
|
||||
|
||||
gock "gopkg.in/h2non/gock.v1"
|
||||
)
|
||||
|
||||
func TestServiceRun(t *testing.T) {
|
||||
defer gock.Off()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
host := config.Host{Host: "127.0.0.1"}
|
||||
api, err := Create(host.Host, constants.AgentPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
service := types.Service{
|
||||
Name: "a-mock-service",
|
||||
Image: "a-mock-image-id",
|
||||
}
|
||||
|
||||
mockContainerID := "mock-container-id"
|
||||
url := "http://" + host.Host + ":" + constants.AgentPort
|
||||
gock.New(url).
|
||||
Post("/v0.2.1/containers").
|
||||
AddMatcher(func(req *http.Request, ereq *gock.Request) (m bool, e error) {
|
||||
// TODO multiple matching not supported by gock
|
||||
if req.URL.String() == url+"/v0.2.1/containers/"+mockContainerID+"/start" {
|
||||
return true, nil
|
||||
} else if req.URL.String() == url+"/v0.2.1/containers/create?name="+service.Name {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}).
|
||||
Reply(201).
|
||||
JSON(map[string]interface{}{
|
||||
"Id": mockContainerID,
|
||||
"Warnings": []string{},
|
||||
})
|
||||
|
||||
// FIXME
|
||||
if err := api.Run(9999, &service); err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
gock "gopkg.in/h2non/gock.v1"
|
||||
)
|
||||
|
||||
func TestStop(t *testing.T) {
|
||||
defer gock.Off()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
host := config.Host{Host: "127.0.0.1"}
|
||||
api, err := Create(host.Host, constants.AgentPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mockServiceName := "mock-service-name"
|
||||
url := "http://" + host.Host + ":" + constants.AgentPort
|
||||
gock.New(url).
|
||||
Post("/v" + api.version + "/containers/" + mockServiceName + "/stop").
|
||||
AddMatcher(func(req *http.Request, ereq *gock.Request) (m bool, e error) {
|
||||
if strings.Contains(req.URL.String(), "/v"+api.version+"/containers/"+mockServiceName+"/stop") {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}).
|
||||
Reply(204)
|
||||
if err := api.Stop(mockServiceName); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,81 +1,71 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// UpOptions options for up
|
||||
type UpOptions struct {
|
||||
Body []byte
|
||||
Lang string
|
||||
Name string
|
||||
Port int
|
||||
HealtCheck bool
|
||||
Project types.Project
|
||||
}
|
||||
|
||||
// Up up a source code of function to be a service
|
||||
func (api *API) Up(opt UpOptions) error {
|
||||
service, err := api.Build(opt.Project)
|
||||
if err != nil {
|
||||
log.Fatalf("Build Service %s: %v", opt.Name, err)
|
||||
return err
|
||||
}
|
||||
log.Infof("Build Service %s: %s", opt.Name, constants.CheckedSymbol)
|
||||
|
||||
if err := api.Run(opt.Port, &service); err != nil {
|
||||
log.Fatalf("Run Service: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Infof("Run Service: %s", constants.CheckedSymbol)
|
||||
log.Infof("Service (%s) is running on: %s:%d", service.Name, service.Host, service.Port)
|
||||
|
||||
if opt.HealtCheck {
|
||||
go func() {
|
||||
resultC, errC := api.ContainerWait(
|
||||
context.Background(),
|
||||
service.ID,
|
||||
container.WaitConditionNextExit,
|
||||
20*time.Second,
|
||||
)
|
||||
for {
|
||||
select {
|
||||
case res := <-resultC:
|
||||
var msg string
|
||||
if res.Error != nil {
|
||||
msg = res.Error.Message
|
||||
}
|
||||
log.Warnf("container exited: Code(%d) %s %s", res.StatusCode, msg, constants.UncheckedSymbol)
|
||||
case err := <-errC:
|
||||
log.Fatalf("wait container status exit: %s, %v", constants.UncheckedSymbol, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
trys := 0
|
||||
for {
|
||||
if trys > 2 {
|
||||
break
|
||||
}
|
||||
info, err := api.inspect(service.ID)
|
||||
if err != nil {
|
||||
log.Fatalf("healt checking failed: %v", err)
|
||||
}
|
||||
if info.State.Running {
|
||||
log.Info("service is running")
|
||||
} else {
|
||||
log.Warnf("service is %s", info.State.Status)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
trys++
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
// // UpOptions options for up
|
||||
// type UpOptions struct {
|
||||
// Body []byte
|
||||
// Lang string
|
||||
// Name string
|
||||
// Port int
|
||||
// HealtCheck bool
|
||||
// Project types.Project
|
||||
// }
|
||||
//
|
||||
// // Up up a source code of function to be a service
|
||||
// func (api *API) Up(opt UpOptions) error {
|
||||
// service, err := api.Build(opt.Project)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Build Service %s: %v", opt.Name, err)
|
||||
// return err
|
||||
// }
|
||||
// log.Infof("Build Service %s: %s", opt.Name, constants.CheckedSymbol)
|
||||
//
|
||||
// if err := api.Run(opt.Port, &service); err != nil {
|
||||
// log.Fatalf("Run Service: %v", err)
|
||||
// return err
|
||||
// }
|
||||
// log.Infof("Run Service: %s", constants.CheckedSymbol)
|
||||
// log.Infof("Service (%s) is running on: %s:%d", service.Name, service.Host, service.Port)
|
||||
//
|
||||
// if opt.HealtCheck {
|
||||
// go func() {
|
||||
// resultC, errC := api.ContainerWait(
|
||||
// context.Background(),
|
||||
// service.ID,
|
||||
// container.WaitConditionNextExit,
|
||||
// 20*time.Second,
|
||||
// )
|
||||
// for {
|
||||
// select {
|
||||
// case res := <-resultC:
|
||||
// var msg string
|
||||
// if res.Error != nil {
|
||||
// msg = res.Error.Message
|
||||
// }
|
||||
// log.Warnf("container exited: Code(%d) %s %s", res.StatusCode, msg, constants.UncheckedSymbol)
|
||||
// case err := <-errC:
|
||||
// log.Fatalf("wait container status exit: %s, %v", constants.UncheckedSymbol, err)
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
// trys := 0
|
||||
// for {
|
||||
// if trys > 2 {
|
||||
// break
|
||||
// }
|
||||
// info, err := api.inspect(service.ID)
|
||||
// if err != nil {
|
||||
// log.Fatalf("healt checking failed: %v", err)
|
||||
// }
|
||||
// if info.State.Running {
|
||||
// log.Info("service is running")
|
||||
// } else {
|
||||
// log.Warnf("service is %s", info.State.Status)
|
||||
// }
|
||||
// time.Sleep(1 * time.Second)
|
||||
// trys++
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
|
||||
@@ -9,13 +9,16 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/apex/log"
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
dockerTypesContainer "github.com/docker/docker/api/types/container"
|
||||
dockerFilters "github.com/docker/docker/api/types/filters"
|
||||
"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"
|
||||
@@ -74,7 +77,9 @@ func (d *Docker) BuildImage(ctx context.Context, workdir string, name string) er
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(string(body))
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
log.Info(string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -131,6 +136,11 @@ func (d *Docker) InspectImage(ctx context.Context, name string, img interface{})
|
||||
return json.NewDecoder(rdr).Decode(&img)
|
||||
}
|
||||
|
||||
// TagImage tag image
|
||||
func (d *Docker) TagImage(ctx context.Context, name string, tag string) error {
|
||||
return d.ImageTag(ctx, name, tag)
|
||||
}
|
||||
|
||||
// StartContainer create and start a container from given image
|
||||
func (d *Docker) StartContainer(ctx context.Context, name string, image string, ports []types.PortBinding) error {
|
||||
portSet := nat.PortSet{}
|
||||
@@ -152,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)
|
||||
@@ -180,7 +190,59 @@ func (d *Docker) StopContainer(ctx context.Context, name string) error {
|
||||
|
||||
// InspectContainer inspect a container
|
||||
func (d *Docker) InspectContainer(ctx context.Context, name string, container interface{}) error {
|
||||
return nil
|
||||
res, err := d.ContainerInspect(ctx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(body, &container)
|
||||
}
|
||||
|
||||
// ListContainer list containers
|
||||
func (d *Docker) ListContainer(ctx context.Context, name string) ([]types.Service, error) {
|
||||
args := dockerFilters.NewArgs(
|
||||
dockerFilters.Arg("label", "belong-to=fx"),
|
||||
)
|
||||
containers, err := d.ContainerList(ctx, dockerTypes.ContainerListOptions{
|
||||
Filters: args,
|
||||
})
|
||||
if err != nil {
|
||||
return []types.Service{}, err
|
||||
}
|
||||
|
||||
svs := make(map[string]types.Service)
|
||||
for _, container := range containers {
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
services := []types.Service{}
|
||||
for _, s := range svs {
|
||||
services = append(services, s)
|
||||
}
|
||||
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 (
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestDocker(t *testing.T) {
|
||||
@@ -42,6 +43,23 @@ func TestDocker(t *testing.T) {
|
||||
t.Fatalf("should have built image with tag %s", name)
|
||||
}
|
||||
|
||||
if err := cli.StartContainer(ctx, name, name, []types.PortBinding{
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 9000,
|
||||
ContainerExposePort: 3000,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var container dockerTypes.ContainerJSON
|
||||
if err := cli.InspectContainer(ctx, name, &container); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.Name != "/"+name {
|
||||
t.Fatalf("should get %s but got %s", "/"+name, container.Name)
|
||||
}
|
||||
|
||||
username := os.Getenv("DOCKER_USERNAME")
|
||||
password := os.Getenv("DOCKER_PASSWORD")
|
||||
if username == "" || password == "" {
|
||||
|
||||
@@ -10,8 +10,11 @@ import (
|
||||
type ContainerRuntime interface {
|
||||
BuildImage(ctx context.Context, workdir string, name string) error
|
||||
PushImage(ctx context.Context, name string) (string, error)
|
||||
InspectImage(ct context.Context, name string, img interface{}) error
|
||||
InspectImage(ctx context.Context, name string, img interface{}) error
|
||||
TagImage(ctx context.Context, name string, tag string) error
|
||||
StartContainer(ctx context.Context, name string, image string, bindings []types.PortBinding) error
|
||||
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)
|
||||
}
|
||||
|
||||
76
context/context.go
Normal file
76
context/context.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type key string
|
||||
|
||||
const (
|
||||
keyCliCtx = key("cmd_cli")
|
||||
)
|
||||
|
||||
// Contexter ctx interface
|
||||
type Contexter interface {
|
||||
Get(k string) interface{}
|
||||
Set(k string, v interface{})
|
||||
Use(fn func(ctx *Context) error) error
|
||||
GetContext() context.Context
|
||||
GetCliContext() *cli.Context
|
||||
}
|
||||
|
||||
// Context fx context
|
||||
type Context struct {
|
||||
context.Context
|
||||
}
|
||||
|
||||
// NewContext new a context
|
||||
func NewContext() *Context {
|
||||
ctx := context.Background()
|
||||
return &Context{ctx}
|
||||
}
|
||||
|
||||
// FromCliContext create context from cli.Context
|
||||
func FromCliContext(c *cli.Context) *Context {
|
||||
ctx := NewContext()
|
||||
ctx.WithCliContext(c)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithCliContext set cli.Context
|
||||
func (ctx *Context) WithCliContext(c *cli.Context) {
|
||||
newCtx := context.WithValue(ctx.Context, keyCliCtx, c)
|
||||
ctx.Context = newCtx
|
||||
}
|
||||
|
||||
// GetCliContext get cli.Context
|
||||
func (ctx *Context) GetCliContext() *cli.Context {
|
||||
return ctx.Value(keyCliCtx).(*cli.Context)
|
||||
}
|
||||
|
||||
// Set a value with name
|
||||
func (ctx *Context) Set(name string, value interface{}) {
|
||||
newCtx := context.WithValue(ctx.Context, name, value)
|
||||
ctx.Context = newCtx
|
||||
}
|
||||
|
||||
// Get a value
|
||||
func (ctx *Context) Get(name string) interface{} {
|
||||
return ctx.Context.Value(name)
|
||||
}
|
||||
|
||||
// Use invole a middle
|
||||
func (ctx *Context) Use(fn func(ctx *Context) error) error {
|
||||
return fn(ctx)
|
||||
}
|
||||
|
||||
// GetContext get context
|
||||
func (ctx *Context) GetContext() context.Context {
|
||||
return ctx.Context
|
||||
}
|
||||
|
||||
var (
|
||||
_ Contexter = &Context{}
|
||||
)
|
||||
29
context/context_test.go
Normal file
29
context/context_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestContext(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
cli := cli.NewContext(nil, nil, nil)
|
||||
ctx.WithCliContext(cli)
|
||||
c := ctx.GetCliContext()
|
||||
if c != cli {
|
||||
t.Fatalf("should get %v but got %v", cli, c)
|
||||
}
|
||||
|
||||
key := "k_1"
|
||||
value := "hello"
|
||||
ctx.Set(key, "hello")
|
||||
v := ctx.Get(key).(string)
|
||||
if v != value {
|
||||
t.Fatalf("should get %v but %v", value, v)
|
||||
}
|
||||
|
||||
if ctx.GetContext() == nil {
|
||||
t.Fatalf("should get context")
|
||||
}
|
||||
}
|
||||
104
context/mocks/context.go
Normal file
104
context/mocks/context.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: context.go
|
||||
|
||||
// Package mock_context is a generated GoMock package.
|
||||
package mock_context
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
context0 "github.com/metrue/fx/context"
|
||||
cli "github.com/urfave/cli"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockContexter is a mock of Contexter interface
|
||||
type MockContexter struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockContexterMockRecorder
|
||||
}
|
||||
|
||||
// MockContexterMockRecorder is the mock recorder for MockContexter
|
||||
type MockContexterMockRecorder struct {
|
||||
mock *MockContexter
|
||||
}
|
||||
|
||||
// NewMockContexter creates a new mock instance
|
||||
func NewMockContexter(ctrl *gomock.Controller) *MockContexter {
|
||||
mock := &MockContexter{ctrl: ctrl}
|
||||
mock.recorder = &MockContexterMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockContexter) EXPECT() *MockContexterMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Get mocks base method
|
||||
func (m *MockContexter) Get(k string) interface{} {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", k)
|
||||
ret0, _ := ret[0].(interface{})
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get
|
||||
func (mr *MockContexterMockRecorder) Get(k interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockContexter)(nil).Get), k)
|
||||
}
|
||||
|
||||
// Set mocks base method
|
||||
func (m *MockContexter) Set(k string, v interface{}) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Set", k, v)
|
||||
}
|
||||
|
||||
// Set indicates an expected call of Set
|
||||
func (mr *MockContexterMockRecorder) Set(k, v interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockContexter)(nil).Set), k, v)
|
||||
}
|
||||
|
||||
// Use mocks base method
|
||||
func (m *MockContexter) Use(fn func(*context0.Context) error) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Use", fn)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Use indicates an expected call of Use
|
||||
func (mr *MockContexterMockRecorder) Use(fn interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Use", reflect.TypeOf((*MockContexter)(nil).Use), fn)
|
||||
}
|
||||
|
||||
// GetContext mocks base method
|
||||
func (m *MockContexter) GetContext() context.Context {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetContext")
|
||||
ret0, _ := ret[0].(context.Context)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetContext indicates an expected call of GetContext
|
||||
func (mr *MockContexterMockRecorder) GetContext() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContext", reflect.TypeOf((*MockContexter)(nil).GetContext))
|
||||
}
|
||||
|
||||
// GetCliContext mocks base method
|
||||
func (m *MockContexter) GetCliContext() *cli.Context {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCliContext")
|
||||
ret0, _ := ret[0].(*cli.Context)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetCliContext indicates an expected call of GetCliContext
|
||||
func (mr *MockContexterMockRecorder) GetCliContext() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCliContext", reflect.TypeOf((*MockContexter)(nil).GetCliContext))
|
||||
}
|
||||
@@ -15,9 +15,6 @@ import (
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
// Version binary version
|
||||
var Version = "0.0.1"
|
||||
|
||||
func init() {
|
||||
// TODO clean it up
|
||||
os.Setenv("DEBUG", "true")
|
||||
@@ -43,6 +40,7 @@ docker_packer <encrypt_docker_project_source_tree> <image_name>
|
||||
}
|
||||
|
||||
var tree map[string]string
|
||||
//nolint
|
||||
if err := json.Unmarshal([]byte(str), &tree); err != nil {
|
||||
log.Fatalf("could not unmarshal meta: %s", meta)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -1,15 +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, bindings []types.PortBinding) error
|
||||
Destroy(ctx context.Context, name string) error
|
||||
Update(ctx context.Context, name string) error
|
||||
GetStatus(ctx context.Context, name string) error
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
|
||||
runtime "github.com/metrue/fx/container_runtimes/docker/sdk"
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Docker manage container
|
||||
type Docker struct {
|
||||
localClient *runtime.Docker
|
||||
}
|
||||
|
||||
// CreateClient create a docker instance
|
||||
func CreateClient(ctx context.Context) (*Docker, error) {
|
||||
cli, err := runtime.CreateClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Docker{localClient: cli}, nil
|
||||
}
|
||||
|
||||
// Deploy create a Docker container from given image, and bind the constants.FxContainerExposePort to given port
|
||||
func (d *Docker) Deploy(ctx context.Context, fn types.Func, name string, ports []types.PortBinding) error {
|
||||
// if DOCKER_REMOTE_HOST and DOCKER_REMOTE_PORT given
|
||||
// it means user is going to deploy service to remote host
|
||||
host := os.Getenv("DOCKER_REMOTE_HOST")
|
||||
port := os.Getenv("DOCKER_REMOTE_PORT")
|
||||
if port != "" && host != "" {
|
||||
httpClient, err := dockerHTTP.Create(host, port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project, err := packer.Pack(name, fn)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could pack function %v (%s)", name, fn)
|
||||
}
|
||||
return httpClient.Up(dockerHTTP.UpOptions{
|
||||
Body: []byte(fn.Source),
|
||||
Lang: fn.Language,
|
||||
Name: name,
|
||||
Port: int(ports[0].ServiceBindingPort),
|
||||
HealtCheck: false,
|
||||
Project: project,
|
||||
})
|
||||
}
|
||||
|
||||
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
|
||||
defer os.RemoveAll(workdir)
|
||||
|
||||
if err := packer.PackIntoDir(fn, workdir); err != nil {
|
||||
log.Fatalf("could not pack function %v: %v", fn, err)
|
||||
return err
|
||||
}
|
||||
if err := d.localClient.BuildImage(ctx, workdir, name); err != nil {
|
||||
log.Fatalf("could not build image: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
nameWithTag := name + ":latest"
|
||||
if err := d.localClient.ImageTag(ctx, name, nameWithTag); err != nil {
|
||||
log.Fatalf("could not tag image: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// when deploy a function on a bare Docker running without Kubernetes,
|
||||
// image would be built on-demand on host locally, so there is no need to
|
||||
// pull image from remote.
|
||||
// But it takes some times waiting image ready after image built, we retry to make sure it ready here
|
||||
var imgInfo dockerTypes.ImageInspect
|
||||
if err := utils.RunWithRetry(func() error {
|
||||
return d.localClient.InspectImage(ctx, name, &imgInfo)
|
||||
}, time.Second*1, 5); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.localClient.StartContainer(ctx, name, name, ports)
|
||||
}
|
||||
|
||||
// Update a container
|
||||
func (d *Docker) Update(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy stop and remove container
|
||||
func (d *Docker) Destroy(ctx context.Context, name string) error {
|
||||
return d.localClient.ContainerStop(ctx, name, nil)
|
||||
}
|
||||
|
||||
// GetStatus get status of container
|
||||
func (d *Docker) GetStatus(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ deploy.Deployer = &Docker{}
|
||||
)
|
||||
@@ -1,47 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestDocker(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cli, err := CreateClient(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
name := "helloworld"
|
||||
bindings := []types.PortBinding{
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 80,
|
||||
ContainerExposePort: 3000,
|
||||
},
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 443,
|
||||
ContainerExposePort: 3000,
|
||||
},
|
||||
}
|
||||
|
||||
fn := types.Func{
|
||||
Language: "node",
|
||||
Source: `
|
||||
module.exports = (ctx) => {
|
||||
ctx.body = 'hello world'
|
||||
}
|
||||
`,
|
||||
}
|
||||
if err := cli.Deploy(ctx, fn, name, bindings); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
if err := cli.Destroy(ctx, name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
46
docs/ubuntu.md
Normal file
46
docs/ubuntu.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# fx on Ubuntu
|
||||
|
||||
> The guide is verified on Amazon Lightsail ubuntu 18.08 instance
|
||||
|
||||
## Install Docker
|
||||
|
||||
```shell
|
||||
apt-get remove -y docker docker-engine docker.io containerd runc
|
||||
apt-get update -y
|
||||
apt-get install -y apt-transport-https ca-certificates curl software-properties-common lsb-core
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
|
||||
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
|
||||
apt-get update -y
|
||||
apt-get install -y docker-ce
|
||||
docker run hello-world
|
||||
```
|
||||
|
||||
## Install fx
|
||||
|
||||
```shell
|
||||
$ curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | sudo bash
|
||||
```
|
||||
|
||||
## Deploy a function onto localhost
|
||||
|
||||
```shell
|
||||
$ cat func.js
|
||||
|
||||
module.exports = (ctx) => {
|
||||
ctx.body = 'hello world'
|
||||
}
|
||||
|
||||
$ fx up -n test -p 2000 func.js
|
||||
$ curl 127.0.0.1:2000
|
||||
```
|
||||
|
||||
## Deploy a function onto remote host
|
||||
|
||||
* make sure your instance can be ssh login
|
||||
* make sure your instance accept port 8866
|
||||
|
||||
then you can deploy function to remote host
|
||||
|
||||
```shell
|
||||
DOCKER_REMOTE_HOST_ADDR=<your host> DOCKER_REMOTE_HOST_USER=<your user> DOCKER_REMOTE_HOST_PASSWORD=<your password> fx up -p 2000 test/functions/func.js
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user