Compare commits
27 Commits
0.8.3-alph
...
0.8.72
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
46
.github/workflows/ci.yml
vendored
46
.github/workflows/ci.yml
vendored
@@ -13,36 +13,22 @@ 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: lint
|
||||
run: |
|
||||
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint \
|
||||
golangci-lint run -v
|
||||
|
||||
- name: setup k8s and kind
|
||||
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
|
||||
|
||||
- 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 ./...
|
||||
- name: code cov
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: |
|
||||
export KUBECONFIG="$(kind get kubeconfig-path)"
|
||||
./scripts/coverage.sh
|
||||
bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}
|
||||
|
||||
@@ -57,6 +43,11 @@ jobs:
|
||||
make docker-build
|
||||
make test
|
||||
# make docker-publish #TODO in release workflow
|
||||
- name: test fx docker cloud
|
||||
run: |
|
||||
make start_docker_infra
|
||||
make test_docker_infra
|
||||
make stop_docker_infra
|
||||
|
||||
- name: test fx cli
|
||||
env:
|
||||
@@ -66,21 +57,22 @@ jobs:
|
||||
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 }}
|
||||
|
||||
29
.github/workflows/release.yml
vendored
29
.github/workflows/release.yml
vendored
@@ -18,32 +18,22 @@ 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: lint
|
||||
run: |
|
||||
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint \
|
||||
golangci-lint run -v
|
||||
|
||||
- name: setup k8s and kind
|
||||
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
|
||||
|
||||
- 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)"
|
||||
DEBUG=true go test -v ./...
|
||||
|
||||
- name: build fx
|
||||
run: |
|
||||
@@ -61,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]
|
||||
|
||||
39
Makefile
39
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
|
||||
@@ -26,12 +28,11 @@ clean:
|
||||
unit-test:
|
||||
./scripts/coverage.sh
|
||||
|
||||
cli-test-ci:
|
||||
./scripts/test_cli.sh 'js'
|
||||
|
||||
cli-test:
|
||||
echo 'run testing on localhost'
|
||||
./scripts/test_cli.sh
|
||||
# TODO enable remote test
|
||||
echo 'run testing on remote host'
|
||||
DOCKER_REMOTE_HOST_ADDR=${REMOTE_HOST_ADDR} DOCKER_REMOTE_HOST_USER=${REMOTE_HOST_USER} DOCKER_REMOTE_HOST_PASSWORD=${REMOTE_HOST_PASSWORD} ./scripts/test_cli.sh
|
||||
./scripts/test_cli.sh 'js rb py go php java d rs'
|
||||
|
||||
http-test:
|
||||
./scripts/http_test.sh
|
||||
@@ -39,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
|
||||
|
||||
95
README.md
95
README.md
@@ -1,5 +1,6 @@
|
||||
fx
|
||||
------
|
||||
|
||||
Poor man's function as a service.
|
||||
<br/>
|
||||

|
||||
@@ -13,6 +14,7 @@ Poor man's function as a service.
|
||||
- [Introduction](#introduction)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Manage Infrastructure](#manage-infrastructure)
|
||||
- [Contribute](#contribute)
|
||||
|
||||
|
||||
@@ -79,10 +81,10 @@ USAGE:
|
||||
fx [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
0.8.1
|
||||
0.8.7
|
||||
|
||||
COMMANDS:
|
||||
init start fx agent on host
|
||||
infra manage infrastructure
|
||||
up deploy a function
|
||||
down destroy a service
|
||||
list, ls list deployed services
|
||||
@@ -96,44 +98,36 @@ GLOBAL OPTIONS:
|
||||
--version, -v print the version
|
||||
```
|
||||
|
||||
1. Write a function
|
||||
### Deploy your function to Docker
|
||||
|
||||
You can check out [examples](https://github.com/metrue/fx/tree/master/examples/functions) for reference. Let's write a function as an example, it calculates the sum of two numbers then returns:
|
||||
|
||||
```js
|
||||
module.exports = (ctx) => {
|
||||
ctx.body = 'hello world'
|
||||
}
|
||||
```
|
||||
Then save it to a file `func.js`.
|
||||
$ fx up --name hello-fx ./examples/functions/JavaScript/func.js
|
||||
|
||||
2. Deploy your function as a service
|
||||
|
||||
Give your service a port with `--port`, and name with `--name`, heath checking with `--healthcheck` if you want.
|
||||
|
||||
```shell
|
||||
$ fx up -name fx_service_name -p 10001 --healthcheck func.js
|
||||
|
||||
2019/08/10 13:26:37 info Pack Service: ✓
|
||||
2019/08/10 13:26:39 info Build Service: ✓
|
||||
2019/08/10 13:26:39 info Run Service: ✓
|
||||
2019/08/10 13:26:39 info Service (fx_service_name) is running on: 0.0.0.0:10001
|
||||
2019/08/10 13:26:39 info up function fx_service_name(func.js) to machine localhost: ✓
|
||||
+------------------------------------------------------------------+-----------+---------------+
|
||||
| ID | NAME | ENDPOINT |
|
||||
+------------------------------------------------------------------+-----------+---------------+
|
||||
| 5b24d36608ee392c937a61a530805f74551ddec304aea3aca2ffa0fabcf98cf3 | /hello-fx | 0.0.0.0:58328 |
|
||||
+------------------------------------------------------------------+-----------+---------------+
|
||||
```
|
||||
|
||||
if you want see what the source code of your service looks like, you can export it into a dirctory,
|
||||
### Deploy your function to Kubernetes
|
||||
|
||||
```shell
|
||||
$ fx image export -o <path of dir> func.js
|
||||
2019/09/25 19:31:19 info exported to <path of dir>: ✓
|
||||
```
|
||||
$ KUBECONFIG=~/.kube/config ./build/fx up examples/functions/JavaScript/func.js --name hello-fx
|
||||
|
||||
+-------------------------------+------+----------------+
|
||||
| ID | NAME | ENDPOINT |
|
||||
+----+--------------------------+-----------------------+
|
||||
| 5b24d36608ee392c937a | hello-fx | 10.0.242.75:80 |
|
||||
+------------------------+-------------+----------------+
|
||||
```
|
||||
|
||||
3. Test your service
|
||||
### Test your service
|
||||
|
||||
then you can test your service:
|
||||
|
||||
```shell
|
||||
$ curl -v 0.0.0.0:10001
|
||||
$ curl -v 0.0.0.0:58328
|
||||
|
||||
|
||||
GET / HTTP/1.1
|
||||
@@ -155,39 +149,32 @@ hello world
|
||||
|
||||
```
|
||||
|
||||
## Docker
|
||||
## Manage Infrastructure
|
||||
|
||||
**fx** is originally designed to turn a function into a runnable Docker container in a easiest way, on a host with Docker running, you can just deploy your function with `fx up` command,
|
||||
**fx** is originally designed to turn a function into a runnable Docker container in a easiest way, on a host with Docker running, you can just deploy your function with `fx up` command, and now **fx** supports deploy function to be a service onto Kubernetes cluster infrasture, and we encourage you to do that other than on bare Docker environment, there are lots of advantage to run your function on Kubernetes like self-healing, load balancing, easy horizontal scaling, etc. It's pretty simple to deploy your function onto Kubernetes with **fx**, you just set KUBECONFIG in your enviroment.
|
||||
|
||||
By default. **fx** use localhost as target infrastructure to run your service, and you can also setup your remote virtual machines as **fx**'s infrastructure and deploy your functions onto it.
|
||||
|
||||
### `fx infra create`
|
||||
|
||||
You can create types (docker and k8s) of infrastructures for **fx** to deploy functions
|
||||
|
||||
```shell
|
||||
fx up --name hello-svc --port 7777 hello.js # onto localhost
|
||||
DOCKER_REMOTE_HOST_ADDR=xx.xx.xx.xx DOCKER_REMOTE_HOST_USER=xxxx DOCKER_REMOTE_HOST_PASSWORD=xxxx fx up --name hello-svc --port 7777 hello.js # onto remote host
|
||||
$ fx infra create --name infra_us --type docker --host <user>@<ip> ## create docker type infrasture on <ip>
|
||||
$ fx infra create --name infra_bj --type k8s --master <user>@<ip> --agents '<user1>@<ip1>,<user2>@<ip2>' ## create k8s type infrasture use <ip> as master node, and <ip1> and <ip2> as agents nodes
|
||||
```
|
||||
|
||||
## Kubernetes
|
||||
### `fx infra use`
|
||||
|
||||
**fx** supports deploy function to be a service onto Kubernetes cluster infrasture, and we encourage you to do that other than on bare Docker environment, there are lots of advantage to run your function on Kubernetes like self-healing, load balancing, easy horizontal scaling, etc. It's pretty simple to deploy your function onto Kubernetes with **fx**, you just set KUBECONFIG in your enviroment.
|
||||
To use a infrastructure, you can use `fx infra use` command to activate it.
|
||||
|
||||
```shell
|
||||
KUBECONFIG=<Your KUBECONFIG> fx deploy -n fx-service-abc_js -p 12349 examples/functions/JavaScript/func.js # function will be deploy to your Kubernetes cluster and expose a IP address of your loadbalencer
|
||||
fx infra use <infrastructure name>
|
||||
```
|
||||
|
||||
or
|
||||
and you can list your infrastructure with `fx infra list`
|
||||
|
||||
```shell
|
||||
$ export KUBECONFIG=<Your KUBECONFIG>
|
||||
$ fx deploy -n fx-service-abc_js -p 12349 examples/functions/JavaScript/func.js # function will be deploy to your Kubernetes cluster and expose a IP address of your loadbalencer
|
||||
```
|
||||
|
||||
* Local Kubernetes Cluster
|
||||
|
||||
Docker for Mac and Docker for Windows already support Kubernetes with single node cluster, we can use it directly, and the default `KUBECONFIG` is `~/.kube/config`.
|
||||
|
||||
```shell
|
||||
$ export KUBECONFIG=~/.kube/config # then fx will take the config to deloy function
|
||||
```
|
||||
|
||||
if you have multiple Kubernetes clusters configured, you have to set context correctly. FYI [configure-access-multiple-clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/)
|
||||
## Use Public Cloud Kubernetes Service as infrastructure to run your functions
|
||||
|
||||
* Azure Kubernetes Service (AKS)
|
||||
|
||||
@@ -222,6 +209,14 @@ But we would suggest you run `kubectl config current-context` to check if the cu
|
||||
* Google Kubernetes Engine (GKET)
|
||||
TODO
|
||||
|
||||
* 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
|
||||
|
||||
fx uses [Project](https://github.com/metrue/fx/projects/4) to manage the development.
|
||||
|
||||
195
config/config.go
Normal file
195
config/config.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Items data of config file
|
||||
type Items struct {
|
||||
Clouds map[string]map[string]string `json:"clouds"`
|
||||
CurrentCloud string `json:"current_cloud"`
|
||||
}
|
||||
|
||||
// Config config of fx
|
||||
type Config struct {
|
||||
mux sync.Mutex
|
||||
configFile string
|
||||
Items
|
||||
}
|
||||
|
||||
// LoadDefault load default config
|
||||
func LoadDefault() (*Config, error) {
|
||||
configFile, err := homedir.Expand("~/.fx/config.yml")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if os.Getenv("FX_CONFIG") != "" {
|
||||
configFile = os.Getenv("FX_CONFIG")
|
||||
|
||||
}
|
||||
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||
if err := utils.EnsureFile(configFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := writeDefaultConfig(configFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return load(configFile)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if err := writeDefaultConfig(configFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return load(configFile)
|
||||
}
|
||||
|
||||
// AddCloud add a cloud
|
||||
func (c *Config) addCloud(name string, cloud map[string]string) error {
|
||||
c.Items.Clouds[name] = cloud
|
||||
return save(c)
|
||||
}
|
||||
|
||||
// AddDockerCloud add docker cloud
|
||||
func (c *Config) AddDockerCloud(name string, config []byte) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
var conf map[string]string
|
||||
err := json.Unmarshal(config, &conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cloud := map[string]string{
|
||||
"type": "docker",
|
||||
"host": conf["ip"],
|
||||
"user": conf["user"],
|
||||
}
|
||||
return c.addCloud(name, cloud)
|
||||
}
|
||||
|
||||
// AddK8SCloud add k8s cloud
|
||||
func (c *Config) AddK8SCloud(name string, kubeconfig []byte) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
dir := path.Dir(c.configFile)
|
||||
kubecfg := path.Join(dir, name+".kubeconfig")
|
||||
if err := utils.EnsureFile(kubecfg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(kubecfg, kubeconfig, 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cloud := map[string]string{
|
||||
"type": "k8s",
|
||||
"kubeConfig": kubecfg,
|
||||
}
|
||||
|
||||
return c.addCloud(name, cloud)
|
||||
}
|
||||
|
||||
// Use set cloud instance with name as current context
|
||||
func (c *Config) Use(name string) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
has := false
|
||||
for n := range c.Clouds {
|
||||
if n == name {
|
||||
has = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !has {
|
||||
return fmt.Errorf("no cloud with name = %s", name)
|
||||
}
|
||||
c.Items.CurrentCloud = name
|
||||
return save(c)
|
||||
}
|
||||
|
||||
// View view current config
|
||||
func (c *Config) View() ([]byte, error) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
return ioutil.ReadFile(c.configFile)
|
||||
}
|
||||
|
||||
func load(configFile string) (*Config, error) {
|
||||
conf, err := ioutil.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var items Items
|
||||
if err := yaml.Unmarshal(conf, &items); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var c = Config{
|
||||
configFile: configFile,
|
||||
Items: items,
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func save(c *Config) error {
|
||||
conf, err := yaml.Marshal(c.Items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(c.configFile, conf, 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeDefaultConfig(configFile string) error {
|
||||
me, err := user.Current()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
items := Items{
|
||||
Clouds: map[string]map[string]string{
|
||||
"default": map[string]string{
|
||||
"type": "docker",
|
||||
"host": "127.0.0.1",
|
||||
"user": me.Username,
|
||||
},
|
||||
},
|
||||
CurrentCloud: "default",
|
||||
}
|
||||
|
||||
body, err := yaml.Marshal(items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(configFile, body, 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
65
config/config_test.go
Normal file
65
config/config_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
configPath := "./tmp/config.yml"
|
||||
defer func() {
|
||||
if err := os.RemoveAll("./tmp"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
c, err := Load(configPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(c.Clouds) != 1 {
|
||||
t.Fatal("should contain default cloud")
|
||||
}
|
||||
|
||||
name := "fx_cluster_1"
|
||||
if err := c.Use(name); err == nil {
|
||||
t.Fatal("should get no such cloud error")
|
||||
}
|
||||
|
||||
if err := c.AddK8SCloud(name, []byte("sampe kubeconfg")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config := map[string]string{
|
||||
"ip": "127.0.0.1",
|
||||
"user": "use1",
|
||||
}
|
||||
configData, _ := json.Marshal(config)
|
||||
if err := c.AddDockerCloud("docker-1", configData); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := c.Use(name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.CurrentCloud != name {
|
||||
t.Fatalf("should get %s but got %s", name, c.CurrentCloud)
|
||||
}
|
||||
|
||||
conf, err := Load(configPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if conf.CurrentCloud != name {
|
||||
t.Fatalf("should get %s but got %s", name, c.CurrentCloud)
|
||||
}
|
||||
|
||||
body, err := c.View()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
7
config/types.go
Normal file
7
config/types.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package config
|
||||
|
||||
// CloudTypeDocker docker type
|
||||
const CloudTypeDocker = "docker"
|
||||
|
||||
// CloudTypeK8S k8s type
|
||||
const CloudTypeK8S = "k8s"
|
||||
@@ -37,27 +37,29 @@ type API struct {
|
||||
|
||||
// Create a API
|
||||
func Create(host string, port string) (*API, error) {
|
||||
version, err := utils.DockerVersion(host, port)
|
||||
addr := host + ":" + port
|
||||
v, err := version(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, version)
|
||||
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, v)
|
||||
return &API{
|
||||
endpoint: endpoint,
|
||||
version: version,
|
||||
version: v,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MustCreate a api object, panic if not
|
||||
func MustCreate(host string, port string) *API {
|
||||
version, err := utils.DockerVersion(host, port)
|
||||
addr := host + ":" + port
|
||||
v, err := version(addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, version)
|
||||
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, v)
|
||||
return &API{
|
||||
endpoint: endpoint,
|
||||
version: version,
|
||||
version: v,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +131,45 @@ func (api *API) post(path string, body []byte, expectStatus int, v interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Version get version of docker engine
|
||||
func (api *API) Version(ctx context.Context) (string, error) {
|
||||
return version(api.endpoint)
|
||||
}
|
||||
|
||||
func version(endpoint string) (string, error) {
|
||||
path := endpoint + "/version"
|
||||
if !strings.HasPrefix(path, "http") {
|
||||
path = "http://" + path
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
client := &http.Client{Timeout: 20 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("request %s failed: %d - %s", path, resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var res dockerTypes.Version
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.APIVersion, nil
|
||||
}
|
||||
|
||||
// ListContainer list service
|
||||
func (api *API) ListContainer(ctx context.Context, name string) ([]types.Service, error) {
|
||||
if name != "" {
|
||||
@@ -416,6 +457,10 @@ func (api *API) StopContainer(ctx context.Context, name string) error {
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,111 +1,31 @@
|
||||
package api
|
||||
|
||||
// func TestDockerHTTP(t *testing.T) {
|
||||
// const addr = "127.0.0.1"
|
||||
// const user = ""
|
||||
// const passord = ""
|
||||
// provisioner := provision.NewWithHost(addr, user, passord)
|
||||
// if err := utils.RunWithRetry(func() error {
|
||||
// if !provisioner.IsFxAgentRunning() {
|
||||
// if err := provisioner.StartFxAgent(); err != nil {
|
||||
// log.Infof("could not start fx agent on host: %s", err)
|
||||
// return err
|
||||
// }
|
||||
// log.Infof("fx agent started")
|
||||
// } else {
|
||||
// log.Infof("fx agent is running")
|
||||
// }
|
||||
// return nil
|
||||
// }, 2*time.Second, 10); err != nil {
|
||||
// t.Fatal(err)
|
||||
// } else {
|
||||
// defer provisioner.StopFxAgent()
|
||||
// }
|
||||
//
|
||||
// host := config.Host{Host: "127.0.0.1"}
|
||||
// api, err := Create(host.Host, constants.AgentPort)
|
||||
// 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 {
|
||||
// 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.ListContainer(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)
|
||||
// }
|
||||
//
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func TestDockerHTTP(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
name := "fx-agent"
|
||||
var container types.ContainerJSON
|
||||
if err := api.InspectContainer(context.Background(), name, &container); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.Name != "/"+name {
|
||||
t.Fatalf("should get %s but got %s", name, container.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,16 @@ 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
|
||||
@@ -226,6 +235,15 @@ func (d *Docker) ListContainer(ctx context.Context, name string) ([]types.Servic
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// Version get version of docker engine
|
||||
func (d *Docker) Version(ctx context.Context) (string, error) {
|
||||
ping, err := d.Ping(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ping.APIVersion, nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ containerruntimes.ContainerRuntime = &Docker{}
|
||||
)
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -16,4 +16,5 @@ type ContainerRuntime interface {
|
||||
StopContainer(ctx context.Context, name string) error
|
||||
InspectContainer(ctx context.Context, name string, container interface{}) error
|
||||
ListContainer(ctx context.Context, filter string) ([]types.Service, error)
|
||||
Version(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,15 @@ 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
|
||||
@@ -56,3 +65,12 @@ func (ctx *Context) Get(name string) interface{} {
|
||||
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{}
|
||||
)
|
||||
|
||||
@@ -22,4 +22,8 @@ func TestContext(t *testing.T) {
|
||||
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))
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
types "github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// Deployer make a image a service
|
||||
type Deployer interface {
|
||||
Deploy(ctx context.Context, fn types.Func, name string, image string, bindings []types.PortBinding) error
|
||||
Destroy(ctx context.Context, name string) error
|
||||
Update(ctx context.Context, name string) error
|
||||
GetStatus(ctx context.Context, name string) error
|
||||
List(ctx context.Context, name string) ([]types.Service, error)
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/constants"
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
|
||||
dockerSDK "github.com/metrue/fx/container_runtimes/docker/sdk"
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// Docker manage container
|
||||
type Docker struct {
|
||||
cli containerruntimes.ContainerRuntime
|
||||
}
|
||||
|
||||
// CreateClient create a docker instance
|
||||
func CreateClient(ctx context.Context) (d *Docker, err error) {
|
||||
var cli containerruntimes.ContainerRuntime
|
||||
host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
|
||||
user := os.Getenv("DOCKER_REMOTE_HOST_USER")
|
||||
if host != "" && user != "" {
|
||||
cli, err = dockerHTTP.Create(host, constants.AgentPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
cli, err = dockerSDK.CreateClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &Docker{cli: 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, image string, ports []types.PortBinding) error {
|
||||
return d.cli.StartContainer(ctx, name, image, 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.cli.StopContainer(ctx, name)
|
||||
}
|
||||
|
||||
// GetStatus get status of container
|
||||
func (d *Docker) GetStatus(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// List services
|
||||
func (d *Docker) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||
// FIXME support remote host
|
||||
return d.cli.ListContainer(ctx, name)
|
||||
}
|
||||
|
||||
var (
|
||||
_ deploy.Deployer = &Docker{}
|
||||
)
|
||||
@@ -1,102 +0,0 @@
|
||||
package k3s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func generateDeploymentSpec(
|
||||
name string,
|
||||
image string,
|
||||
bindPorts []types.PortBinding,
|
||||
replicas int32,
|
||||
selector map[string]string,
|
||||
) *appsv1.Deployment {
|
||||
ports := []apiv1.ContainerPort{}
|
||||
for index, binding := range bindPorts {
|
||||
ports = append(ports, apiv1.ContainerPort{
|
||||
Name: fmt.Sprintf("fx-container-%d", index),
|
||||
ContainerPort: binding.ContainerExposePort,
|
||||
})
|
||||
}
|
||||
|
||||
container := apiv1.Container{
|
||||
Name: "fx-placeholder-container-name",
|
||||
Image: image,
|
||||
Ports: ports,
|
||||
ImagePullPolicy: v1.PullIfNotPresent,
|
||||
}
|
||||
return &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: &replicas,
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: selector,
|
||||
},
|
||||
Template: apiv1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: selector,
|
||||
},
|
||||
Spec: apiv1.PodSpec{
|
||||
Containers: []apiv1.Container{container},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetDeployment get a deployment
|
||||
func (k *K3S) GetDeployment(namespace string, name string) (*appsv1.Deployment, error) {
|
||||
return k.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
// CreateDeployment create a deployment
|
||||
func (k *K3S) CreateDeployment(
|
||||
namespace string,
|
||||
name string,
|
||||
image string,
|
||||
ports []types.PortBinding,
|
||||
replicas int32,
|
||||
selector map[string]string,
|
||||
) (*appsv1.Deployment, error) {
|
||||
deployment := generateDeploymentSpec(name, image, ports, replicas, selector)
|
||||
return k.AppsV1().Deployments(namespace).Create(deployment)
|
||||
}
|
||||
|
||||
// UpdateDeployment update a deployment
|
||||
func (k *K3S) UpdateDeployment(
|
||||
namespace string,
|
||||
name string,
|
||||
image string,
|
||||
ports []types.PortBinding,
|
||||
replicas int32,
|
||||
selector map[string]string,
|
||||
) (*appsv1.Deployment, error) {
|
||||
deployment := generateDeploymentSpec(name, image, ports, replicas, selector)
|
||||
return k.AppsV1().Deployments(namespace).Update(deployment)
|
||||
}
|
||||
|
||||
// DeleteDeployment delete a deployment
|
||||
func (k *K3S) DeleteDeployment(namespace string, name string) error {
|
||||
return k.AppsV1().Deployments(namespace).Delete(name, &metav1.DeleteOptions{})
|
||||
}
|
||||
|
||||
// CreateDeploymentWithInitContainer create a deployment which will wait InitContainer to do the image build before function container start
|
||||
func (k *K3S) CreateDeploymentWithInitContainer(
|
||||
namespace string,
|
||||
name string,
|
||||
ports []types.PortBinding,
|
||||
replicas int32,
|
||||
selector map[string]string,
|
||||
) (*appsv1.Deployment, error) {
|
||||
deployment := generateDeploymentSpec(name, name, ports, replicas, selector)
|
||||
updatedDeployment := injectInitContainer(name, deployment)
|
||||
return k.AppsV1().Deployments(namespace).Create(updatedDeployment)
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package k3s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// K3S client
|
||||
type K3S struct {
|
||||
*kubernetes.Clientset
|
||||
}
|
||||
|
||||
const namespace = "default"
|
||||
|
||||
// Create a k8s cluster client
|
||||
func Create() (*K3S, error) {
|
||||
config, err := clientcmd.BuildConfigFromKubeconfigGetter("", clientcmd.NewDefaultClientConfigLoadingRules().Load)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &K3S{clientset}, nil
|
||||
}
|
||||
|
||||
// Deploy a image to be a service
|
||||
func (k *K3S) Deploy(
|
||||
ctx context.Context,
|
||||
fn types.Func,
|
||||
name string,
|
||||
image string,
|
||||
ports []types.PortBinding,
|
||||
) error {
|
||||
selector := map[string]string{
|
||||
"app": "fx-app-" + name,
|
||||
}
|
||||
|
||||
const replicas = int32(3)
|
||||
if _, err := k.GetDeployment(namespace, name); err != nil {
|
||||
// TODO enable passing replica from fx CLI
|
||||
if _, err := k.CreateDeployment(
|
||||
namespace,
|
||||
name,
|
||||
image,
|
||||
ports,
|
||||
replicas,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err := k.UpdateDeployment(
|
||||
namespace,
|
||||
name,
|
||||
image,
|
||||
ports,
|
||||
replicas,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO fx should be able to know what's the target Kubernetes service platform
|
||||
// it's going to deploy to
|
||||
typ := "LoadBalancer"
|
||||
if os.Getenv("SERVICE_TYPE") != "" {
|
||||
typ = os.Getenv("SERVICE_TYPE")
|
||||
}
|
||||
|
||||
if _, err := k.GetService(namespace, name); err != nil {
|
||||
if _, err := k.CreateService(
|
||||
namespace,
|
||||
name,
|
||||
typ,
|
||||
ports,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err := k.UpdateService(
|
||||
namespace,
|
||||
name,
|
||||
typ,
|
||||
ports,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update a service
|
||||
func (k *K3S) Update(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy a service
|
||||
func (k *K3S) Destroy(ctx context.Context, name string) error {
|
||||
if err := k.DeleteService(namespace, name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := k.DeleteDeployment(namespace, name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStatus get status of a service
|
||||
func (k *K3S) GetStatus(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// List services
|
||||
func (k *K3S) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||
return []types.Service{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ deploy.Deployer = &K3S{}
|
||||
)
|
||||
@@ -1,87 +0,0 @@
|
||||
package k3s
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
intstr "k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
func generateServiceSpec(
|
||||
namespace string,
|
||||
name string,
|
||||
typ string,
|
||||
bindings []types.PortBinding,
|
||||
selector map[string]string,
|
||||
) *apiv1.Service {
|
||||
servicePorts := []apiv1.ServicePort{}
|
||||
for index, binding := range bindings {
|
||||
servicePorts = append(servicePorts, apiv1.ServicePort{
|
||||
Name: "port-" + strconv.Itoa(index),
|
||||
Protocol: apiv1.ProtocolTCP,
|
||||
Port: binding.ServiceBindingPort,
|
||||
TargetPort: intstr.FromInt(int(binding.ContainerExposePort)),
|
||||
})
|
||||
}
|
||||
|
||||
return &apiv1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
ClusterName: namespace,
|
||||
},
|
||||
Spec: apiv1.ServiceSpec{
|
||||
Ports: servicePorts,
|
||||
Type: apiv1.ServiceType(typ),
|
||||
Selector: selector,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CreateService create a service
|
||||
func (k *K3S) CreateService(
|
||||
namespace string,
|
||||
name string,
|
||||
typ string,
|
||||
bindings []types.PortBinding,
|
||||
selector map[string]string,
|
||||
) (*apiv1.Service, error) {
|
||||
service := generateServiceSpec(namespace, name, typ, bindings, selector)
|
||||
createdService, err := k.CoreV1().Services(namespace).Create(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createdService, nil
|
||||
}
|
||||
|
||||
// UpdateService update a service
|
||||
// TODO this method is not perfect yet, should refactor later
|
||||
func (k *K3S) UpdateService(
|
||||
namespace string,
|
||||
name string,
|
||||
typ string,
|
||||
bindings []types.PortBinding,
|
||||
selector map[string]string,
|
||||
) (*apiv1.Service, error) {
|
||||
svc, err := k.GetService(namespace, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
svc.Spec.Selector = selector
|
||||
svc.Spec.Type = apiv1.ServiceType(typ)
|
||||
return k.CoreV1().Services(namespace).Update(svc)
|
||||
}
|
||||
|
||||
// DeleteService a service
|
||||
func (k *K3S) DeleteService(namespace string, name string) error {
|
||||
// TODO figure out the elegant way to delete a service
|
||||
options := &metav1.DeleteOptions{}
|
||||
return k.CoreV1().Services(namespace).Delete(name, options)
|
||||
}
|
||||
|
||||
// GetService get a service
|
||||
func (k *K3S) GetService(namespace string, name string) (*apiv1.Service, error) {
|
||||
return k.CoreV1().Services(namespace).Get(name, metav1.GetOptions{})
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package kubernetes
|
||||
|
||||
// ConfigMap is the key to function docker project source code in configmap
|
||||
var ConfigMap = struct {
|
||||
AppMetaEnvName string
|
||||
}{
|
||||
AppMetaEnvName: "APP_META",
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestDeployment(t *testing.T) {
|
||||
namespace := "default"
|
||||
name := "fx-hello-world"
|
||||
image := "metrue/kube-hello"
|
||||
selector := map[string]string{
|
||||
"app": "fx-app",
|
||||
}
|
||||
|
||||
kubeconfig := os.Getenv("KUBECONFIG")
|
||||
if kubeconfig == "" {
|
||||
t.Skip("skip test since no KUBECONFIG given in environment variable")
|
||||
}
|
||||
|
||||
k8s, err := Create()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := k8s.GetDeployment(namespace, name); err == nil {
|
||||
t.Fatalf("should get not found error")
|
||||
}
|
||||
|
||||
replicas := int32(2)
|
||||
bindings := []types.PortBinding{
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 80,
|
||||
ContainerExposePort: 3000,
|
||||
},
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 443,
|
||||
ContainerExposePort: 3000,
|
||||
},
|
||||
}
|
||||
deployment, err := k8s.CreateDeployment(namespace, name, image, bindings, replicas, selector)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if deployment == nil {
|
||||
t.Fatalf("deploymetn should not be %v", nil)
|
||||
}
|
||||
|
||||
if deployment.Name != name {
|
||||
t.Fatalf("should get %s but got %s", name, deployment.Name)
|
||||
}
|
||||
|
||||
if *deployment.Spec.Replicas != replicas {
|
||||
t.Fatalf("should get %v but got %v", replicas, deployment.Spec.Replicas)
|
||||
}
|
||||
|
||||
if err := k8s.DeleteDeployment(namespace, name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// This is docker image provided by fx/contrib/docker_packer
|
||||
// it can build a Docker image with give Docker project source codes encoded with base64
|
||||
// check the detail fx/contrib/docker_packer/main.go
|
||||
const image = "metrue/fx-docker"
|
||||
|
||||
func injectInitContainer(name string, deployment *appsv1.Deployment) *appsv1.Deployment {
|
||||
configMapHasToBeReady := true
|
||||
valueInConfigMapHasToBeReady := true
|
||||
initContainer := v1.Container{
|
||||
Name: "fx-docker-build-c",
|
||||
Image: image,
|
||||
ImagePullPolicy: v1.PullAlways,
|
||||
Command: []string{
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"/usr/bin/docker_packer $(APP_META) " + name,
|
||||
}, // Maybe it can be passed by Binary data from config map
|
||||
// Args: []string{"${APP_META}"}, // function source codes and name
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
v1.VolumeMount{
|
||||
Name: "dockersock",
|
||||
MountPath: "/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
Env: []v1.EnvVar{
|
||||
v1.EnvVar{
|
||||
Name: ConfigMap.AppMetaEnvName,
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{Name: name},
|
||||
Key: ConfigMap.AppMetaEnvName,
|
||||
Optional: &valueInConfigMapHasToBeReady,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
EnvFrom: []v1.EnvFromSource{
|
||||
v1.EnvFromSource{
|
||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
Optional: &configMapHasToBeReady,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
volumes := []v1.Volume{
|
||||
v1.Volume{
|
||||
Name: "dockersock",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
deployment.Spec.Template.Spec.InitContainers = []apiv1.Container{initContainer}
|
||||
deployment.Spec.Template.Spec.Volumes = volumes
|
||||
return deployment
|
||||
}
|
||||
BIN
docs/fx-init-cluster.png
Normal file
BIN
docs/fx-init-cluster.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 294 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 78 KiB |
47
examples/functions/Rust/README.md
vendored
Normal file
47
examples/functions/Rust/README.md
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# Make a Rust function a service with fx
|
||||
|
||||
Write a function like,
|
||||
|
||||
```rust
|
||||
pub mod fns {
|
||||
#[derive(Serialize)]
|
||||
pub struct Response {
|
||||
pub result: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Request {
|
||||
pub a: i32,
|
||||
pub b: i32,
|
||||
}
|
||||
|
||||
pub fn func(req: Request) -> Response {
|
||||
Response {
|
||||
result: req.a + req.b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
then deploy it with `fx up` command,
|
||||
|
||||
```shell
|
||||
$ fx up -p 8080 func.rs
|
||||
```
|
||||
|
||||
test it using `curl`
|
||||
|
||||
```shell
|
||||
$ curl -X 'POST' --header 'Content-Type: application/json' --data '{"a":1,"b":1}' '0.0.0.0:3000'
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 12
|
||||
Content-Type: application/json
|
||||
Date: Fri, 06 Dec 2019 06:45:14 GMT
|
||||
Server: Rocket
|
||||
|
||||
{
|
||||
"result": 2
|
||||
}
|
||||
```
|
||||
161
fx.go
161
fx.go
@@ -9,18 +9,31 @@ import (
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/google/uuid"
|
||||
aurora "github.com/logrusorgru/aurora"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/handlers"
|
||||
"github.com/metrue/fx/middlewares"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const version = "0.8.3"
|
||||
const version = "0.8.72"
|
||||
|
||||
func init() {
|
||||
go checkForUpdate()
|
||||
}
|
||||
|
||||
func handle(fns ...func(ctx context.Contexter) error) func(ctx *cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
ctx := context.FromCliContext(c)
|
||||
for _, fn := range fns {
|
||||
if err := fn(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkForUpdate() {
|
||||
const releaseURL = "https://api.github.com/repos/metrue/fx/releases/latest"
|
||||
resp, err := http.Get(releaseURL)
|
||||
@@ -56,12 +69,66 @@ func main() {
|
||||
app.Usage = "makes function as a service"
|
||||
app.Version = version
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println(aurora.Red("*****************"))
|
||||
fmt.Println(r)
|
||||
fmt.Println(aurora.Red("*****************"))
|
||||
}
|
||||
}()
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "init",
|
||||
Usage: "start fx agent on host",
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.Init()(context.FromCliContext(c))
|
||||
Name: "infra",
|
||||
Usage: "manage infrastructure",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "create",
|
||||
Usage: "create a infra for fx service",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "type, t",
|
||||
Usage: "infracture type, 'docker', 'k8s' and 'k3s' support",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Usage: "name to identify the infrastructure",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "host",
|
||||
Usage: "user and ip of your host, eg. 'root@182.12.1.12'",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "master",
|
||||
Usage: "serve as master node in K3S cluster, eg. 'root@182.12.1.12'",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "agents",
|
||||
Usage: "serve as agent node in K3S cluster, eg. 'root@187.1. 2. 3,root@123.3.2.1'",
|
||||
},
|
||||
},
|
||||
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
handlers.Setup,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list all infrastructures",
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
handlers.ListInfra,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "use",
|
||||
Usage: "set current context to target cloud with given name",
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
handlers.UseInfra,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -87,46 +154,36 @@ func main() {
|
||||
Usage: "force deploy a function or functions",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := context.FromCliContext(c)
|
||||
if err := ctx.Use(middlewares.Setup); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
if err := ctx.Use(middlewares.Binding); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
if err := ctx.Use(middlewares.Parse); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
if err := ctx.Use(middlewares.Build); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
return handlers.Up()(ctx)
|
||||
},
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Provision,
|
||||
middlewares.Parse("up"),
|
||||
middlewares.Binding,
|
||||
middlewares.Build,
|
||||
handlers.Up,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "down",
|
||||
Usage: "destroy a service",
|
||||
ArgsUsage: "[service 1, service 2, ....]",
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := context.FromCliContext(c)
|
||||
if err := ctx.Use(middlewares.Setup); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
return handlers.Down()(ctx)
|
||||
},
|
||||
Action: handle(
|
||||
middlewares.Parse("down"),
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Provision,
|
||||
handlers.Down,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list deployed services",
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := context.FromCliContext(c)
|
||||
if err := ctx.Use(middlewares.Setup); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
return handlers.List()(ctx)
|
||||
},
|
||||
Action: handle(
|
||||
middlewares.Parse("list"),
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Provision,
|
||||
handlers.List,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "call",
|
||||
@@ -137,9 +194,7 @@ func main() {
|
||||
Usage: "fx server host, default is localhost",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.Call()(context.FromCliContext(c))
|
||||
},
|
||||
Action: handle(handlers.Call),
|
||||
},
|
||||
{
|
||||
Name: "image",
|
||||
@@ -154,13 +209,12 @@ func main() {
|
||||
Usage: "image tag",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := context.FromCliContext(c)
|
||||
if err := ctx.Use(middlewares.Setup); err != nil {
|
||||
log.Fatalf("%v", err)
|
||||
}
|
||||
return handlers.BuildImage()(ctx)
|
||||
},
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Provision,
|
||||
middlewares.Parse("image_build"),
|
||||
handlers.BuildImage,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "export",
|
||||
@@ -171,18 +225,19 @@ func main() {
|
||||
Usage: "output directory",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.ExportImage()(context.FromCliContext(c))
|
||||
},
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Provision,
|
||||
middlewares.Parse("image_export"),
|
||||
handlers.ExportImage,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "doctor",
|
||||
Usage: "health check for fx",
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.Doctor()(context.FromCliContext(c))
|
||||
},
|
||||
Name: "doctor",
|
||||
Usage: "health check for fx",
|
||||
Action: handle(handlers.Doctor),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
16
go.mod
16
go.mod
@@ -22,25 +22,27 @@ require (
|
||||
github.com/gorilla/mux v1.7.3 // indirect
|
||||
github.com/imdario/mergo v0.3.7 // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434
|
||||
github.com/metrue/go-ssh-client v0.0.0-20190810064746-98a7a27048f3
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b
|
||||
github.com/mholt/archiver v3.1.1+incompatible
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/nwaples/rardecode v1.0.0 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/pelletier/go-toml v1.4.0 // indirect
|
||||
github.com/otiai10/copy v1.0.2
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
|
||||
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/schollz/progressbar/v2 v2.14.0
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/viper v1.5.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/ugorji/go v1.1.7 // indirect
|
||||
github.com/urfave/cli v1.22.1
|
||||
github.com/urfave/cli v1.22.2
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect
|
||||
gopkg.in/h2non/gock.v1 v1.0.15
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
||||
google.golang.org/grpc v1.21.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
gotest.tools v2.2.0+incompatible // indirect
|
||||
k8s.io/api v0.0.0-20190925180651-d58b53da08f5
|
||||
k8s.io/apimachinery v0.0.0-20190925235427-62598f38f24e
|
||||
|
||||
107
go.sum
107
go.sum
@@ -15,11 +15,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/apex/log v1.1.1 h1:BwhRZ0qbjYtTob0I+2M+smavV0kOC8XgcnGZcyL9liA=
|
||||
github.com/apex/log v1.1.1/go.mod h1:Ls949n1HFtXfbDcjiTTFQqkVUrte0puoIBfO3SVgwOA=
|
||||
github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
|
||||
@@ -27,18 +24,12 @@ github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3st
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/briandowns/spinner v1.7.0 h1:aan1hBBOoscry2TXAkgtxkJiq7Se0+9pt+TUWaPrB4g=
|
||||
github.com/briandowns/spinner v1.7.0/go.mod h1://Zf9tMcxfRUA36V23M6YGEAv+kECGfvpnLTnb8n4XQ=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
@@ -48,7 +39,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v0.0.0-20190313072916-46036c230805 h1:Imk7y5LY4ljn+DhwaPVj9d8kvAxiZw8DQGwNmivIom0=
|
||||
@@ -71,20 +61,16 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||
@@ -93,14 +79,10 @@ github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b
|
||||
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
|
||||
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
|
||||
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@@ -114,7 +96,6 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -134,13 +115,7 @@ github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1a
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
@@ -153,17 +128,14 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU=
|
||||
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
@@ -180,8 +152,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434 h1:im9kkmH0WWwxzegiv18gSUJbuXR9y028rXrWuPp6Jug=
|
||||
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
@@ -191,14 +161,16 @@ github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/metrue/go-ssh-client v0.0.0-20190810064746-98a7a27048f3 h1:IzZATG6TKa6amM5pr8HK7w/Ae4l0VBjmTwTmVbszWFw=
|
||||
github.com/metrue/go-ssh-client v0.0.0-20190810064746-98a7a27048f3/go.mod h1:ERHOEBrDy6+8vfoJjjmhdmBpOzdvvP7bLtwYTTK6LOs=
|
||||
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
|
||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b h1:JGD0sJ44XzhsT1voOg00zji4ubuMNcVNK3m7d9GI88k=
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b/go.mod h1:ERHOEBrDy6+8vfoJjjmhdmBpOzdvvP7bLtwYTTK6LOs=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
|
||||
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
@@ -212,13 +184,13 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
|
||||
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.3 h1:i0LBnzgiChAWHJYTQAZJDOgf8MNxAVYZJ2m63SIDimI=
|
||||
github.com/olekukonko/tablewriter v0.0.3/go.mod h1:YZeBtGzYYEsCHp2LST/u/0NDwGkRoBtmn1cIWCJiS6M=
|
||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -228,29 +200,22 @@ github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2i
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/otiai10/copy v1.0.2 h1:DDNipYy6RkIkjMwy+AWzgKiNTyj2RUI9yEMeETEpVyc=
|
||||
github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
|
||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf h1:0d7SseXGaeqFXfRTLbiCkuLhSGEHZyKpz1XD3e5lbSo=
|
||||
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||
@@ -259,20 +224,15 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/schollz/progressbar/v2 v2.14.0 h1:vo7bdkI9E4/CIk9DnL5uVIaybLQiVtiCC2vO+u9j5IM=
|
||||
github.com/schollz/progressbar/v2 v2.14.0/go.mod h1:6YZjqdthH6SCZKv2rqGryrxPtfmRB/DWZxSMfCXPyD8=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
@@ -281,16 +241,10 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4=
|
||||
github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -300,13 +254,10 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
|
||||
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
|
||||
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
|
||||
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
@@ -317,16 +268,12 @@ github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -344,15 +291,11 @@ golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
|
||||
@@ -372,8 +315,6 @@ golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -393,7 +334,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -402,6 +342,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c h1:KfpJVdWhuRqNk4XVXzjXf2KAV4TBEP77SYdFGjeGuIE=
|
||||
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
@@ -415,7 +356,6 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -425,23 +365,22 @@ gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXa
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
|
||||
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a h1:/8zB6iBfHCl1qAnEAWwGPNrUvapuy6CPla1VM0k8hQw=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.0.0-20190925180651-d58b53da08f5 h1:PEYuamj4laOODrvrh/KIKxihqE8kAnxFRZ6kKtrAS8c=
|
||||
k8s.io/api v0.0.0-20190925180651-d58b53da08f5/go.mod h1:blPYY5r6fKug8SVOnjDtFAlzZzInCRL9NNls66SFhFI=
|
||||
|
||||
@@ -1,45 +1,11 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
// Call command handle
|
||||
func Call() HandleFunc {
|
||||
return func(ctx *context.Context) error {
|
||||
cli := ctx.GetCliContext()
|
||||
_ = strings.Join(cli.Args()[1:], " ")
|
||||
|
||||
file := cli.Args().First()
|
||||
src, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
log.Fatalf("Read Source: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Read Source: \u2713")
|
||||
|
||||
lang := utils.GetLangFromFileName(file)
|
||||
fn := types.Func{
|
||||
Language: lang,
|
||||
Source: string(src),
|
||||
}
|
||||
if _, err := packer.Pack(file, fn); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// TODO not supported
|
||||
// if err := api.MustCreate(host.Host, constants.AgentPort).
|
||||
// Call(file, params, project); err != nil {
|
||||
// log.Fatalf("call functions on machine %s with %v failed: %v", name, params, err)
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
func Call(ctx context.Contexter) error {
|
||||
// TODO not supported
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,19 +10,17 @@ import (
|
||||
)
|
||||
|
||||
// Doctor command handle
|
||||
func Doctor() HandleFunc {
|
||||
return func(ctx *context.Context) error {
|
||||
host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
|
||||
user := os.Getenv("DOCKER_REMOTE_HOST_USER")
|
||||
password := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD")
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
if err := doctor.New(host, user, password).Start(); err != nil {
|
||||
log.Warnf("machine %s is in dirty state: %v", host, err)
|
||||
} else {
|
||||
log.Infof("machine %s is in healthy state: %s", host, constants.CheckedSymbol)
|
||||
}
|
||||
return nil
|
||||
func Doctor(ctx context.Contexter) error {
|
||||
host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
|
||||
user := os.Getenv("DOCKER_REMOTE_HOST_USER")
|
||||
password := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD")
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
if err := doctor.New(host, user, password).Start(); err != nil {
|
||||
log.Warnf("machine %s is in dirty state: %v", host, err)
|
||||
} else {
|
||||
log.Infof("machine %s is in healthy state: %s", host, constants.CheckedSymbol)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,27 +2,17 @@ package handlers
|
||||
|
||||
import (
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/infra"
|
||||
)
|
||||
|
||||
// Down command handle
|
||||
func Down() HandleFunc {
|
||||
return func(ctx *context.Context) (err error) {
|
||||
const task = "destroying"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
cli := ctx.GetCliContext()
|
||||
services := cli.Args()
|
||||
runner := ctx.Get("deployer").(deploy.Deployer)
|
||||
for _, svc := range services {
|
||||
if err := runner.Destroy(ctx.Context, svc); err != nil {
|
||||
return err
|
||||
}
|
||||
func Down(ctx context.Contexter) (err error) {
|
||||
services := ctx.Get("services").([]string)
|
||||
runner := ctx.Get("deployer").(infra.Deployer)
|
||||
for _, svc := range services {
|
||||
if err := runner.Destroy(ctx.GetContext(), svc); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
27
handlers/down_test.go
Normal file
27
handlers/down_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
mockCtx "github.com/metrue/fx/context/mocks"
|
||||
mockDeployer "github.com/metrue/fx/infra/mocks"
|
||||
)
|
||||
|
||||
func TestDown(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ctx := mockCtx.NewMockContexter(ctrl)
|
||||
deployer := mockDeployer.NewMockDeployer(ctrl)
|
||||
|
||||
services := []string{"sample-name"}
|
||||
ctx.EXPECT().Get("services").Return(services)
|
||||
ctx.EXPECT().Get("deployer").Return(deployer)
|
||||
ctx.EXPECT().GetContext().Return(context.Background())
|
||||
deployer.EXPECT().Destroy(gomock.Any(), services[0]).Return(nil)
|
||||
if err := Down(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/metrue/fx/context"
|
||||
)
|
||||
|
||||
// HandleFunc command handle function
|
||||
type HandleFunc func(ctx *context.Context) error
|
||||
@@ -2,84 +2,74 @@ package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/google/uuid"
|
||||
"github.com/metrue/fx/constants"
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/otiai10/copy"
|
||||
)
|
||||
|
||||
// BuildImage build image
|
||||
func BuildImage() HandleFunc {
|
||||
return func(ctx *context.Context) error {
|
||||
cli := ctx.GetCliContext()
|
||||
funcFile := cli.Args().First()
|
||||
tag := cli.String("tag")
|
||||
if tag == "" {
|
||||
tag = uuid.New().String()
|
||||
}
|
||||
func BuildImage(ctx context.Contexter) (err error) {
|
||||
spinner.Start("building")
|
||||
defer func() {
|
||||
spinner.Stop("building", err)
|
||||
}()
|
||||
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
|
||||
defer os.RemoveAll(workdir)
|
||||
|
||||
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
|
||||
defer os.RemoveAll(workdir)
|
||||
sources := ctx.Get("sources").([]string)
|
||||
|
||||
body, err := ioutil.ReadFile(funcFile)
|
||||
if err != nil {
|
||||
log.Fatalf("function code load failed: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Infof("function code loaded: %v", constants.CheckedSymbol)
|
||||
lang := utils.GetLangFromFileName(funcFile)
|
||||
|
||||
fn := types.Func{Language: lang, Source: string(body)}
|
||||
|
||||
if err := packer.PackIntoDir(fn, workdir); err != nil {
|
||||
log.Fatalf("could not pack function %v: %v", fn, err)
|
||||
return err
|
||||
}
|
||||
|
||||
docker, ok := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
||||
if ok {
|
||||
nameWithTag := tag + ":latest"
|
||||
if err := docker.BuildImage(ctx.Context, workdir, nameWithTag); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("image built: %v", constants.CheckedSymbol)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("no available docker cli")
|
||||
if len(sources) == 0 {
|
||||
return fmt.Errorf("source file/directory of function required")
|
||||
}
|
||||
if len(sources) == 1 &&
|
||||
utils.IsDir(sources[0]) &&
|
||||
utils.HasDockerfile(sources[0]) {
|
||||
if err := copy.Copy(sources[0], workdir); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := packer.Pack(workdir, sources...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
||||
nameWithTag := ctx.Get("tag").(string) + ":latest"
|
||||
if err := docker.BuildImage(ctx.GetContext(), workdir, nameWithTag); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("image built: %s %v", nameWithTag, constants.CheckedSymbol)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExportImage export service's code into a directory
|
||||
func ExportImage() HandleFunc {
|
||||
return func(ctx *context.Context) (err error) {
|
||||
cli := ctx.GetCliContext()
|
||||
funcFile := cli.Args().First()
|
||||
outputDir := cli.String("output")
|
||||
if outputDir == "" {
|
||||
log.Fatalf("output directory required")
|
||||
return nil
|
||||
}
|
||||
func ExportImage(ctx context.Contexter) (err error) {
|
||||
outputDir := ctx.Get("output").(string)
|
||||
sources := ctx.Get("sources").([]string)
|
||||
|
||||
body, err := ioutil.ReadFile(funcFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read source failed")
|
||||
}
|
||||
lang := utils.GetLangFromFileName(funcFile)
|
||||
|
||||
if err := packer.PackIntoDir(types.Func{Language: lang, Source: string(body)}, outputDir); err != nil {
|
||||
log.Fatalf("write source code to file failed: %v", constants.UncheckedSymbol)
|
||||
if len(sources) == 0 {
|
||||
return fmt.Errorf("source file/directory of function required")
|
||||
}
|
||||
if len(sources) == 1 &&
|
||||
utils.IsDir(sources[0]) &&
|
||||
utils.HasDockerfile(sources[0]) {
|
||||
if err := copy.Copy(sources[0], outputDir); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := packer.Pack(outputDir, sources...); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("exported to %v: %v", outputDir, constants.CheckedSymbol)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("exported to %v: %v", outputDir, constants.CheckedSymbol)
|
||||
return nil
|
||||
}
|
||||
|
||||
97
handlers/infra.go
Normal file
97
handlers/infra.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/context"
|
||||
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||
"github.com/metrue/fx/infra/k8s"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
)
|
||||
|
||||
func setupK8S(masterInfo string, agentsInfo string) ([]byte, error) {
|
||||
info := strings.Split(masterInfo, "@")
|
||||
if len(info) != 2 {
|
||||
return nil, fmt.Errorf("incorrect master info, should be <user>@<ip> format")
|
||||
}
|
||||
master := k8s.MasterNode{
|
||||
User: info[0],
|
||||
IP: info[1],
|
||||
}
|
||||
agents := []k8s.AgentNode{}
|
||||
if agentsInfo != "" {
|
||||
agentsInfoList := strings.Split(agentsInfo, ",")
|
||||
for _, agent := range agentsInfoList {
|
||||
info := strings.Split(agent, "@")
|
||||
if len(info) != 2 {
|
||||
return nil, fmt.Errorf("incorrect agent info, should be <user>@<ip> format")
|
||||
}
|
||||
agents = append(agents, k8s.AgentNode{
|
||||
User: info[0],
|
||||
IP: info[1],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(master, agents, len(agents))
|
||||
k8sOperator := k8s.New(master, agents)
|
||||
return k8sOperator.Provision()
|
||||
}
|
||||
|
||||
func setupDocker(hostInfo string) ([]byte, error) {
|
||||
info := strings.Split(hostInfo, "@")
|
||||
if len(info) != 2 {
|
||||
return nil, fmt.Errorf("incorrect master info, should be <user>@<ip> format")
|
||||
}
|
||||
user := info[1]
|
||||
host := info[0]
|
||||
dockr := dockerInfra.CreateProvisioner(user, host)
|
||||
return dockr.Provision()
|
||||
}
|
||||
|
||||
// Setup infra
|
||||
func Setup(ctx context.Contexter) (err error) {
|
||||
const task = "setup infra"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
cli := ctx.GetCliContext()
|
||||
typ := cli.String("type")
|
||||
name := cli.String("name")
|
||||
if name == "" {
|
||||
return fmt.Errorf("name required")
|
||||
}
|
||||
if typ == "docker" {
|
||||
if cli.String("host") == "" {
|
||||
return fmt.Errorf("host required, eg. 'root@123.1.2.12'")
|
||||
}
|
||||
} else if typ == "k8s" {
|
||||
if cli.String("master") == "" {
|
||||
return fmt.Errorf("master required, eg. 'root@123.1.2.12'")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("invalid type, 'docker' and 'k8s' support")
|
||||
}
|
||||
|
||||
fxConfig := ctx.Get("config").(*config.Config)
|
||||
|
||||
switch strings.ToLower(typ) {
|
||||
case "k8s":
|
||||
kubeconf, err := setupK8S(cli.String("master"), cli.String("agents"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fxConfig.AddK8SCloud(name, kubeconf)
|
||||
case "docker":
|
||||
config, err := setupDocker(cli.String("host"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fxConfig.AddDockerCloud(name, config)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/provision"
|
||||
)
|
||||
|
||||
// Init start fx-agent
|
||||
func Init() HandleFunc {
|
||||
return func(ctx *context.Context) error {
|
||||
host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
|
||||
user := os.Getenv("DOCKER_REMOTE_HOST_USER")
|
||||
passord := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD")
|
||||
if host == "" {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
provisioner := provision.NewWithHost(host, user, passord)
|
||||
if !provisioner.IsFxAgentRunning() {
|
||||
if err := provisioner.StartFxAgent(); err != nil {
|
||||
log.Fatalf("could not start fx agent on host: %s", err)
|
||||
return err
|
||||
}
|
||||
log.Info("fx agent started")
|
||||
}
|
||||
log.Info("fx agent already started")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -2,34 +2,20 @@ package handlers
|
||||
|
||||
import (
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/pkg/render"
|
||||
)
|
||||
|
||||
// List command handle
|
||||
func List() HandleFunc {
|
||||
return func(ctx *context.Context) (err error) {
|
||||
const task = "deploying"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
func List(ctx context.Contexter) (err error) {
|
||||
cli := ctx.GetCliContext()
|
||||
deployer := ctx.Get("deployer").(infra.Deployer)
|
||||
|
||||
cli := ctx.GetCliContext()
|
||||
deployer := ctx.Get("deployer").(deploy.Deployer)
|
||||
|
||||
services, err := deployer.List(ctx.Context, cli.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, service := range services {
|
||||
if err := utils.OutputJSON(service); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
services, err := deployer.List(ctx.GetContext(), cli.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
render.Table(services)
|
||||
return nil
|
||||
}
|
||||
|
||||
19
handlers/list_infra.go
Normal file
19
handlers/list_infra.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/context"
|
||||
)
|
||||
|
||||
// ListInfra list infra
|
||||
func ListInfra(ctx context.Contexter) (err error) {
|
||||
fxConfig := ctx.Get("config").(*config.Config)
|
||||
conf, err := fxConfig.View()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(conf))
|
||||
return nil
|
||||
}
|
||||
@@ -1,50 +1,40 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/pkg/render"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// PortRange usable port range https: //en.wikipedia.org/wiki/Ephemeral_port
|
||||
var PortRange = struct {
|
||||
min int
|
||||
max int
|
||||
}{
|
||||
min: 1023,
|
||||
max: 65535,
|
||||
}
|
||||
|
||||
// Up command handle
|
||||
func Up() HandleFunc {
|
||||
return func(ctx *context.Context) (err error) {
|
||||
const task = "deploying"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
cli := ctx.GetCliContext()
|
||||
name := cli.String("name")
|
||||
port := cli.Int("port")
|
||||
|
||||
if port < PortRange.min || port > PortRange.max {
|
||||
return fmt.Errorf("invalid port number: %d, port number should in range of %d - %d", port, PortRange.min, PortRange.max)
|
||||
}
|
||||
|
||||
fn := ctx.Get("fn").(types.Func)
|
||||
image := ctx.Get("image").(string)
|
||||
deployer := ctx.Get("deployer").(deploy.Deployer)
|
||||
bindings := ctx.Get("bindings").([]types.PortBinding)
|
||||
return deployer.Deploy(
|
||||
ctx.Context,
|
||||
fn,
|
||||
name,
|
||||
image,
|
||||
bindings,
|
||||
)
|
||||
func Up(ctx context.Contexter) (err error) {
|
||||
fn, ok := ctx.Get("data").(string)
|
||||
if !ok {
|
||||
fn = ""
|
||||
}
|
||||
image, ok := ctx.Get("image").(string)
|
||||
if !ok {
|
||||
image = ""
|
||||
}
|
||||
name := ctx.Get("name").(string)
|
||||
deployer := ctx.Get("deployer").(infra.Deployer)
|
||||
bindings := ctx.Get("bindings").([]types.PortBinding)
|
||||
|
||||
if err := deployer.Deploy(
|
||||
ctx.GetContext(),
|
||||
fn,
|
||||
name,
|
||||
image,
|
||||
bindings,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service, err := deployer.GetStatus(ctx.GetContext(), name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
render.Table([]types.Service{service})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,12 +1,40 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
mockCtx "github.com/metrue/fx/context/mocks"
|
||||
mockDeployer "github.com/metrue/fx/infra/mocks"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestUp(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ctx := mockCtx.NewMockContexter(ctrl)
|
||||
deployer := mockDeployer.NewMockDeployer(ctrl)
|
||||
|
||||
bindings := []types.PortBinding{}
|
||||
name := "sample-name"
|
||||
image := "sample-image"
|
||||
data := "sample-data"
|
||||
ctx.EXPECT().Get("name").Return(name)
|
||||
ctx.EXPECT().Get("image").Return(image)
|
||||
ctx.EXPECT().Get("deployer").Return(deployer)
|
||||
ctx.EXPECT().Get("bindings").Return(bindings)
|
||||
ctx.EXPECT().Get("data").Return(data)
|
||||
ctx.EXPECT().GetContext().Return(context.Background()).Times(2)
|
||||
deployer.EXPECT().Deploy(gomock.Any(), data, name, image, bindings).Return(nil)
|
||||
deployer.EXPECT().GetStatus(gomock.Any(), name).Return(types.Service{
|
||||
ID: "id-1",
|
||||
Name: name,
|
||||
Host: "127.0.0.1",
|
||||
Port: 2100,
|
||||
}, nil)
|
||||
if err := Up(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
13
handlers/use_infra.go
Normal file
13
handlers/use_infra.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/context"
|
||||
)
|
||||
|
||||
// UseInfra use infra
|
||||
func UseInfra(ctx context.Contexter) error {
|
||||
fxConfig := ctx.Get("config").(*config.Config)
|
||||
cli := ctx.GetCliContext()
|
||||
return fxConfig.Use(cli.Args().First())
|
||||
}
|
||||
101
infra/docker/deployer.go
Normal file
101
infra/docker/deployer.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// Deployer manage container
|
||||
type Deployer struct {
|
||||
cli containerruntimes.ContainerRuntime
|
||||
}
|
||||
|
||||
// CreateClient create a docker instance
|
||||
func CreateClient(client containerruntimes.ContainerRuntime) (d *Deployer, err error) {
|
||||
return &Deployer{cli: client}, nil
|
||||
}
|
||||
|
||||
// Deploy create a Docker container from given image, and bind the constants.FxContainerExposePort to given port
|
||||
func (d *Deployer) Deploy(ctx context.Context, fn string, name string, image string, ports []types.PortBinding) (err error) {
|
||||
spinner.Start("deploying " + name)
|
||||
defer func() {
|
||||
spinner.Stop("deploying "+name, err)
|
||||
}()
|
||||
return d.cli.StartContainer(ctx, name, image, ports)
|
||||
}
|
||||
|
||||
// Update a container
|
||||
func (d *Deployer) Update(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy stop and remove container
|
||||
func (d *Deployer) Destroy(ctx context.Context, name string) (err error) {
|
||||
spinner.Start("destroying " + name)
|
||||
defer func() {
|
||||
spinner.Stop("destroying "+name, err)
|
||||
}()
|
||||
return d.cli.StopContainer(ctx, name)
|
||||
}
|
||||
|
||||
// GetStatus get a service status
|
||||
func (d *Deployer) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||
var container dockerTypes.ContainerJSON
|
||||
if err := d.cli.InspectContainer(ctx, name, &container); err != nil {
|
||||
return types.Service{}, err
|
||||
}
|
||||
|
||||
service := types.Service{
|
||||
ID: container.ID,
|
||||
Name: container.Name,
|
||||
}
|
||||
for _, bindings := range container.NetworkSettings.Ports {
|
||||
if len(bindings) > 0 {
|
||||
binding := bindings[0]
|
||||
port, err := strconv.Atoi(binding.HostPort)
|
||||
if err != nil {
|
||||
return service, err
|
||||
}
|
||||
service.Port = port
|
||||
service.Host = binding.HostIP
|
||||
service.State = container.State.Status
|
||||
service.Image = container.Image
|
||||
break
|
||||
}
|
||||
if service.Port != 0 && service.Host != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
// Ping check healty status of infra
|
||||
func (d *Deployer) Ping(ctx context.Context) error {
|
||||
if _, err := d.cli.Version(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List services
|
||||
func (d *Deployer) List(ctx context.Context, name string) (svcs []types.Service, err error) {
|
||||
const task = "listing"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
// FIXME support remote host
|
||||
return d.cli.ListContainer(ctx, name)
|
||||
}
|
||||
|
||||
var (
|
||||
_ infra.Deployer = &Deployer{}
|
||||
)
|
||||
13
infra/docker/docker.go
Normal file
13
infra/docker/docker.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package docker
|
||||
|
||||
import containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
|
||||
// CreateProvisioner create a provisioner
|
||||
func CreateProvisioner(ip string, user string) *Provisioner {
|
||||
return NewProvisioner(ip, user)
|
||||
}
|
||||
|
||||
// CreateDeployer create a deployer
|
||||
func CreateDeployer(client containerruntimes.ContainerRuntime) (*Deployer, error) {
|
||||
return &Deployer{cli: client}, nil
|
||||
}
|
||||
217
infra/docker/provisioner.go
Normal file
217
infra/docker/provisioner.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
sshOperator "github.com/metrue/go-ssh-client"
|
||||
)
|
||||
|
||||
// Provisioner docker host
|
||||
type Provisioner struct {
|
||||
IP string
|
||||
User string
|
||||
}
|
||||
|
||||
// NewProvisioner new a docker object
|
||||
func NewProvisioner(ip string, user string) *Provisioner {
|
||||
return &Provisioner{
|
||||
IP: ip,
|
||||
User: user,
|
||||
}
|
||||
}
|
||||
|
||||
// Provision provision a host, install docker and start dockerd
|
||||
func (d *Provisioner) Provision() (config []byte, err error) {
|
||||
spinner.Start("provisioning")
|
||||
defer func() {
|
||||
spinner.Stop("provisioning", err)
|
||||
}()
|
||||
|
||||
// TODO clean up, skip check localhost or not if in CICD env
|
||||
if os.Getenv("CICD") != "" {
|
||||
if err := d.Install(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := d.StartDockerd(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := d.StartFxAgent(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config, _ := json.Marshal(map[string]string{
|
||||
"ip": d.IP,
|
||||
"user": d.User,
|
||||
})
|
||||
return config, nil
|
||||
}
|
||||
|
||||
if d.isLocalHost() {
|
||||
if !d.hasDocker() {
|
||||
return nil, fmt.Errorf("please make sure docker installed and running")
|
||||
}
|
||||
|
||||
if err := d.StartFxAgentLocally(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, _ := json.Marshal(map[string]string{
|
||||
"ip": d.IP,
|
||||
"user": d.User,
|
||||
})
|
||||
return config, nil
|
||||
}
|
||||
|
||||
if err := d.Install(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := d.StartDockerd(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := d.StartFxAgent(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(map[string]string{
|
||||
"ip": d.IP,
|
||||
"user": d.User,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Provisioner) isLocalHost() bool {
|
||||
return strings.ToLower(d.IP) == "localhost" || d.IP == "127.0.0.1"
|
||||
}
|
||||
|
||||
func (d *Provisioner) hasDocker() bool {
|
||||
cmd := exec.Command("docker", "version")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// HealthCheck check healthy status of host
|
||||
func (d *Provisioner) HealthCheck() (bool, error) {
|
||||
if d.isLocalHost() {
|
||||
return d.IfFxAgentRunningLocally(), nil
|
||||
}
|
||||
return d.IfFxAgentRunning(), nil
|
||||
}
|
||||
|
||||
// Install docker on host
|
||||
func (d *Provisioner) Install() error {
|
||||
sudo := ""
|
||||
if d.User != "root" {
|
||||
sudo = "sudo"
|
||||
}
|
||||
installCmd := fmt.Sprintf("curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-18.06.3-ce.tgz -o docker.tgz && tar zxvf docker.tgz && %s mv docker/* /usr/bin && rm -rf docker docker.tgz", sudo)
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
sshPort := infra.GetSSHPort()
|
||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||
if err := ssh.RunCommand(installCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("install docker failed \n================")
|
||||
fmt.Println(err)
|
||||
fmt.Println("================")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartDockerd start dockerd
|
||||
func (d *Provisioner) StartDockerd() error {
|
||||
sudo := ""
|
||||
if d.User != "root" {
|
||||
sudo = "sudo"
|
||||
}
|
||||
installCmd := fmt.Sprintf("%s dockerd >/dev/null 2>&1 & sleep 2", sudo)
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
sshPort := infra.GetSSHPort()
|
||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||
if err := ssh.RunCommand(installCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("start dockerd failed \n================")
|
||||
fmt.Println(err)
|
||||
fmt.Println("================")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartFxAgent start fx agent
|
||||
func (d *Provisioner) StartFxAgent() error {
|
||||
startCmd := fmt.Sprintf("sleep 3 && docker stop %s || true && docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentContainerName, constants.AgentPort)
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
sshPort := infra.GetSSHPort()
|
||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||
if err := ssh.RunCommand(startCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("start fx agent failed \n================")
|
||||
fmt.Println(err)
|
||||
fmt.Println("================")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartFxAgentLocally start fx agent
|
||||
func (d *Provisioner) StartFxAgentLocally() error {
|
||||
startCmd := fmt.Sprintf("docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentPort)
|
||||
params := strings.Split(startCmd, " ")
|
||||
var cmd *exec.Cmd
|
||||
if len(params) > 1 {
|
||||
// nolint: gosec
|
||||
cmd = exec.Command(params[0], params[1:]...)
|
||||
} else {
|
||||
// nolint: gosec
|
||||
cmd = exec.Command(params[0])
|
||||
}
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
fmt.Println(string(out))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IfFxAgentRunningLocally check if fx agent is running
|
||||
func (d *Provisioner) IfFxAgentRunningLocally() bool {
|
||||
cmd := exec.Command("docker", "inspect", "fx-agent")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IfFxAgentRunning check if fx agent is running
|
||||
func (d *Provisioner) IfFxAgentRunning() bool {
|
||||
inspectCmd := infra.Sudo("docker inspect fx-agent", d.User)
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
sshPort := infra.GetSSHPort()
|
||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||
if err := ssh.RunCommand(inspectCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var _ infra.Provisioner = &Provisioner{}
|
||||
23
infra/docker/provisioner_test.go
Normal file
23
infra/docker/provisioner_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProvisioner(t *testing.T) {
|
||||
if os.Getenv("DOCKER_HOST") == "" ||
|
||||
os.Getenv("DOCKER_USER") == "" {
|
||||
t.Skip("skip test since DOCKER_HOST and DOCKER_USER not ready")
|
||||
}
|
||||
d := NewProvisioner(os.Getenv("DOCKER_HOST"), os.Getenv("DOCKER_USER"))
|
||||
if err := d.Install(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := d.StartDockerd(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := d.StartFxAgent(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
29
infra/infra.go
Normal file
29
infra/infra.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// Provisioner provision interface
|
||||
type Provisioner interface {
|
||||
Provision() (config []byte, err error)
|
||||
HealthCheck() (bool, error)
|
||||
}
|
||||
|
||||
// Deployer deploy interface
|
||||
type Deployer interface {
|
||||
Deploy(ctx context.Context, fn string, name string, image string, bindings []types.PortBinding) error
|
||||
Destroy(ctx context.Context, name string) error
|
||||
Update(ctx context.Context, name string) error
|
||||
GetStatus(ctx context.Context, name string) (types.Service, error)
|
||||
List(ctx context.Context, name string) ([]types.Service, error)
|
||||
Ping(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Infra infrastructure provision interface
|
||||
type Infra interface {
|
||||
Provisioner
|
||||
Deployer
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package kubernetes
|
||||
package k8s
|
||||
|
||||
import (
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
@@ -1,4 +1,4 @@
|
||||
package kubernetes
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -11,7 +11,7 @@ func TestConfigMap(t *testing.T) {
|
||||
t.Skip("skip test since no KUBECONFIG given in environment variable")
|
||||
}
|
||||
|
||||
k8s, err := Create()
|
||||
k8s, err := Create("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package k3s
|
||||
package k8s
|
||||
|
||||
// ConfigMap is the key to function docker project source code in configmap
|
||||
var ConfigMap = struct {
|
||||
@@ -1,10 +1,11 @@
|
||||
package kubernetes
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
@@ -26,21 +27,17 @@ func TestK8SDeployer(t *testing.T) {
|
||||
if kubeconfig == "" || username == "" || password == "" {
|
||||
t.Skip("skip test since no KUBECONFIG, DOCKER_USERNAME and DOCKER_PASSWORD given in environment variable")
|
||||
}
|
||||
k8s, err := Create()
|
||||
k8s, err := Create("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fn := types.Func{
|
||||
Language: "node",
|
||||
Source: `
|
||||
module.exports = (ctx) => {
|
||||
ctx.body = 'hello world'
|
||||
}
|
||||
`,
|
||||
data, err := packer.PackIntoK8SConfigMapFile("./fixture")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
if err := k8s.Deploy(ctx, fn, name, name, bindings); err != nil {
|
||||
if err := k8s.Deploy(ctx, data, name, name, bindings); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package kubernetes
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
@@ -19,8 +20,12 @@ type K8S struct {
|
||||
const namespace = "default"
|
||||
|
||||
// Create a k8s cluster client
|
||||
func Create() (*K8S, error) {
|
||||
config, err := clientcmd.BuildConfigFromKubeconfigGetter("", clientcmd.NewDefaultClientConfigLoadingRules().Load)
|
||||
func Create(kubeconfig string) (*K8S, error) {
|
||||
if os.Getenv("KUBECONFIG") != "" {
|
||||
kubeconfig = os.Getenv("KUBECONFIG")
|
||||
}
|
||||
|
||||
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -35,18 +40,13 @@ func Create() (*K8S, error) {
|
||||
// Deploy a image to be a service
|
||||
func (k *K8S) Deploy(
|
||||
ctx context.Context,
|
||||
fn types.Func,
|
||||
fn string,
|
||||
name string,
|
||||
image string,
|
||||
ports []types.PortBinding,
|
||||
) error {
|
||||
// put source code of function docker project into k8s config map
|
||||
tree, err := packer.PackIntoK8SConfigMapFile(fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := map[string]string{}
|
||||
data[ConfigMap.AppMetaEnvName] = tree
|
||||
data[ConfigMap.AppMetaEnvName] = fn
|
||||
if _, err := k.CreateOrUpdateConfigMap(namespace, name, data); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -58,14 +58,28 @@ func (k *K8S) Deploy(
|
||||
const replicas = int32(3)
|
||||
if _, err := k.GetDeployment(namespace, name); err != nil {
|
||||
// TODO enable passing replica from fx CLI
|
||||
if _, err := k.CreateDeploymentWithInitContainer(
|
||||
namespace,
|
||||
name,
|
||||
ports,
|
||||
replicas,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
if os.Getenv("K3S") != "" {
|
||||
// NOTE Doing docker build in initial container will fail when cluster is created by K3S
|
||||
if _, err := k.CreateDeployment(
|
||||
namespace,
|
||||
name,
|
||||
image,
|
||||
ports,
|
||||
replicas,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err := k.CreateDeploymentWithInitContainer(
|
||||
namespace,
|
||||
name,
|
||||
ports,
|
||||
replicas,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := k.UpdateDeployment(
|
||||
@@ -128,15 +142,49 @@ func (k *K8S) Destroy(ctx context.Context, name string) error {
|
||||
}
|
||||
|
||||
// GetStatus get status of a service
|
||||
func (k *K8S) GetStatus(ctx context.Context, name string) error {
|
||||
return nil
|
||||
func (k *K8S) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||
svc, err := k.GetService(namespace, name)
|
||||
service := types.Service{}
|
||||
if err != nil {
|
||||
return service, err
|
||||
}
|
||||
|
||||
service.Host = svc.Spec.ClusterIP
|
||||
if len(svc.Spec.ExternalIPs) > 0 {
|
||||
service.Host = svc.Spec.ExternalIPs[0]
|
||||
}
|
||||
|
||||
for _, port := range svc.Spec.Ports {
|
||||
// TODO should clearify which port (target port, node port) should use
|
||||
service.Port = int(port.Port)
|
||||
break
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
// List services
|
||||
func (k *K8S) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||
func (k *K8S) List(ctx context.Context, name string) (svcs []types.Service, err error) {
|
||||
const task = "listing"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
return []types.Service{}, nil
|
||||
}
|
||||
|
||||
// Ping health check of infra
|
||||
func (k *K8S) Ping(ctx context.Context) error {
|
||||
// Does not find any ping method for k8s
|
||||
nodes, err := k.ListNodes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(nodes.Items) <= 0 {
|
||||
return fmt.Errorf("no available nodes")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ deploy.Deployer = &K8S{}
|
||||
_ infra.Deployer = &K8S{}
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package kubernetes
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package k3s
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -20,7 +20,7 @@ func TestDeployment(t *testing.T) {
|
||||
t.Skip("skip test since no KUBECONFIG given in environment variable")
|
||||
}
|
||||
|
||||
k8s, err := Create()
|
||||
k8s, err := Create("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package k3s
|
||||
package k8s
|
||||
|
||||
import (
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
11
infra/k8s/k8s.go
Normal file
11
infra/k8s/k8s.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package k8s
|
||||
|
||||
// CreateProvisioner create a provisioner
|
||||
func CreateProvisioner(master MasterNode, agents []AgentNode) *Provisioner {
|
||||
return New(master, agents)
|
||||
}
|
||||
|
||||
// CreateDeployer create a deployer
|
||||
func CreateDeployer(kubeconfig string) (*K8S, error) {
|
||||
return Create(kubeconfig)
|
||||
}
|
||||
15
infra/k8s/node.go
Normal file
15
infra/k8s/node.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ListNodes list node
|
||||
func (k *K8S) ListNodes() (*v1.NodeList, error) {
|
||||
nodes, err := k.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package kubernetes
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"github.com/metrue/fx/constants"
|
||||
154
infra/k8s/provisioner.go
Normal file
154
infra/k8s/provisioner.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/infra"
|
||||
sshOperator "github.com/metrue/go-ssh-client"
|
||||
)
|
||||
|
||||
// MasterNode master node instance
|
||||
type MasterNode struct {
|
||||
IP string
|
||||
User string
|
||||
}
|
||||
|
||||
// AgentNode agent node instance
|
||||
type AgentNode struct {
|
||||
IP string
|
||||
User string
|
||||
}
|
||||
|
||||
// Provisioner k3s operator
|
||||
type Provisioner struct {
|
||||
master MasterNode
|
||||
agents []AgentNode
|
||||
}
|
||||
|
||||
// TODO upgrade to latest when k3s fix the tls scan issue
|
||||
// https://github.com/rancher/k3s/issues/556
|
||||
const version = "v0.9.1"
|
||||
|
||||
// New new a operator
|
||||
func New(master MasterNode, agents []AgentNode) *Provisioner {
|
||||
return &Provisioner{
|
||||
master: master,
|
||||
agents: agents,
|
||||
}
|
||||
}
|
||||
|
||||
// Provision provision k3s cluster
|
||||
func (k *Provisioner) Provision() ([]byte, error) {
|
||||
if err := k.SetupMaster(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := k.SetupAgent(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return k.GetKubeConfig()
|
||||
}
|
||||
|
||||
// HealthCheck check healthy status of host
|
||||
func (k *Provisioner) HealthCheck() (bool, error) {
|
||||
// TODO
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// SetupMaster setup master node
|
||||
func (k *Provisioner) SetupMaster() error {
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
||||
installCmd := fmt.Sprintf("curl -sLS https://get.k3s.io | INSTALL_K3S_EXEC='server --tls-san %s' INSTALL_K3S_VERSION='%s' sh -", k.master.IP, version)
|
||||
if err := ssh.RunCommand(infra.Sudo(installCmd, k.master.User), sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("setup master failed \n ===========")
|
||||
fmt.Println(err)
|
||||
fmt.Println("===========")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Provisioner) getToken() (string, error) {
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
||||
script := "cat /var/lib/rancher/k3s/server/node-token"
|
||||
var outPipe bytes.Buffer
|
||||
if err := ssh.RunCommand(infra.Sudo(script, k.master.User), sshOperator.CommandOptions{
|
||||
Stdout: bufio.NewWriter(&outPipe),
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return outPipe.String(), nil
|
||||
}
|
||||
|
||||
// SetupAgent set agent node
|
||||
func (k *Provisioner) SetupAgent() error {
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
tok, err := k.getToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
const k3sExtraArgs = ""
|
||||
joinCmd := fmt.Sprintf("curl -fL https://get.k3s.io/ | K3S_URL='https://%s:6443' K3S_TOKEN='%s' INSTALL_K3S_VERSION='%s' sh -s - %s", k.master.IP, tok, version, k3sExtraArgs)
|
||||
for _, agent := range k.agents {
|
||||
ssh := sshOperator.New(agent.IP).WithUser(agent.User).WithKey(sshKeyFile)
|
||||
if err := ssh.RunCommand(joinCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("setup agent failed \n================")
|
||||
fmt.Println(err)
|
||||
fmt.Println("================")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetKubeConfig get kubeconfig of k3s cluster
|
||||
func (k *Provisioner) GetKubeConfig() ([]byte, error) {
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
var config []byte
|
||||
getConfigCmd := "cat /etc/rancher/k3s/k3s.yaml\n"
|
||||
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
||||
var outPipe bytes.Buffer
|
||||
if err := ssh.RunCommand(infra.Sudo(getConfigCmd, k.master.User), sshOperator.CommandOptions{
|
||||
Stdout: bufio.NewWriter(&outPipe),
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("setup agent failed \n================")
|
||||
fmt.Println("================")
|
||||
fmt.Println(err)
|
||||
return config, err
|
||||
}
|
||||
return rewriteKubeconfig(outPipe.String(), k.master.IP, "default"), nil
|
||||
}
|
||||
|
||||
func rewriteKubeconfig(kubeconfig string, ip string, context string) []byte {
|
||||
if context == "" {
|
||||
// nolint
|
||||
context = "default"
|
||||
}
|
||||
|
||||
kubeconfigReplacer := strings.NewReplacer(
|
||||
"127.0.0.1", ip,
|
||||
"localhost", ip,
|
||||
"default", context,
|
||||
)
|
||||
|
||||
return []byte(kubeconfigReplacer.Replace(kubeconfig))
|
||||
}
|
||||
|
||||
var _ infra.Provisioner = &Provisioner{}
|
||||
45
infra/k8s/provisioner_test.go
Normal file
45
infra/k8s/provisioner_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProvisioner(t *testing.T) {
|
||||
if os.Getenv("K3S_MASTER_IP") == "" ||
|
||||
os.Getenv("K3S_MASTER_USER") == "" ||
|
||||
os.Getenv("K3S_AGENT_IP") == "" ||
|
||||
os.Getenv("K3S_AGENT_USER") == "" {
|
||||
t.Skip("skip k3s test since K3S_MASTER_IP, K3S_MASTER_USER and K3S_AGENT_IP, K3S_AGENT_USER not ready")
|
||||
}
|
||||
|
||||
master := MasterNode{
|
||||
IP: os.Getenv("K3S_MASTER_IP"),
|
||||
User: os.Getenv("K3S_MASTER_USER"),
|
||||
}
|
||||
agents := []AgentNode{
|
||||
AgentNode{
|
||||
IP: os.Getenv("K3S_AGENT_IP"),
|
||||
User: os.Getenv("K3S_AGENT_USER"),
|
||||
},
|
||||
}
|
||||
k3s := New(master, agents)
|
||||
if err := k3s.SetupMaster(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
kubeconfig, err := k3s.GetKubeConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(kubeconfig))
|
||||
|
||||
if _, err := k3s.getToken(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := k3s.SetupAgent(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package kubernetes
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
@@ -1,4 +1,4 @@
|
||||
package kubernetes
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -27,7 +27,7 @@ func TestK8S(t *testing.T) {
|
||||
if kubeconfig == "" {
|
||||
t.Skip("skip test since no KUBECONFIG given in environment variable")
|
||||
}
|
||||
k8s, err := Create()
|
||||
k8s, err := Create("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
313
infra/mocks/infra.go
Normal file
313
infra/mocks/infra.go
Normal file
@@ -0,0 +1,313 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: infra.go
|
||||
|
||||
// Package mock_infra is a generated GoMock package.
|
||||
package mock_infra
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
types "github.com/metrue/fx/types"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockProvisioner is a mock of Provisioner interface
|
||||
type MockProvisioner struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockProvisionerMockRecorder
|
||||
}
|
||||
|
||||
// MockProvisionerMockRecorder is the mock recorder for MockProvisioner
|
||||
type MockProvisionerMockRecorder struct {
|
||||
mock *MockProvisioner
|
||||
}
|
||||
|
||||
// NewMockProvisioner creates a new mock instance
|
||||
func NewMockProvisioner(ctrl *gomock.Controller) *MockProvisioner {
|
||||
mock := &MockProvisioner{ctrl: ctrl}
|
||||
mock.recorder = &MockProvisionerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockProvisioner) EXPECT() *MockProvisionerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Provision mocks base method
|
||||
func (m *MockProvisioner) Provision() ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Provision")
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Provision indicates an expected call of Provision
|
||||
func (mr *MockProvisionerMockRecorder) Provision() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provision", reflect.TypeOf((*MockProvisioner)(nil).Provision))
|
||||
}
|
||||
|
||||
// HealthCheck mocks base method
|
||||
func (m *MockProvisioner) HealthCheck() (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HealthCheck")
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// HealthCheck indicates an expected call of HealthCheck
|
||||
func (mr *MockProvisionerMockRecorder) HealthCheck() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockProvisioner)(nil).HealthCheck))
|
||||
}
|
||||
|
||||
// MockDeployer is a mock of Deployer interface
|
||||
type MockDeployer struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockDeployerMockRecorder
|
||||
}
|
||||
|
||||
// MockDeployerMockRecorder is the mock recorder for MockDeployer
|
||||
type MockDeployerMockRecorder struct {
|
||||
mock *MockDeployer
|
||||
}
|
||||
|
||||
// NewMockDeployer creates a new mock instance
|
||||
func NewMockDeployer(ctrl *gomock.Controller) *MockDeployer {
|
||||
mock := &MockDeployer{ctrl: ctrl}
|
||||
mock.recorder = &MockDeployerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockDeployer) EXPECT() *MockDeployerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Deploy mocks base method
|
||||
func (m *MockDeployer) Deploy(ctx context.Context, fn, name, image string, bindings []types.PortBinding) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Deploy", ctx, fn, name, image, bindings)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Deploy indicates an expected call of Deploy
|
||||
func (mr *MockDeployerMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockDeployer)(nil).Deploy), ctx, fn, name, image, bindings)
|
||||
}
|
||||
|
||||
// Destroy mocks base method
|
||||
func (m *MockDeployer) Destroy(ctx context.Context, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Destroy", ctx, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Destroy indicates an expected call of Destroy
|
||||
func (mr *MockDeployerMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockDeployer)(nil).Destroy), ctx, name)
|
||||
}
|
||||
|
||||
// Update mocks base method
|
||||
func (m *MockDeployer) Update(ctx context.Context, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Update", ctx, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Update indicates an expected call of Update
|
||||
func (mr *MockDeployerMockRecorder) Update(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDeployer)(nil).Update), ctx, name)
|
||||
}
|
||||
|
||||
// GetStatus mocks base method
|
||||
func (m *MockDeployer) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetStatus", ctx, name)
|
||||
ret0, _ := ret[0].(types.Service)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetStatus indicates an expected call of GetStatus
|
||||
func (mr *MockDeployerMockRecorder) GetStatus(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockDeployer)(nil).GetStatus), ctx, name)
|
||||
}
|
||||
|
||||
// List mocks base method
|
||||
func (m *MockDeployer) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "List", ctx, name)
|
||||
ret0, _ := ret[0].([]types.Service)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// List indicates an expected call of List
|
||||
func (mr *MockDeployerMockRecorder) List(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockDeployer)(nil).List), ctx, name)
|
||||
}
|
||||
|
||||
// Ping mocks base method
|
||||
func (m *MockDeployer) Ping(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Ping", ctx)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Ping indicates an expected call of Ping
|
||||
func (mr *MockDeployerMockRecorder) Ping(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockDeployer)(nil).Ping), ctx)
|
||||
}
|
||||
|
||||
// MockInfra is a mock of Infra interface
|
||||
type MockInfra struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockInfraMockRecorder
|
||||
}
|
||||
|
||||
// MockInfraMockRecorder is the mock recorder for MockInfra
|
||||
type MockInfraMockRecorder struct {
|
||||
mock *MockInfra
|
||||
}
|
||||
|
||||
// NewMockInfra creates a new mock instance
|
||||
func NewMockInfra(ctrl *gomock.Controller) *MockInfra {
|
||||
mock := &MockInfra{ctrl: ctrl}
|
||||
mock.recorder = &MockInfraMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockInfra) EXPECT() *MockInfraMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Provision mocks base method
|
||||
func (m *MockInfra) Provision() ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Provision")
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Provision indicates an expected call of Provision
|
||||
func (mr *MockInfraMockRecorder) Provision() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provision", reflect.TypeOf((*MockInfra)(nil).Provision))
|
||||
}
|
||||
|
||||
// HealthCheck mocks base method
|
||||
func (m *MockInfra) HealthCheck() (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HealthCheck")
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// HealthCheck indicates an expected call of HealthCheck
|
||||
func (mr *MockInfraMockRecorder) HealthCheck() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockInfra)(nil).HealthCheck))
|
||||
}
|
||||
|
||||
// Deploy mocks base method
|
||||
func (m *MockInfra) Deploy(ctx context.Context, fn, name, image string, bindings []types.PortBinding) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Deploy", ctx, fn, name, image, bindings)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Deploy indicates an expected call of Deploy
|
||||
func (mr *MockInfraMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockInfra)(nil).Deploy), ctx, fn, name, image, bindings)
|
||||
}
|
||||
|
||||
// Destroy mocks base method
|
||||
func (m *MockInfra) Destroy(ctx context.Context, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Destroy", ctx, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Destroy indicates an expected call of Destroy
|
||||
func (mr *MockInfraMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockInfra)(nil).Destroy), ctx, name)
|
||||
}
|
||||
|
||||
// Update mocks base method
|
||||
func (m *MockInfra) Update(ctx context.Context, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Update", ctx, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Update indicates an expected call of Update
|
||||
func (mr *MockInfraMockRecorder) Update(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockInfra)(nil).Update), ctx, name)
|
||||
}
|
||||
|
||||
// GetStatus mocks base method
|
||||
func (m *MockInfra) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetStatus", ctx, name)
|
||||
ret0, _ := ret[0].(types.Service)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetStatus indicates an expected call of GetStatus
|
||||
func (mr *MockInfraMockRecorder) GetStatus(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockInfra)(nil).GetStatus), ctx, name)
|
||||
}
|
||||
|
||||
// List mocks base method
|
||||
func (m *MockInfra) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "List", ctx, name)
|
||||
ret0, _ := ret[0].([]types.Service)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// List indicates an expected call of List
|
||||
func (mr *MockInfraMockRecorder) List(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInfra)(nil).List), ctx, name)
|
||||
}
|
||||
|
||||
// Ping mocks base method
|
||||
func (m *MockInfra) Ping(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Ping", ctx)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Ping indicates an expected call of Ping
|
||||
func (mr *MockInfraMockRecorder) Ping(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockInfra)(nil).Ping), ctx)
|
||||
}
|
||||
35
infra/ssh.go
Normal file
35
infra/ssh.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// GetSSHKeyFile get ssh private key file
|
||||
func GetSSHKeyFile() (string, error) {
|
||||
path := os.Getenv("SSH_KEY_FILE")
|
||||
if path != "" {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
key, err := homedir.Expand("~/.ssh/id_rsa")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// GetSSHPort get ssh port
|
||||
func GetSSHPort() string {
|
||||
port := os.Getenv("SSH_PORT")
|
||||
if port != "" {
|
||||
return port
|
||||
}
|
||||
return "22"
|
||||
}
|
||||
50
infra/ssh_test.go
Normal file
50
infra/ssh_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
func TestGetSSHKeyFile(t *testing.T) {
|
||||
t.Run("defaut", func(t *testing.T) {
|
||||
defau, err := GetSSHKeyFile()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defaultPath, _ := homedir.Expand("~/.ssh/id_rsa")
|
||||
if defau != defaultPath {
|
||||
t.Fatalf("should get %s but got %s", defaultPath, defau)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("override from env", func(t *testing.T) {
|
||||
os.Setenv("SSH_KEY_FILE", "/tmp/id_rsa")
|
||||
keyFile, err := GetSSHKeyFile()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if keyFile != "/tmp/id_rsa" {
|
||||
t.Fatalf("should get %s but got %s", "/tmp/id_rsa", keyFile)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSSHPort(t *testing.T) {
|
||||
t.Run("defaut", func(t *testing.T) {
|
||||
defau := GetSSHPort()
|
||||
if defau != "22" {
|
||||
t.Fatalf("should get %s but got %s", "22", defau)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("override from env", func(t *testing.T) {
|
||||
os.Setenv("SSH_PORT", "2222")
|
||||
defau := GetSSHPort()
|
||||
if defau != "2222" {
|
||||
t.Fatalf("should get %s but got %s", "2222", defau)
|
||||
}
|
||||
})
|
||||
}
|
||||
9
infra/sudo.go
Normal file
9
infra/sudo.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package infra
|
||||
|
||||
// Sudo append sudo when user is not root
|
||||
func Sudo(cmd string, user string) string {
|
||||
if user == "root" {
|
||||
return cmd
|
||||
}
|
||||
return "sudo " + cmd
|
||||
}
|
||||
5
k3s.cluster.json
Normal file
5
k3s.cluster.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"master": "52.78.196.250",
|
||||
"agents": [
|
||||
"13.125.243.192",
|
||||
],
|
||||
@@ -1,17 +1,36 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/phayes/freeport"
|
||||
)
|
||||
|
||||
// PortRange usable port range https: //en.wikipedia.org/wiki/Ephemeral_port
|
||||
var PortRange = struct {
|
||||
min int
|
||||
max int
|
||||
}{
|
||||
min: 1023,
|
||||
max: 65535,
|
||||
}
|
||||
|
||||
// Binding create bindings
|
||||
func Binding(ctx *context.Context) error {
|
||||
cli := ctx.GetCliContext()
|
||||
port := cli.Int("port")
|
||||
func Binding(ctx context.Contexter) (err error) {
|
||||
port := ctx.Get("port").(int)
|
||||
if port == 0 {
|
||||
port, err = freeport.GetFreePort()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if port < PortRange.min || port > PortRange.max {
|
||||
return fmt.Errorf("invalid port number: %d, port number should in range of %d - %d", port, PortRange.min, PortRange.max)
|
||||
}
|
||||
|
||||
var bindings []types.PortBinding
|
||||
if os.Getenv("KUBECONFIG") != "" {
|
||||
|
||||
20
middlewares/binding_test.go
Normal file
20
middlewares/binding_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
mockCtx "github.com/metrue/fx/context/mocks"
|
||||
)
|
||||
|
||||
func TestBinding(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ctx := mockCtx.NewMockContexter(ctrl)
|
||||
ctx.EXPECT().Get("port").Return(0)
|
||||
ctx.EXPECT().Set("bindings", gomock.Any())
|
||||
if err := Binding(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -5,51 +5,85 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/otiai10/copy"
|
||||
)
|
||||
|
||||
// Build image
|
||||
func Build(ctx *context.Context) (err error) {
|
||||
func Build(ctx context.Contexter) (err error) {
|
||||
const task = "building"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
cli := ctx.GetCliContext()
|
||||
name := cli.String("name")
|
||||
fn := ctx.Get("fn").(types.Func)
|
||||
docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
||||
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
|
||||
defer os.RemoveAll(workdir)
|
||||
|
||||
if err := packer.PackIntoDir(fn, workdir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := docker.BuildImage(ctx.Context, workdir, name); err != nil {
|
||||
return err
|
||||
// Cases supports
|
||||
// 1. a single file function
|
||||
// fx up func.js
|
||||
// 2. a directory with Docker in it
|
||||
// fx up ./func/
|
||||
// 3. a directory without Dockerfile in it, but has fx handle function file
|
||||
// 4. a fx handlefunction file and its dependencies files or/and directory
|
||||
// fx up func.js helper.js ./lib/
|
||||
|
||||
// When only one directory given and there is a Dockerfile in given directory, treat it as a containerized project and skip packing
|
||||
sources := ctx.Get("sources").([]string)
|
||||
|
||||
if len(sources) == 0 {
|
||||
return fmt.Errorf("source file/directory of function required")
|
||||
}
|
||||
|
||||
nameWithTag := name + ":latest"
|
||||
if err := docker.TagImage(ctx, name, nameWithTag); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("image", nameWithTag)
|
||||
|
||||
if os.Getenv("K3S") != "" {
|
||||
name := cli.String("name")
|
||||
username := os.Getenv("DOCKER_USERNAME")
|
||||
password := os.Getenv("DOCKER_PASSWORD")
|
||||
if username != "" && password != "" {
|
||||
if _, err := docker.PushImage(ctx.Context, name); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("image", username+"/"+name)
|
||||
if len(sources) == 1 &&
|
||||
utils.IsDir(sources[0]) &&
|
||||
utils.HasDockerfile(sources[0]) {
|
||||
if err := copy.Copy(sources[0], workdir); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := packer.Pack(workdir, sources...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cloudType := ctx.Get("cloud_type").(string)
|
||||
name := ctx.Get("name").(string)
|
||||
if cloudType == config.CloudTypeK8S && os.Getenv("K3S") == "" {
|
||||
data, err := packer.PackIntoK8SConfigMapFile(workdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("data", data)
|
||||
} else {
|
||||
docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
||||
if err := docker.BuildImage(ctx.GetContext(), workdir, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nameWithTag := name + ":latest"
|
||||
if err := docker.TagImage(ctx.GetContext(), name, nameWithTag); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("image", nameWithTag)
|
||||
|
||||
if os.Getenv("K3S") != "" {
|
||||
username := os.Getenv("DOCKER_USERNAME")
|
||||
password := os.Getenv("DOCKER_PASSWORD")
|
||||
if username != "" && password != "" {
|
||||
if _, err := docker.PushImage(ctx.GetContext(), name); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("image", username+"/"+name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
16
middlewares/load_config.go
Normal file
16
middlewares/load_config.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/context"
|
||||
)
|
||||
|
||||
// LoadConfig load default config
|
||||
func LoadConfig(ctx context.Contexter) error {
|
||||
config, err := config.LoadDefault()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("config", config)
|
||||
return nil
|
||||
}
|
||||
@@ -1,35 +1,64 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Parse parse input
|
||||
func Parse(ctx *context.Context) (err error) {
|
||||
const task = "parsing"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
func Parse(action string) func(ctx context.Contexter) (err error) {
|
||||
return func(ctx context.Contexter) error {
|
||||
cli := ctx.GetCliContext()
|
||||
switch action {
|
||||
case "up":
|
||||
sources := []string{}
|
||||
for _, s := range cli.Args() {
|
||||
sources = append(sources, s)
|
||||
}
|
||||
ctx.Set("sources", sources)
|
||||
name := cli.String("name")
|
||||
ctx.Set("name", name)
|
||||
port := cli.Int("port")
|
||||
ctx.Set("port", port)
|
||||
case "down":
|
||||
services := cli.Args()
|
||||
if len(services) == 0 {
|
||||
return fmt.Errorf("service name required")
|
||||
}
|
||||
svc := []string{}
|
||||
for _, service := range services {
|
||||
svc = append(svc, service)
|
||||
}
|
||||
ctx.Set("services", svc)
|
||||
case "list":
|
||||
name := cli.Args().First()
|
||||
ctx.Set("filter", name)
|
||||
case "image_build":
|
||||
sources := []string{}
|
||||
for _, s := range cli.Args() {
|
||||
sources = append(sources, s)
|
||||
}
|
||||
ctx.Set("sources", sources)
|
||||
tag := cli.String("tag")
|
||||
if tag == "" {
|
||||
tag = uuid.New().String()
|
||||
}
|
||||
ctx.Set("tag", tag)
|
||||
case "image_export":
|
||||
sources := []string{}
|
||||
for _, s := range cli.Args() {
|
||||
sources = append(sources, s)
|
||||
}
|
||||
ctx.Set("sources", sources)
|
||||
outputDir := cli.String("output")
|
||||
if outputDir == "" {
|
||||
return fmt.Errorf("output directory required")
|
||||
}
|
||||
ctx.Set("output", outputDir)
|
||||
}
|
||||
|
||||
cli := ctx.GetCliContext()
|
||||
funcFile := cli.Args().First()
|
||||
lang := utils.GetLangFromFileName(funcFile)
|
||||
body, err := ioutil.ReadFile(funcFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read source failed")
|
||||
return nil
|
||||
}
|
||||
fn := types.Func{
|
||||
Language: lang,
|
||||
Source: string(body),
|
||||
}
|
||||
ctx.Set("fn", fn)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
66
middlewares/provision.go
Normal file
66
middlewares/provision.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/infra"
|
||||
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||
k8sInfra "github.com/metrue/fx/infra/k8s"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Provision make sure infrastructure is healthy
|
||||
func Provision(ctx context.Contexter) (err error) {
|
||||
fxConfig := ctx.Get("config").(*config.Config)
|
||||
cloud := fxConfig.Clouds[fxConfig.CurrentCloud]
|
||||
|
||||
var deployer infra.Deployer
|
||||
if os.Getenv("KUBECONFIG") != "" {
|
||||
deployer, err = k8sInfra.CreateDeployer(os.Getenv("KUBECONFIG"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("cloud_type", config.CloudTypeK8S)
|
||||
} else if cloud["type"] == config.CloudTypeDocker {
|
||||
provisioner := dockerInfra.CreateProvisioner(cloud["host"], cloud["user"])
|
||||
ok, err := provisioner.HealthCheck()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
if _, err := provisioner.Provision(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
docker, err := dockerHTTP.Create(cloud["host"], constants.AgentPort)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "please make sure docker is installed and running on your host")
|
||||
}
|
||||
|
||||
// TODO should clean up, but it needed in middlewares.Build
|
||||
ctx.Set("docker", docker)
|
||||
deployer, err = dockerInfra.CreateDeployer(docker)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("cloud_type", config.CloudTypeDocker)
|
||||
} else if cloud["type"] == config.CloudTypeK8S {
|
||||
deployer, err = k8sInfra.CreateDeployer(cloud["kubeconfig"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("cloud_type", config.CloudTypeK8S)
|
||||
} else {
|
||||
return fmt.Errorf("unsupport cloud type %s, please make sure you config is correct", cloud["type"])
|
||||
}
|
||||
|
||||
ctx.Set("deployer", deployer)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/constants"
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
|
||||
dockerSDK "github.com/metrue/fx/container_runtimes/docker/sdk"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/deploy"
|
||||
dockerDeployer "github.com/metrue/fx/deploy/docker"
|
||||
k3sDeployer "github.com/metrue/fx/deploy/k3s"
|
||||
k8sDeployer "github.com/metrue/fx/deploy/kubernetes"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/provision"
|
||||
)
|
||||
|
||||
// Setup create k8s or docker cli
|
||||
func Setup(ctx *context.Context) (err error) {
|
||||
const task = "setup"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
|
||||
user := os.Getenv("DOCKER_REMOTE_HOST_USER")
|
||||
passord := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD")
|
||||
var docker containerruntimes.ContainerRuntime
|
||||
if host != "" && user != "" {
|
||||
provisioner := provision.NewWithHost(host, user, passord)
|
||||
if !provisioner.IsFxAgentRunning() {
|
||||
if err := provisioner.StartFxAgent(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
docker, err = dockerHTTP.Create(host, constants.AgentPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
docker, err = dockerSDK.CreateClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ctx.Set("docker", docker)
|
||||
|
||||
var deployer deploy.Deployer
|
||||
if os.Getenv("K3S") != "" {
|
||||
deployer, err = k3sDeployer.Create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if os.Getenv("KUBECONFIG") != "" {
|
||||
deployer, err = k8sDeployer.Create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
deployer, err = dockerDeployer.CreateClient(ctx.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ctx.Set("deployer", deployer)
|
||||
|
||||
return nil
|
||||
}
|
||||
14
packer/doc.go
Normal file
14
packer/doc.go
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
|
||||
Packer takes source codes of a function, and pack them into a containerized service, that means there is Dockerfile generated in the output directory
|
||||
|
||||
e.g.
|
||||
|
||||
Pack(output, "hello.js") # a single file function
|
||||
Pack(output, "hello.js", "helper.js") # multiple files function
|
||||
Pack(output, "./func/") # a directory of function
|
||||
Pack(output, "hello.js", "./func/") # a directory and files of function
|
||||
|
||||
*/
|
||||
|
||||
package packer
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestPacker(t *testing.T) {
|
||||
func TestDockerPacker(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
|
||||
5
packer/fixture/p1/Dockerfile
Normal file
5
packer/fixture/p1/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
||||
FROM metrue/fx-node-base
|
||||
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["node", "app.js"]
|
||||
9
packer/fixture/p1/app.js
Normal file
9
packer/fixture/p1/app.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const Koa = require('koa');
|
||||
const bodyParser = require('koa-bodyparser');
|
||||
const fx = require('./fx');
|
||||
|
||||
const app = new Koa();
|
||||
app.use(bodyParser());
|
||||
app.use(fx);
|
||||
|
||||
app.listen(3000);
|
||||
3
packer/fixture/p1/fx.js
Normal file
3
packer/fixture/p1/fx.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (ctx) => {
|
||||
ctx.body = 'hello world'
|
||||
}
|
||||
3
packer/fixture/p2/fx.js
Normal file
3
packer/fixture/p2/fx.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = (ctx) => {
|
||||
ctx.body = 'hello world'
|
||||
}
|
||||
6
packer/fixture/p3/fx.js
Normal file
6
packer/fixture/p3/fx.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const say = require('./helper')
|
||||
|
||||
module.exports = (ctx) => {
|
||||
say("hi")
|
||||
ctx.body = 'hello world'
|
||||
}
|
||||
5
packer/fixture/p3/helper.js
Normal file
5
packer/fixture/p3/helper.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const say = (msg) => {
|
||||
console.log(msg)
|
||||
}
|
||||
|
||||
module.exports = say
|
||||
234
packer/packer.go
234
packer/packer.go
@@ -1,59 +1,218 @@
|
||||
package packer
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/gobuffalo/packr"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/otiai10/copy"
|
||||
)
|
||||
|
||||
// Packer interface
|
||||
type Packer interface {
|
||||
Pack(serviceName string, fn types.Func) (types.Project, error)
|
||||
var presets packr.Box
|
||||
|
||||
func init() {
|
||||
presets = packr.NewBox("./images")
|
||||
}
|
||||
|
||||
// Pack a function to be a docker project which is web service, handle the imcome request with given function
|
||||
func Pack(svcName string, fn types.Func) (types.Project, error) {
|
||||
box := packr.NewBox("./images")
|
||||
pkr := NewDockerPacker(box)
|
||||
return pkr.Pack(svcName, fn)
|
||||
}
|
||||
// Pack pack a file or directory into a Docker project
|
||||
func Pack(output string, input ...string) error {
|
||||
if len(input) == 0 {
|
||||
return fmt.Errorf("source file or directory required")
|
||||
}
|
||||
|
||||
// PackIntoDir pack service code into directory
|
||||
func PackIntoDir(fn types.Func, outputDir string) error {
|
||||
project, err := Pack("", fn)
|
||||
if err != nil {
|
||||
var lang string
|
||||
for _, f := range input {
|
||||
if utils.IsRegularFile(f) {
|
||||
lang = langFromFileName(f)
|
||||
} else if utils.IsDir(f) {
|
||||
if err := filepath.Walk(f, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if utils.IsRegularFile(path) {
|
||||
lang = langFromFileName(path)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if lang == "" {
|
||||
return fmt.Errorf("could not tell programe language of your input source codes")
|
||||
}
|
||||
|
||||
if err := restore(output, lang); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range project.Files {
|
||||
tmpfn := filepath.Join(outputDir, file.Path)
|
||||
if err := utils.EnsureFile(tmpfn); err != nil {
|
||||
|
||||
if len(input) == 1 {
|
||||
stat, err := os.Stat(input[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(tmpfn, []byte(file.Body), 0666); err != nil {
|
||||
return err
|
||||
if stat.Mode().IsRegular() {
|
||||
if err := filepath.Walk(output, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isHandler(path) {
|
||||
if err := copy.Copy(input[0], path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if !hasFxHandleFile(input...) {
|
||||
msg := `it requires a fx handle file when input is not a single file function, e.g.
|
||||
fx.go for Golang
|
||||
Fx.java for Java
|
||||
fx.php for PHP
|
||||
fx.py for Python
|
||||
fx.js for JavaScript or Node
|
||||
fx.rb for Ruby
|
||||
fx.jl for Julia
|
||||
fx.d for D`
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
if err := merge(output, input...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func restore(output string, lang string) error {
|
||||
for _, name := range presets.List() {
|
||||
prefix := fmt.Sprintf("%s/", lang)
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
content, err := presets.FindString(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filePath := filepath.Join(output, strings.Replace(name, prefix, "", 1))
|
||||
if err := utils.EnsureFile(filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(filePath, []byte(content), 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackIntoK8SConfigMapFile pack function a K8S config map file
|
||||
func PackIntoK8SConfigMapFile(fn types.Func) (string, error) {
|
||||
project, err := Pack("", fn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
func merge(dest string, input ...string) error {
|
||||
for _, file := range input {
|
||||
stat, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stat.Mode().IsRegular() {
|
||||
targetFilePath := filepath.Join(dest, stat.Name())
|
||||
if err := utils.EnsureFile(targetFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(targetFilePath, body, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if stat.Mode().IsDir() {
|
||||
if err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := copy.Copy(file, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
tree := map[string]string{}
|
||||
for _, file := range project.Files {
|
||||
tree[file.Path] = file.Body
|
||||
return nil
|
||||
}
|
||||
|
||||
func langFromFileName(fileName string) string {
|
||||
extLangMap := map[string]string{
|
||||
".js": "node",
|
||||
".go": "go",
|
||||
".rb": "ruby",
|
||||
".py": "python",
|
||||
".php": "php",
|
||||
".jl": "julia",
|
||||
".java": "java",
|
||||
".d": "d",
|
||||
".rs": "rust",
|
||||
}
|
||||
return extLangMap[filepath.Ext(fileName)]
|
||||
}
|
||||
|
||||
func hasFxHandleFile(input ...string) bool {
|
||||
var handleFile string
|
||||
for _, file := range input {
|
||||
if utils.IsRegularFile(file) && isHandler(file) {
|
||||
handleFile = file
|
||||
break
|
||||
} else if utils.IsDir(file) {
|
||||
if err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if utils.IsRegularFile(path) && isHandler(info.Name()) {
|
||||
handleFile = path
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handleFile != ""
|
||||
}
|
||||
|
||||
// PackIntoK8SConfigMapFile pack function a K8S config map file
|
||||
func PackIntoK8SConfigMapFile(dir string) (string, error) {
|
||||
tree := map[string]string{}
|
||||
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
relpath := strings.Replace(path, dir, "", 1)
|
||||
body, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tree[relpath] = string(body)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return "", nil
|
||||
}
|
||||
data, err := json.Marshal(tree)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -74,18 +233,3 @@ func TreeToDir(tree map[string]string, outputDir string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackIntoTar pack service code into directory
|
||||
func PackIntoTar(fn types.Func, path string) error {
|
||||
tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tarDir)
|
||||
|
||||
if err := PackIntoDir(fn, tarDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utils.TarDir(tarDir, path)
|
||||
}
|
||||
|
||||
@@ -1,80 +1,182 @@
|
||||
package packer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
func TestPack(t *testing.T) {
|
||||
mockSource := `
|
||||
module.exports = ({a, b}) => {
|
||||
return a + b
|
||||
}
|
||||
`
|
||||
fn := types.Func{
|
||||
Language: "node",
|
||||
Source: mockSource,
|
||||
}
|
||||
func TestPacker(t *testing.T) {
|
||||
t.Run("Pack directory with Dockerfile in it", func(t *testing.T) {
|
||||
input := "./fixture/p1"
|
||||
output := "output-1"
|
||||
defer func() {
|
||||
os.RemoveAll(output)
|
||||
}()
|
||||
if err := Pack(output, input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
serviceName := "service-mock"
|
||||
project, err := Pack(serviceName, fn)
|
||||
t.Run("Pack directory only fx.js in it", func(t *testing.T) {
|
||||
input := "./fixture/p2"
|
||||
output := "output-2"
|
||||
defer func() {
|
||||
os.RemoveAll(output)
|
||||
}()
|
||||
if err := Pack(output, input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Pack directory with fx.js and helper in it", func(t *testing.T) {
|
||||
input := "./fixture/p3"
|
||||
output := "output-3"
|
||||
defer func() {
|
||||
os.RemoveAll(output)
|
||||
}()
|
||||
if err := Pack(output, input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Pack files list with fx.js in it", func(t *testing.T) {
|
||||
handleFile := "./fixture/p3/fx.js"
|
||||
helperFile := "./fixture/p3/helper.js"
|
||||
output := "output-4"
|
||||
defer func() {
|
||||
os.RemoveAll(output)
|
||||
}()
|
||||
if err := Pack(output, handleFile, helperFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Pack files list without fx.js in it", func(t *testing.T) {
|
||||
f1 := "./fixture/p3/helper.js"
|
||||
f2 := "./fixture/p3/helper.js"
|
||||
output := "output-5"
|
||||
defer func() {
|
||||
os.RemoveAll(output)
|
||||
}()
|
||||
if err := Pack(output, f1, f2); err == nil {
|
||||
t.Fatalf("should report error when there is not Dockerfile or fx.[ext] in it")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTreeAndUnTree(t *testing.T) {
|
||||
_, err := PackIntoK8SConfigMapFile("./fixture/p1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if project.Name != serviceName {
|
||||
t.Fatalf("should get %s but got %s", serviceName, project.Name)
|
||||
func TestGenerate(t *testing.T) {
|
||||
langs := []string{
|
||||
"d",
|
||||
"go",
|
||||
"java",
|
||||
"julia",
|
||||
"node",
|
||||
"php",
|
||||
"python",
|
||||
"ruby",
|
||||
"rust",
|
||||
}
|
||||
|
||||
if project.Language != "node" {
|
||||
t.Fatal("incorrect Language")
|
||||
}
|
||||
|
||||
if len(project.Files) != 3 {
|
||||
t.Fatal("node project should have 3 files")
|
||||
}
|
||||
|
||||
for _, file := range project.Files {
|
||||
if file.Path == "fx.js" {
|
||||
if file.IsHandler == false {
|
||||
t.Fatal("fx.js should be handler")
|
||||
}
|
||||
if file.Body != mockSource {
|
||||
t.Fatalf("should get %s but got %v", mockSource, file.Body)
|
||||
}
|
||||
} else if file.Path == "Dockerfile" {
|
||||
if file.IsHandler == true {
|
||||
t.Fatalf("should get %v but got %v", false, file.IsHandler)
|
||||
}
|
||||
} else {
|
||||
if file.IsHandler == true {
|
||||
t.Fatalf("should get %v but %v", false, file.IsHandler)
|
||||
}
|
||||
for _, lang := range langs {
|
||||
output := fmt.Sprintf("output-%s-%d", lang, time.Now().Unix())
|
||||
defer func() {
|
||||
os.RemoveAll(output)
|
||||
}()
|
||||
if err := restore(output, lang); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
diffCmd := exec.Command("diff", "-r", output, "./images/"+lang)
|
||||
if stdoutStderr, err := diffCmd.CombinedOutput(); err != nil {
|
||||
fmt.Printf("%s\n", stdoutStderr)
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeAndUnTree(t *testing.T) {
|
||||
mockSource := `
|
||||
package fx;
|
||||
func TestMerge(t *testing.T) {
|
||||
// TODO should check the merge result
|
||||
t.Run("NoInput", func(t *testing.T) {
|
||||
dest := "./dest"
|
||||
_ = utils.EnsureDir("./dest")
|
||||
defer func() {
|
||||
os.RemoveAll(dest)
|
||||
}()
|
||||
|
||||
import org.json.JSONObject;
|
||||
if err := merge(dest); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
public class Fx {
|
||||
public int handle(JSONObject input) {
|
||||
String a = input.get("a").toString();
|
||||
String b = input.get("b").toString();
|
||||
return Integer.parseInt(a) + Integer.parseInt(b);
|
||||
}
|
||||
}
|
||||
`
|
||||
fn := types.Func{
|
||||
Language: "java",
|
||||
Source: mockSource,
|
||||
}
|
||||
_, err := PackIntoK8SConfigMapFile(fn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Run("Files", func(t *testing.T) {
|
||||
dest := "./dest"
|
||||
_ = utils.EnsureDir("./dest")
|
||||
defer func() {
|
||||
os.RemoveAll(dest)
|
||||
}()
|
||||
|
||||
f1, err := ioutil.TempFile("", "fx.*.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f1.Name())
|
||||
|
||||
f2, err := ioutil.TempFile("", "fx.*.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f2.Name())
|
||||
|
||||
if err := merge(dest, f1.Name(), f2.Name()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Directories", func(t *testing.T) {
|
||||
dest := "./dest"
|
||||
_ = utils.EnsureDir("./dest")
|
||||
defer func() {
|
||||
os.RemoveAll(dest)
|
||||
}()
|
||||
|
||||
if err := merge(dest, "./fixture/p1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Files and Directories", func(t *testing.T) {
|
||||
dest := "./dest"
|
||||
_ = utils.EnsureDir("./dest")
|
||||
defer func() {
|
||||
os.RemoveAll(dest)
|
||||
}()
|
||||
|
||||
f1, err := ioutil.TempFile("", "fx.*.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f1.Name())
|
||||
|
||||
f2, err := ioutil.TempFile("", "fx.*.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f2.Name())
|
||||
|
||||
if err := merge(dest, "./fixture/p1", f1.Name(), f2.Name()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
@@ -24,8 +26,13 @@ func NewRemoteRunner(sshClient ssh.Client) *RemoteRunner {
|
||||
|
||||
// Run script on remote host
|
||||
func (r *RemoteRunner) Run(script string) ([]byte, error) {
|
||||
stdout, stderr, err := r.sshClient.RunCommand(script)
|
||||
output := string(stdout) + string(stderr)
|
||||
var outPipe bytes.Buffer
|
||||
var errPipe bytes.Buffer
|
||||
err := r.sshClient.RunCommand(script, ssh.CommandOptions{
|
||||
Stdout: bufio.NewWriter(&outPipe),
|
||||
Stderr: bufio.NewWriter(&errPipe),
|
||||
})
|
||||
output := outPipe.String() + errPipe.String()
|
||||
return []byte(output), err
|
||||
}
|
||||
|
||||
|
||||
27
pkg/render/render.go
Normal file
27
pkg/render/render.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
// Table output services as table format
|
||||
func Table(services []types.Service) {
|
||||
data := [][]string{}
|
||||
for _, s := range services {
|
||||
col := []string{
|
||||
s.ID,
|
||||
s.Name,
|
||||
fmt.Sprintf("%s:%d", s.Host, +s.Port),
|
||||
}
|
||||
data = append(data, col)
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"ID", "Name", "Endpoint"})
|
||||
table.AppendBulk(data)
|
||||
table.Render()
|
||||
}
|
||||
19
pkg/render/render_test.go
Normal file
19
pkg/render/render_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestTable(t *testing.T) {
|
||||
services := []types.Service{
|
||||
types.Service{
|
||||
ID: "id-1",
|
||||
Name: "name-1",
|
||||
Host: "127.0.0.1",
|
||||
Port: 1000,
|
||||
},
|
||||
}
|
||||
Table(services)
|
||||
}
|
||||
@@ -44,9 +44,6 @@ func Start(task string) {
|
||||
func Stop(task string, err error) {
|
||||
if err != nil {
|
||||
fmt.Println(aurora.Red("\u2717"))
|
||||
fmt.Println(aurora.Red("*****************"))
|
||||
fmt.Println(err)
|
||||
fmt.Println(aurora.Red("*****************"))
|
||||
}
|
||||
s.Stop()
|
||||
}
|
||||
|
||||
20
pkg/spinner/spiner_test.go
Normal file
20
pkg/spinner/spiner_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package spinner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSpinner(t *testing.T) {
|
||||
t.Run("failure", func(t *testing.T) {
|
||||
Start("task 2")
|
||||
time.Sleep(1 * time.Second)
|
||||
Stop("task 2", fmt.Errorf("error happened"))
|
||||
})
|
||||
t.Run("success", func(t *testing.T) {
|
||||
Start("task 1")
|
||||
time.Sleep(1 * time.Second)
|
||||
Stop("task 1", nil)
|
||||
})
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package provision
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/pkg/command"
|
||||
ssh "github.com/metrue/go-ssh-client"
|
||||
)
|
||||
|
||||
// Provisioner provision
|
||||
type Provisioner interface {
|
||||
Start() error
|
||||
}
|
||||
|
||||
// Provisionor provision-or
|
||||
type Provisionor struct {
|
||||
sshClient ssh.Client
|
||||
host string
|
||||
}
|
||||
|
||||
func isLocal(host string) bool {
|
||||
if host == "" {
|
||||
return false
|
||||
}
|
||||
return host == "127.0.0.1" || host == "localhost" || host == "0.0.0.0"
|
||||
}
|
||||
|
||||
// NewWithHost create a provisionor with host, user, and password
|
||||
func NewWithHost(host string, user string, password string) *Provisionor {
|
||||
p := &Provisionor{
|
||||
host: host,
|
||||
}
|
||||
if !isLocal(host) {
|
||||
p.sshClient = ssh.New(host).
|
||||
WithUser(user).
|
||||
WithPassword(password)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// IsFxAgentRunning check if fx-agent is running on host
|
||||
func (p *Provisionor) IsFxAgentRunning() bool {
|
||||
script := fmt.Sprintf("docker inspect %s", constants.AgentContainerName)
|
||||
var cmd *command.Command
|
||||
if !isLocal(p.host) {
|
||||
cmd = command.New("inspect fx-agent", script, command.NewRemoteRunner(p.sshClient))
|
||||
} else {
|
||||
cmd = command.New("inspect fx-agent", script, command.NewLocalRunner())
|
||||
}
|
||||
output, err := cmd.Exec()
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
log.Infof(string(output))
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// StartFxAgent start fx agent
|
||||
func (p *Provisionor) StartFxAgent() error {
|
||||
script := fmt.Sprintf("docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentPort)
|
||||
var cmd *command.Command
|
||||
if !isLocal(p.host) {
|
||||
cmd = command.New("start fx-agent", script, command.NewRemoteRunner(p.sshClient))
|
||||
} else {
|
||||
cmd = command.New("start fx-agent", script, command.NewLocalRunner())
|
||||
}
|
||||
if output, err := cmd.Exec(); err != nil {
|
||||
log.Info(string(output))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopFxAgent stop fx agent
|
||||
func (p *Provisionor) StopFxAgent() error {
|
||||
script := fmt.Sprintf("docker stop %s", constants.AgentContainerName)
|
||||
var cmd *command.Command
|
||||
if !isLocal(p.host) {
|
||||
cmd = command.New("stop fx agent", script, command.NewRemoteRunner(p.sshClient))
|
||||
} else {
|
||||
cmd = command.New("stop fx agent", script, command.NewLocalRunner())
|
||||
}
|
||||
if output, err := cmd.Exec(); err != nil {
|
||||
log.Infof(string(output))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start start provision progress
|
||||
func (p *Provisionor) Start() error {
|
||||
scripts := map[string]string{
|
||||
"pull java Docker base image": "docker pull metrue/fx-java-base",
|
||||
"pull julia Docker base image": "docker pull metrue/fx-julia-base",
|
||||
"pull python Docker base iamge": "docker pull metrue/fx-python-base",
|
||||
"pull node Docker base image": "docker pull metrue/fx-node-base",
|
||||
"pull d Docker base image": "docker pull metrue/fx-d-base",
|
||||
"pull go Docker base image": "docker pull metrue/fx-go-base",
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for n, s := range scripts {
|
||||
wg.Add(1)
|
||||
go func(name, script string) {
|
||||
var cmd *command.Command
|
||||
if !isLocal(p.host) {
|
||||
cmd = command.New(name, script, command.NewRemoteRunner(p.sshClient))
|
||||
} else {
|
||||
cmd = command.New(name, script, command.NewLocalRunner())
|
||||
}
|
||||
if _, err := cmd.Exec(); err != nil {
|
||||
log.Fatalf("Provision:%s: %s", cmd.Name, err)
|
||||
} else {
|
||||
log.Infof("Provision:%s: \u2713", cmd.Name)
|
||||
}
|
||||
wg.Done()
|
||||
}(n, s)
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package provision
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestProvisionWorkflow(t *testing.T) {
|
||||
provisionor := NewWithHost("127.0.0.1", "", "")
|
||||
|
||||
_ = provisionor.StopFxAgent()
|
||||
// TODO wait too long here to make test pass
|
||||
time.Sleep(40 * time.Second)
|
||||
|
||||
running := provisionor.IsFxAgentRunning()
|
||||
if running {
|
||||
t.Fatalf("fx-agent should not be running")
|
||||
}
|
||||
|
||||
if err := provisionor.StartFxAgent(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
running = provisionor.IsFxAgentRunning()
|
||||
if !running {
|
||||
t.Fatalf("fx-agent should be running")
|
||||
}
|
||||
|
||||
if err := provisionor.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := provisionor.StopFxAgent(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ echo "mode: atomic\n" > coverage.txt
|
||||
for d in `go list ./... | grep -v 'mocks\|images\|examples\|assets'`; do
|
||||
echo $d
|
||||
go test -race -coverprofile=profile.out -covermode=atomic $d
|
||||
if [ $? -ne 0 ];then
|
||||
exit 1
|
||||
fi
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out | grep -v "mode: atomic" >> coverage.txt
|
||||
rm profile.out
|
||||
|
||||
@@ -5,7 +5,8 @@ sudo apt-get remove -y docker docker-engine docker.io containerd runc
|
||||
apt-get update -y
|
||||
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common lsb-core curl
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
|
||||
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu \$(lsb_release -cs) stable"
|
||||
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7EA0A9C3F273FCD8
|
||||
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y docker-ce
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user