Compare commits
60 Commits
0.7.2-alph
...
0.8.73
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2298f39cca | ||
|
|
23d68bc27b | ||
|
|
74c0423f0d | ||
|
|
06f87c4d8e | ||
|
|
35262de828 | ||
|
|
34a495984c | ||
|
|
d7130c4e28 | ||
|
|
c9630a53c3 | ||
|
|
0522690472 | ||
|
|
a8a0fbed32 | ||
|
|
26ae9585f6 | ||
|
|
b69bd699c8 | ||
|
|
650ee5f63a | ||
|
|
e3c60cbb77 | ||
|
|
0daca43d10 | ||
|
|
d3c239dc54 | ||
|
|
05ac2441da | ||
|
|
c0009b1b64 | ||
|
|
82960824ef | ||
|
|
64b63cbd0f | ||
|
|
05771fb07f | ||
|
|
d1f680dacd | ||
|
|
14c9397b70 | ||
|
|
eb5e724899 | ||
|
|
80619bd800 | ||
|
|
8e2cdfc607 | ||
|
|
58f416b7b2 | ||
|
|
b6cf39e3e5 | ||
|
|
41bc98ab64 | ||
|
|
b007ac315a | ||
|
|
940f6b8f72 | ||
|
|
f9690b74a5 | ||
|
|
f2c58d545a | ||
|
|
4732426629 | ||
|
|
d4af4f67b2 | ||
|
|
6420e8b6c6 | ||
|
|
15c59fa31f | ||
|
|
294131b48f | ||
|
|
48413abaa1 | ||
|
|
d36b2b935b | ||
|
|
f493749689 | ||
|
|
9de10bc885 | ||
|
|
2d5446686a | ||
|
|
0d7d4f4a6a | ||
|
|
23c4171ece | ||
|
|
d8a1868fce | ||
|
|
d91a9959a8 | ||
|
|
87e7c7d6ae | ||
|
|
89c94daebc | ||
|
|
047fac2a0a | ||
|
|
1cb68766f7 | ||
|
|
91fd5dc59f | ||
|
|
184235acb2 | ||
|
|
aa49a59feb | ||
|
|
c9d382d903 | ||
|
|
81e18e5b0d | ||
|
|
3882f843bf | ||
|
|
293481f081 | ||
|
|
c12d967ced | ||
|
|
b2a62cbd94 |
@@ -1,73 +0,0 @@
|
||||
defaults: &defaults
|
||||
machine: true
|
||||
environment:
|
||||
IMPORT_PATH: "github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME"
|
||||
OUTPUT_DIR: "./build"
|
||||
DIST_DIR: "./dist"
|
||||
|
||||
install_golang: &install_golang
|
||||
run:
|
||||
name: install Golang 1.11
|
||||
command: |
|
||||
sudo add-apt-repository ppa:gophers/archive
|
||||
sudo apt-get update
|
||||
sudo apt-get install golang-1.11-go
|
||||
alias go="/usr/lib/go-1.11/bin/go"
|
||||
go version
|
||||
|
||||
install_deps: &install_deps
|
||||
run:
|
||||
name: Install deps
|
||||
command: |
|
||||
/usr/lib/go-1.11/bin/go mod vendor
|
||||
/usr/lib/go-1.11/bin/go get -u github.com/gobuffalo/packr/packr
|
||||
|
||||
install_httpie: &install_httpie
|
||||
run:
|
||||
name: install httpie
|
||||
command: |
|
||||
sudo apt-get -y update && sudo apt-get -y install httpie
|
||||
|
||||
install_jq: &install_jq
|
||||
run:
|
||||
name: install jq
|
||||
command: |
|
||||
sudo apt-get update && sudo apt-get -y install jq
|
||||
|
||||
build_binary: &build_binary
|
||||
run:
|
||||
name: build binary
|
||||
command: |
|
||||
/usr/lib/go-1.11/bin/go build -o ${OUTPUT_DIR}/fx fx.go
|
||||
|
||||
unit_test: &unit_test
|
||||
run:
|
||||
name: unit test
|
||||
command: |
|
||||
make unit-test
|
||||
bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}
|
||||
cli_test: &cli_test
|
||||
run:
|
||||
name: cli test
|
||||
command: make cli-test
|
||||
|
||||
version: 2
|
||||
jobs:
|
||||
test:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- *install_golang
|
||||
- *install_deps
|
||||
- *unit_test
|
||||
- *build_binary
|
||||
- run:
|
||||
name: Pull images
|
||||
command: make pull
|
||||
- *cli_test
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
workflow:
|
||||
jobs:
|
||||
- test
|
||||
64
.github/workflows/ci.yml
vendored
64
.github/workflows/ci.yml
vendored
@@ -1,4 +1,4 @@
|
||||
on: push
|
||||
on: [push, pull_request]
|
||||
name: ci
|
||||
jobs:
|
||||
Test:
|
||||
@@ -13,57 +13,67 @@ jobs:
|
||||
- name: check out
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: setup docker
|
||||
- name: kind create a k8s cluster
|
||||
run: |
|
||||
./scripts/provision.sh
|
||||
kind create cluster
|
||||
|
||||
- name: setup k8s and kind
|
||||
- name: lint
|
||||
run: |
|
||||
export GOBIN=$(go env GOPATH)/bin
|
||||
export PATH=$PATH:$GOBIN
|
||||
mkdir -p $GOBIN
|
||||
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
|
||||
chmod +x kubectl && mv kubectl $GOBIN
|
||||
wget https://github.com/kubernetes-sigs/kind/releases/download/v0.5.0/kind-linux-amd64 && chmod +x kind-linux-amd64 && mv kind-linux-amd64 $GOBIN/kind
|
||||
./scripts/setup_kind.sh
|
||||
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint \
|
||||
golangci-lint run -v
|
||||
|
||||
- name: unit test
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: |
|
||||
export KUBECONFIG=/home/runner/.kube/kind-config-fx-test
|
||||
DEBUG=true go test -v ./container_runtimes/... ./deploy/...
|
||||
export KUBECONFIG="$(kind get kubeconfig-path)"
|
||||
./scripts/coverage.sh
|
||||
bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}
|
||||
|
||||
- name: build fx
|
||||
run: |
|
||||
make build
|
||||
|
||||
- name: lint
|
||||
- name: test fx-docker
|
||||
run: |
|
||||
export GOBIN=$(go env GOPATH)/bin
|
||||
export PATH=$PATH:$GOBIN
|
||||
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
golangci-lint run
|
||||
|
||||
cd ./contrib/docker_packer
|
||||
make linux-build
|
||||
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:
|
||||
REMOTE_HOST_ADDR: ${{secrets.DOCKER_REMOTE_HOST_ADDR}}
|
||||
REMOTE_HOST_USER: ${{secrets.DOCKER_REMOTE_HOST_USER}}
|
||||
REMOTE_HOST_PASSWORD: ${{secrets.DOCKER_REMOTE_HOST_PASSWORD}}
|
||||
run: |
|
||||
echo $KUBECONFIG
|
||||
unset KUBECONFIG
|
||||
make cli-test
|
||||
make cli-test-ci
|
||||
|
||||
- name: test AKS
|
||||
env:
|
||||
AKS_KUBECONFIG: ${{ secrets.AKS_KUBECONFIG }}
|
||||
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 deploy -n hello -p 12345 examples/functions/JavaScript/func.js
|
||||
./build/fx destroy hello
|
||||
rm ${KUBECONFIG}
|
||||
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 }}
|
||||
needs: [Test]
|
||||
|
||||
93
.github/workflows/docker.yml
vendored
93
.github/workflows/docker.yml
vendored
@@ -1,57 +1,70 @@
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * *'
|
||||
on: [push]
|
||||
# schedule:
|
||||
# - cron: '0 12 * * *'
|
||||
name: docker
|
||||
jobs:
|
||||
Docker:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
docker_version:
|
||||
- 18.09
|
||||
# - 19.03
|
||||
# - 19.09
|
||||
docker_channel:
|
||||
- stable
|
||||
# - test
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: setup docker
|
||||
uses: docker-practice/actions-setup-docker@master
|
||||
with:
|
||||
docker_version: ${{ matrix.docker_version }}
|
||||
docker_channel: ${{ matrix.docker_channel }}
|
||||
|
||||
- name: login
|
||||
uses: actions/docker/login@8cdf801b322af5f369e00d85e9cf3a7122f49108
|
||||
- name: login docker hub
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
run: |
|
||||
docker login --username $DOCKER_USERNAME --password $DOCKER_PASSWORD
|
||||
|
||||
- name: build-fx-go-image
|
||||
uses: actions/docker/cli@master
|
||||
with:
|
||||
args: build -t metrue/fx-go-base:latest -f api/asserts/dockerfiles/base/go/Dockerfile
|
||||
api/asserts/dockerfiles/base/go
|
||||
- name: build and publish fx d image
|
||||
if: always()
|
||||
run: |
|
||||
docker build -t metrue/fx-d-base:latest -f ./assets/dockerfiles/base/d/Dockerfile ./assets/dockerfiles/base/d
|
||||
docker push metrue/fx-d-base:latest
|
||||
|
||||
- name: push-fx-go-image
|
||||
uses: actions/docker/cli@master
|
||||
# - name: build and publish fx java image
|
||||
# run: |
|
||||
# docker build -t metrue/fx-go-base:latest -f ./assets/dockerfiles/base/java/Dockerfile ./assets/dockerfiles/base/java
|
||||
# docker push metrue/fx-java-base:latest
|
||||
|
||||
- name: build and publish fx node image
|
||||
if: always()
|
||||
run: |
|
||||
docker build -t metrue/fx-node-base:latest -f ./assets/dockerfiles/base/node/Dockerfile ./assets/dockerfiles/base/node
|
||||
docker push metrue/fx-node-base:latest
|
||||
|
||||
- name: build and publish fx python image
|
||||
if: always()
|
||||
run: |
|
||||
docker build -t metrue/fx-python-base:latest -f ./assets/dockerfiles/base/python/Dockerfile ./assets/dockerfiles/base/python
|
||||
- name: publish fx python image
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
args: push metrue/fx-go-base:latest
|
||||
run: |
|
||||
docker push metrue/fx-python-base:latest
|
||||
|
||||
- name: build-fx-rust-image
|
||||
uses: actions/docker/cli@master
|
||||
with:
|
||||
args: build -t metrue/fx-rust-base:latest -f api/asserts/dockerfiles/base/rust/Dockerfile
|
||||
api/asserts/dockerfiles/base/rust
|
||||
# - name: build and publish fx rust image
|
||||
# if: always()
|
||||
# run: |
|
||||
# docker build -t metrue/fx-rust-base:latest -f ./assets/dockerfiles/base/rust/Dockerfile ./assets/dockerfiles/base/python
|
||||
# docker push metrue/fx-rust-base:latest
|
||||
|
||||
- name: push-fx-rust-image
|
||||
uses: actions/docker/cli@master
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
args: push metrue/fx-rust-base:latest
|
||||
|
||||
- name: build-fx-node-image
|
||||
uses: actions/docker/cli@master
|
||||
with:
|
||||
args: build -t metrue/fx-node-base:latest -f api/asserts/dockerfiles/base/node/Dockerfile
|
||||
api/asserts/dockerfiles/base/node
|
||||
|
||||
- name: push-fx-node-image
|
||||
uses: actions/docker/cli@master
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
with:
|
||||
args: push metrue/fx-node-base:latest
|
||||
- name: build and publish fx julia image
|
||||
if: always()
|
||||
run: |
|
||||
docker build -t metrue/fx-julia-base:latest -f ./assets/dockerfiles/base/julia/Dockerfile ./assets/dockerfiles/base/julia
|
||||
docker push metrue/fx-julia-base:latest
|
||||
|
||||
42
.github/workflows/release.yml
vendored
42
.github/workflows/release.yml
vendored
@@ -18,40 +18,27 @@ jobs:
|
||||
- name: check out
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: setup docker
|
||||
- name: kind create a k8s cluster
|
||||
run: |
|
||||
./scripts/provision.sh
|
||||
kind create cluster
|
||||
|
||||
- name: setup k8s and kind
|
||||
- name: lint
|
||||
run: |
|
||||
export GOBIN=$(go env GOPATH)/bin
|
||||
export PATH=$PATH:$GOBIN
|
||||
mkdir -p $GOBIN
|
||||
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
|
||||
chmod +x kubectl && mv kubectl $GOBIN
|
||||
wget https://github.com/kubernetes-sigs/kind/releases/download/v0.5.0/kind-linux-amd64 && chmod +x kind-linux-amd64 && mv kind-linux-amd64 $GOBIN/kind
|
||||
./scripts/setup_kind.sh
|
||||
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint \
|
||||
golangci-lint run -v
|
||||
|
||||
- name: unit test
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
run: |
|
||||
export KUBECONFIG=/home/runner/.kube/kind-config-fx-test
|
||||
DEBUG=true go test -v ./container_runtimes/... ./deploy/...
|
||||
export KUBECONFIG="$(kind get kubeconfig-path)"
|
||||
DEBUG=true go test -v ./...
|
||||
|
||||
- name: build fx
|
||||
run: |
|
||||
make build
|
||||
|
||||
- name: lint
|
||||
run: |
|
||||
export GOBIN=$(go env GOPATH)/bin
|
||||
export PATH=$PATH:$GOBIN
|
||||
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
golangci-lint run
|
||||
|
||||
|
||||
- name: test fx cli
|
||||
run: |
|
||||
echo $KUBECONFIG
|
||||
@@ -64,11 +51,12 @@ jobs:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: |
|
||||
export KUBECONFIG=${HOME}/.kube/aks
|
||||
echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
|
||||
DEBUG=true ./build/fx deploy -n hello -p 12345 examples/functions/JavaScript/func.js
|
||||
./build/fx destroy 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]
|
||||
@@ -97,11 +85,11 @@ jobs:
|
||||
git config --global user.name "Minghe Huang"
|
||||
|
||||
commit=$(git rev-parse --short HEAD)
|
||||
version=$(cat fx.go| grep Version | awk -F'"' '{print $2}')
|
||||
version=$(cat fx.go| grep 'const version' | awk -F'"' '{print $2}')
|
||||
|
||||
echo "workflow is running on branch ${GITHUB_REF}"
|
||||
|
||||
if [[ ${GITHUB_REF} == "refs/heads/production" ]];then
|
||||
if [[ ${GITHUB_REF} == "refs/heads/master" ]];then
|
||||
version=${version}-alpha.${commit}
|
||||
echo "alpha release $version"
|
||||
elif [[ "${GITHUB_REF}" == *--auto-release ]];then
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
run:
|
||||
concurrency: 4
|
||||
deadline: 10m
|
||||
timeout: 10m
|
||||
issues-exit-code: 1
|
||||
@@ -7,21 +6,25 @@ run:
|
||||
skip-dirs:
|
||||
- examples
|
||||
- api/images
|
||||
- test
|
||||
# skip-files:
|
||||
|
||||
- test/functions
|
||||
linters:
|
||||
enable:
|
||||
- megacheck
|
||||
- govet
|
||||
- deadcode
|
||||
# - gocyclo
|
||||
- golint
|
||||
- varcheck
|
||||
- structcheck
|
||||
- errcheck
|
||||
- dupl
|
||||
- ineffassign
|
||||
- goimports
|
||||
- stylecheck
|
||||
- gosec
|
||||
- interfacer
|
||||
- unconvert
|
||||
enable-all: false
|
||||
- goconst
|
||||
- gocyclo
|
||||
- misspell
|
||||
- unparam
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gocyclo
|
||||
- goconst
|
||||
- errcheck
|
||||
- dupl
|
||||
- gosec
|
||||
|
||||
38
Makefile
38
Makefile
@@ -1,5 +1,7 @@
|
||||
OUTPUT_DIR=./build
|
||||
DIST_DIR=./dist
|
||||
OUTPUT_DIR ?=./build
|
||||
DIST_DIR ?=./dist
|
||||
DOCKER_REMOTE_HOST_ADDR ?= "127.0.0.1"
|
||||
DOCKER_REMOTE_HOST_USER ?= $(whoami)
|
||||
|
||||
lint:
|
||||
golangci-lint run
|
||||
@@ -7,6 +9,9 @@ lint:
|
||||
generate:
|
||||
packr
|
||||
|
||||
b:
|
||||
go build -o ${OUTPUT_DIR}/fx fx.go
|
||||
|
||||
build:
|
||||
go build -o ${OUTPUT_DIR}/fx fx.go
|
||||
|
||||
@@ -23,8 +28,11 @@ clean:
|
||||
unit-test:
|
||||
./scripts/coverage.sh
|
||||
|
||||
cli-test-ci:
|
||||
./scripts/test_cli.sh 'js'
|
||||
|
||||
cli-test:
|
||||
./scripts/test_cli.sh
|
||||
./scripts/test_cli.sh 'js rb py go php java d rs'
|
||||
|
||||
http-test:
|
||||
./scripts/http_test.sh
|
||||
@@ -32,3 +40,27 @@ http-test:
|
||||
zip:
|
||||
zip -r images.zip images/
|
||||
.PHONY: test build start list clean generate
|
||||
|
||||
start_docker_infra:
|
||||
docker build -t fx-docker-infra -f test/Dockerfile ./test
|
||||
docker run --rm --name fx-docker-infra -p 2222:22 -v /var/run/docker.sock:/var/run/docker.sock -d fx-docker-infra
|
||||
|
||||
test_docker_infra:
|
||||
CICD=true SSH_PORT=2222 SSH_KEY_FILE=./test/id_rsa ./build/fx infra create --name docker-local -t docker --host root@127.0.0.1
|
||||
|
||||
stop_docker_infra:
|
||||
docker stop fx-docker-infra
|
||||
|
||||
start_k3s_infra:
|
||||
multipass launch --name k3s-master --cpus 1 --mem 512M --disk 3G --cloud-init ./test/k3s/ssh-cloud-init.yaml
|
||||
multipass launch --name k3s-worker1 --cpus 1 --mem 512M --disk 3G --cloud-init ./test/k3s/ssh-cloud-init.yaml
|
||||
multipass launch --name k3s-worker2 --cpus 1 --mem 512M --disk 3G --cloud-init ./test/k3s/ssh-cloud-init.yaml
|
||||
|
||||
test_k3s_infra:
|
||||
./scripts/test_k3s_infra.sh
|
||||
|
||||
stop_k3s_infra:
|
||||
multipass delete k3s-master
|
||||
multipass delete k3s-worker1
|
||||
multipass delete k3s-worker2
|
||||
multipass purge
|
||||
|
||||
208
README.md
208
README.md
@@ -1,10 +1,10 @@
|
||||
fx
|
||||
------
|
||||
|
||||
Poor man's function as a service.
|
||||
<br/>
|
||||

|
||||

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

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

|
||||
@@ -14,12 +14,13 @@ Poor man's function as a service.
|
||||
- [Introduction](#introduction)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Manage Infrastructure](#manage-infrastructure)
|
||||
- [Contribute](#contribute)
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
fx is a tool to help you do Function as a Service on your own server. fx can make your stateless function a service in seconds. The most exciting thing is that you can write your functions with most programming languages.
|
||||
fx is a tool to help you do Function as a Service on your own server, fx can make your stateless function a service in seconds, both Docker host and Kubernetes cluster supported. The most exciting thing is that you can write your functions with most programming languages.
|
||||
|
||||
Feel free hacking fx to support the languages not listed. Welcome to tweet me [@_metrue](https://twitter.com/_metrue) on Twitter, [@metrue](https://www.weibo.com/u/2165714507) on Weibo.
|
||||
|
||||
@@ -39,6 +40,8 @@ Feel free hacking fx to support the languages not listed. Welcome to tweet me [@
|
||||
|
||||
# Installation
|
||||
|
||||
Binaries are available for Windows, MacOS and Linux/Unix on x86. For other architectures and platforms, follow instructions to [build fx from source](#buildtest).
|
||||
|
||||
* MacOS
|
||||
|
||||
```
|
||||
@@ -58,9 +61,9 @@ curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh |
|
||||
curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | sudo bash
|
||||
```
|
||||
|
||||
fx will be installed into /usr/local/bin, sometimes you may need `source ~/.zshrc` or `source ~/.bashrc` to make fx available in `$PAHT`.
|
||||
fx will be installed into /usr/local/bin, sometimes you may need `source ~/.zshrc` or `source ~/.bashrc` to make fx available in `$PATH`.
|
||||
|
||||
* Window
|
||||
* Windows
|
||||
|
||||
You can go the release page to [download](https://github.com/metrue/fx/releases) fx manually;
|
||||
|
||||
@@ -76,16 +79,16 @@ USAGE:
|
||||
fx [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
0.6.0
|
||||
0.8.7
|
||||
|
||||
COMMANDS:
|
||||
infra manage infrastructure of fx
|
||||
image manage image of service
|
||||
doctor health check for fx
|
||||
up deploy a function or a group of functions
|
||||
infra manage infrastructure
|
||||
up deploy a function
|
||||
down destroy a service
|
||||
list, ls list deployed services
|
||||
call run a function instantly
|
||||
image manage image of service
|
||||
doctor health check for fx
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
@@ -93,99 +96,36 @@ GLOBAL OPTIONS:
|
||||
--version, -v print the version
|
||||
```
|
||||
|
||||
1. List your current machines and activate you machine
|
||||
|
||||
```shell
|
||||
$ fx infra ls # list machines
|
||||
|
||||
{
|
||||
"localhost": {
|
||||
"Host": "localhost",
|
||||
"User": "",
|
||||
"Password": "",
|
||||
"Enabled": true,
|
||||
"Provisioned": false
|
||||
}
|
||||
}
|
||||
|
||||
$ fx infra activate localhost # activate 'localhost'
|
||||
|
||||
2019/08/10 13:21:20 info Provision:pull python Docker base iamge: ✓
|
||||
2019/08/10 13:21:21 info Provision:pull d Docker base image: ✓
|
||||
2019/08/10 13:21:23 info Provision:pull java Docker base image: ✓
|
||||
2019/08/10 13:21:28 info Provision:pull julia Docker base image: ✓
|
||||
2019/08/10 13:21:31 info Provision:pull node Docker base image: ✓
|
||||
2019/08/10 13:22:09 info Provision:pull go Docker base image: ✓
|
||||
2019/08/10 13:22:09 info provision machine localhost: ✓
|
||||
2019/08/10 13:22:09 info enble machine localhost: ✓
|
||||
```
|
||||
It may take seconds since `fx` needs to download some basic resources
|
||||
|
||||
*Note* you can add a remote host as fx machine also,
|
||||
```
|
||||
$ fx infra add --name my_aws_vm --host 13.121.202.227 --user root --password yourpassword
|
||||
|
||||
$ fx infra list
|
||||
{
|
||||
"my_aws_vm": {
|
||||
"Host": "13.121.202.227",
|
||||
"User": "root",
|
||||
"Password": "yourpassword",
|
||||
"Enabled": false,
|
||||
"Provisioned": false
|
||||
},
|
||||
"localhost": {
|
||||
"Host": "localhost",
|
||||
"User": "",
|
||||
"Password": "",
|
||||
"Enabled": true,
|
||||
"Provisioned": true
|
||||
}
|
||||
}
|
||||
|
||||
$ fx infra activate my_aws_vm
|
||||
### Deploy your function to Docker
|
||||
|
||||
```
|
||||
then your function will be deployed onto remote host also.
|
||||
$ fx up --name hello-fx ./examples/functions/JavaScript/func.js
|
||||
|
||||
2. Write a function
|
||||
|
||||
You can check out [examples](https://github.com/metrue/fx/tree/master/examples/functions) for reference. Let's write a function as an example, it calculates the sum of two numbers then returns:
|
||||
|
||||
```js
|
||||
module.exports = (ctx) => {
|
||||
ctx.body = 'hello world'
|
||||
}
|
||||
```
|
||||
Then save it to a file `func.js`.
|
||||
|
||||
3. Deploy your function as a service
|
||||
|
||||
Give your service a port with `--port`, and name with `--name`, heath checking with `--healthcheck` if you want.
|
||||
|
||||
```shell
|
||||
$ fx up -name fx_service_name -p 10001 --healthcheck func.js
|
||||
|
||||
2019/08/10 13:26:37 info Pack Service: ✓
|
||||
2019/08/10 13:26:39 info Build Service: ✓
|
||||
2019/08/10 13:26:39 info Run Service: ✓
|
||||
2019/08/10 13:26:39 info Service (fx_service_name) is running on: 0.0.0.0:10001
|
||||
2019/08/10 13:26:39 info up function fx_service_name(func.js) to machine localhost: ✓
|
||||
+------------------------------------------------------------------+-----------+---------------+
|
||||
| ID | NAME | ENDPOINT |
|
||||
+------------------------------------------------------------------+-----------+---------------+
|
||||
| 5b24d36608ee392c937a61a530805f74551ddec304aea3aca2ffa0fabcf98cf3 | /hello-fx | 0.0.0.0:58328 |
|
||||
+------------------------------------------------------------------+-----------+---------------+
|
||||
```
|
||||
|
||||
if you want see what the source code of your service looks like, you can export it into a dirctory,
|
||||
### Deploy your function to Kubernetes
|
||||
|
||||
```shell
|
||||
$ fx image export -o <path of dir> func.js
|
||||
2019/09/25 19:31:19 info exported to <path of dir>: ✓
|
||||
```
|
||||
$ KUBECONFIG=~/.kube/config ./build/fx up examples/functions/JavaScript/func.js --name hello-fx
|
||||
|
||||
+-------------------------------+------+----------------+
|
||||
| ID | NAME | ENDPOINT |
|
||||
+----+--------------------------+-----------------------+
|
||||
| 5b24d36608ee392c937a | hello-fx | 10.0.242.75:80 |
|
||||
+------------------------+-------------+----------------+
|
||||
```
|
||||
|
||||
4. Test your service
|
||||
### Test your service
|
||||
|
||||
then you can test your service:
|
||||
|
||||
```shell
|
||||
$ curl -v 0.0.0.0:10001
|
||||
$ curl -v 0.0.0.0:58328
|
||||
|
||||
|
||||
GET / HTTP/1.1
|
||||
@@ -207,34 +147,32 @@ hello world
|
||||
|
||||
```
|
||||
|
||||
## Docker
|
||||
## Manage Infrastructure
|
||||
|
||||
TODO
|
||||
**fx** is originally designed to turn a function into a runnable Docker container in a easiest way, on a host with Docker running, you can just deploy your function with `fx up` command, and now **fx** supports deploy function to be a service onto Kubernetes cluster infrasture, and we encourage you to do that other than on bare Docker environment, there are lots of advantage to run your function on Kubernetes like self-healing, load balancing, easy horizontal scaling, etc. It's pretty simple to deploy your function onto Kubernetes with **fx**, you just set KUBECONFIG in your enviroment.
|
||||
|
||||
## Kubernetes
|
||||
By default. **fx** use localhost as target infrastructure to run your service, and you can also setup your remote virtual machines as **fx**'s infrastructure and deploy your functions onto it.
|
||||
|
||||
**fx** supports deploy function to be a service onto Kubernetes cluster infrasture, and we encourage you to do that other than on bare Docker environment, there are lots of advantage to run your function on Kubernetes like self-healing, load balancing, easy horizontal scaling, etc. It's pretty simple to deploy your function onto Kubernetes with **fx**, you just set KUBECONFIG in your enviroment.
|
||||
### `fx infra create`
|
||||
|
||||
You can create types (docker and k8s) of infrastructures for **fx** to deploy functions
|
||||
|
||||
```shell
|
||||
KUBECONFIG=<Your KUBECONFIG> fx deploy -n fx-service-abc_js -p 12349 examples/functions/JavaScript/func.js # function will be deploy to your Kubernetes cluster and expose a IP address of your loadbalencer
|
||||
$ fx infra create --name infra_us --type docker --host <user>@<ip> ## create docker type infrasture on <ip>
|
||||
$ fx infra create --name infra_bj --type k8s --master <user>@<ip> --agents '<user1>@<ip1>,<user2>@<ip2>' ## create k8s type infrasture use <ip> as master node, and <ip1> and <ip2> as agents nodes
|
||||
```
|
||||
|
||||
or
|
||||
### `fx infra use`
|
||||
|
||||
To use a infrastructure, you can use `fx infra use` command to activate it.
|
||||
|
||||
```shell
|
||||
$ export KUBECONFIG=<Your KUBECONFIG>
|
||||
$ fx deploy -n fx-service-abc_js -p 12349 examples/functions/JavaScript/func.js # function will be deploy to your Kubernetes cluster and expose a IP address of your loadbalencer
|
||||
fx infra use <infrastructure name>
|
||||
```
|
||||
|
||||
* Local Kubernetes Cluster
|
||||
and you can list your infrastructure with `fx infra list`
|
||||
|
||||
Docker for Mac and Docker for Windows already support Kubernetes with single node cluster, we can use it directly, and the default `KUBECONFIG` is `~/.kube/config`.
|
||||
|
||||
```shell
|
||||
$ export KUBECONFIG=~/.kube/config # then fx will take the config to deloy function
|
||||
```
|
||||
|
||||
if you have multiple Kubernetes clusters configured, you have to set context correctly. FYI [configure-access-multiple-clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/)
|
||||
## Use Public Cloud Kubernetes Service as infrastructure to run your functions
|
||||
|
||||
* Azure Kubernetes Service (AKS)
|
||||
|
||||
@@ -269,6 +207,12 @@ 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.
|
||||
@@ -278,6 +222,7 @@ fx uses [Project](https://github.com/metrue/fx/projects/4) to manage the develop
|
||||
Docker: make sure [Docker](https://docs.docker.com/engine/installation/) installed and running on your server.
|
||||
|
||||
|
||||
<a name="buildtest"></a>
|
||||
#### Build & Test
|
||||
|
||||
```
|
||||
@@ -304,33 +249,54 @@ Thank you to all the people who already contributed to fx!
|
||||
<a href="https://github.com/metrue" target="_blank">
|
||||
<img alt="metrue" src="https://avatars2.githubusercontent.com/u/1001246?v=4&s=50" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/pplam" target="_blank">
|
||||
<img alt="pplam" src="https://avatars2.githubusercontent.com/u/12783579?v=4&s=50" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/muka" target="_blank">
|
||||
<img alt="muka" src="https://avatars2.githubusercontent.com/u/1021269?v=4&s=50" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/xwjdsh" target="_blank">
|
||||
<img alt="xwjdsh" src="https://avatars2.githubusercontent.com/u/11025519?v=4&s=50" width="50">
|
||||
<a href="https://github.com/pplam" target="_blank">
|
||||
<img alt="pplam" src="https://avatars2.githubusercontent.com/u/12783579?v=4&s=50" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/mbesancon" target="_blank">
|
||||
<img alt="mbesancon" src="https://avatars2.githubusercontent.com/u/7623090?v=4&s=50" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/avelino" target="_blank">
|
||||
<img alt="avelino" src="https://avatars2.githubusercontent.com/u/31996?v=4&s=50" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/DaidoujiChen" target="_blank">
|
||||
<img alt="DaidoujiChen" src="https://avatars0.githubusercontent.com/u/670441?v=4&s=50" width="50">
|
||||
<a href="https://github.com/matbesancon" target="_blank">
|
||||
<img alt="mbesancon" src="https://avatars2.githubusercontent.com/u/7623090?s=60&v=4" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/chlins" target="_blank">
|
||||
<img alt="chlins" src="https://avatars2.githubusercontent.com/u/31262637?v=4&s=50" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/xwjdsh" target="_blank">
|
||||
<img alt="xwjdsh" src="https://avatars2.githubusercontent.com/u/11025519?v=4&s=50" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/DaidoujiChen" target="_blank">
|
||||
<img alt="DaidoujiChen" src="https://avatars0.githubusercontent.com/u/670441?v=4&s=50" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/avelino" target="_blank">
|
||||
<img alt="avelino" src="https://avatars2.githubusercontent.com/u/31996?v=4&s=50" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/andre2007" target="_blank">
|
||||
<img alt="andre2007" src="https://avatars1.githubusercontent.com/u/1451047?s=50&v=4" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/polyrabbit" target="_blank">
|
||||
<img alt="polyrabbit" src="https://avatars0.githubusercontent.com/u/2657334?s=60&v=4" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/johnlunney" target="_blank">
|
||||
<img alt="johnlunney" src="https://avatars3.githubusercontent.com/u/536947?s=60&v=4" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/tbrand" target="_blank">
|
||||
<img alt="tbrand" src="https://avatars0.githubusercontent.com/u/3483230?s=60&v=4" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/steventhanna" target="_blank">
|
||||
<img alt="andre2007" src="https://avatars1.githubusercontent.com/u/2541678?s=50&v=4" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/border-radius" target="_blank">
|
||||
<img alt="border-radius" src="https://avatars0.githubusercontent.com/u/3204785?s=60&v=4" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/Russtopia" target="_blank">
|
||||
<img alt="Russtopia" src="https://avatars1.githubusercontent.com/u/2966177?s=60&v=4<Paste>" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/FrontMage" target="_blank">
|
||||
<img alt="FrontMage" src="https://avatars2.githubusercontent.com/u/17007026?s=60&v=4" width="50">
|
||||
</a>
|
||||
<a href="https://github.com/DropNib" target="_blank">
|
||||
<img alt="DropNib" src="https://avatars0.githubusercontent.com/u/32019589?s=60&v=4" width="50">
|
||||
</a>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
328
config/config.go
328
config/config.go
@@ -1,213 +1,195 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Configer interface
|
||||
type Configer interface {
|
||||
GetMachine(name string) (Host, error)
|
||||
AddMachine(name string, host Host) error
|
||||
RemoveHost(name string) error
|
||||
ListActiveMachines() (map[string]Host, error)
|
||||
ListMachines() (map[string]Host, error)
|
||||
EnableMachine(name string) error
|
||||
DisableMachine(name string) error
|
||||
UpdateProvisionedStatus(name string, ok bool) error
|
||||
// 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 {
|
||||
dir string
|
||||
mux sync.Mutex
|
||||
configFile string
|
||||
Items
|
||||
}
|
||||
|
||||
// New create a config
|
||||
func New(dir string) *Config {
|
||||
return &Config{dir: dir}
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Init config
|
||||
func (c *Config) Init() error {
|
||||
if err := os.MkdirAll(c.dir, os.ModePerm); err != nil {
|
||||
// Load config
|
||||
func Load(configFile string) (*Config, error) {
|
||||
if configFile == "" {
|
||||
return nil, fmt.Errorf("invalid config file")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||
if err := utils.EnsureFile(configFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
ext := "yaml"
|
||||
name := "config"
|
||||
viper.SetConfigType(ext)
|
||||
viper.SetConfigName(name)
|
||||
viper.AddConfigPath(c.dir)
|
||||
cloud := map[string]string{
|
||||
"type": "docker",
|
||||
"host": conf["ip"],
|
||||
"user": conf["user"],
|
||||
}
|
||||
return c.addCloud(name, cloud)
|
||||
}
|
||||
|
||||
// detect if file exists
|
||||
configFilePath := path.Join(c.dir, name+"."+ext)
|
||||
if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
|
||||
fd, err := os.Create(configFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fd.Close()
|
||||
// AddK8SCloud add k8s cloud
|
||||
func (c *Config) AddK8SCloud(name string, kubeconfig []byte) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
localhost := Host{
|
||||
Host: "localhost",
|
||||
Password: "",
|
||||
User: "",
|
||||
Enabled: true,
|
||||
Provisioned: false,
|
||||
}
|
||||
viper.Set("hosts", map[string]Host{"localhost": localhost})
|
||||
return viper.WriteConfig()
|
||||
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
|
||||
}
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return fmt.Errorf("Fatal error config file: %s", 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
|
||||
}
|
||||
|
||||
// GetMachine get host by name
|
||||
func (c *Config) GetMachine(name string) (Host, error) {
|
||||
var hosts map[string]Host
|
||||
if err := viper.UnmarshalKey("hosts", &hosts); err != nil {
|
||||
return Host{}, err
|
||||
}
|
||||
host, ok := hosts[name]
|
||||
if !ok {
|
||||
return Host{}, fmt.Errorf("no such host %v", name)
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
// ListActiveMachines list enabled machines
|
||||
func (c *Config) ListActiveMachines() (map[string]Host, error) {
|
||||
hosts, err := c.ListMachines()
|
||||
if err != nil {
|
||||
return map[string]Host{}, err
|
||||
}
|
||||
lst := map[string]Host{}
|
||||
for name, h := range hosts {
|
||||
if h.Enabled {
|
||||
lst[name] = h
|
||||
}
|
||||
}
|
||||
return lst, nil
|
||||
}
|
||||
|
||||
// AddMachine add host
|
||||
func (c *Config) AddMachine(name string, host Host) error {
|
||||
if !viper.IsSet("hosts") {
|
||||
viper.Set("hosts", map[string]Host{})
|
||||
}
|
||||
|
||||
hosts, err := c.ListMachines()
|
||||
func writeDefaultConfig(configFile string) error {
|
||||
me, err := user.Current()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hosts[name] = host
|
||||
viper.Set("hosts", hosts)
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
items := Items{
|
||||
Clouds: map[string]map[string]string{
|
||||
"default": map[string]string{
|
||||
"type": "docker",
|
||||
"host": "127.0.0.1",
|
||||
"user": me.Username,
|
||||
},
|
||||
},
|
||||
CurrentCloud: "default",
|
||||
}
|
||||
|
||||
// RemoveHost remote a host
|
||||
func (c *Config) RemoveHost(name string) error {
|
||||
hosts, err := c.ListMachines()
|
||||
body, err := yaml.Marshal(items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(hosts) == 1 {
|
||||
return fmt.Errorf("only one host left now, at least one host required by fx")
|
||||
}
|
||||
|
||||
if _, ok := hosts[name]; ok {
|
||||
delete(hosts, name)
|
||||
|
||||
viper.Set("hosts", hosts)
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
return fmt.Errorf("no such host %s", name)
|
||||
}
|
||||
|
||||
// ListMachines list hosts
|
||||
func (c *Config) ListMachines() (map[string]Host, error) {
|
||||
var hosts map[string]Host
|
||||
if err := viper.UnmarshalKey("hosts", &hosts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
// EnableMachine enable a machine, after machine enabled, function will be deployed onto it when ever `fx up` invoked
|
||||
func (c *Config) EnableMachine(name string) error {
|
||||
host, err := c.GetMachine(name)
|
||||
if err != nil {
|
||||
if err := ioutil.WriteFile(configFile, body, 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
host.Enabled = true
|
||||
|
||||
if !viper.IsSet("hosts") {
|
||||
viper.Set("hosts", map[string]Host{})
|
||||
}
|
||||
|
||||
hosts, err := c.ListMachines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hosts[name] = host
|
||||
viper.Set("hosts", hosts)
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
// DisableMachine disable a machine, after machine disabled, function will not be deployed onto it
|
||||
func (c *Config) DisableMachine(name string) error {
|
||||
host, err := c.GetMachine(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
host.Enabled = false
|
||||
|
||||
if !viper.IsSet("hosts") {
|
||||
viper.Set("hosts", map[string]Host{})
|
||||
}
|
||||
|
||||
hosts, err := c.ListMachines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hosts[name] = host
|
||||
viper.Set("hosts", hosts)
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
// UpdateProvisionedStatus update provisioned status
|
||||
func (c *Config) UpdateProvisionedStatus(name string, ok bool) error {
|
||||
host, err := c.GetMachine(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
host.Provisioned = ok
|
||||
|
||||
if !viper.IsSet("hosts") {
|
||||
viper.Set("hosts", map[string]Host{})
|
||||
}
|
||||
|
||||
hosts, err := c.ListMachines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hosts[name] = host
|
||||
viper.Set("hosts", hosts)
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
// IsMachineProvisioned check if machine provisioned
|
||||
func (c *Config) IsMachineProvisioned(name string) bool {
|
||||
host, err := c.GetMachine(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return host.Provisioned
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,98 +1,65 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
configPath := "/tmp/.fx"
|
||||
configPath := "./tmp/config.yml"
|
||||
defer func() {
|
||||
if err := os.RemoveAll(configPath); err != nil {
|
||||
if err := os.RemoveAll("./tmp"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
c := New(configPath)
|
||||
if err := c.Init(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hosts, err := c.ListMachines()
|
||||
c, err := Load(configPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(hosts) != 1 {
|
||||
t.Fatalf("should have localhost as default machine")
|
||||
if len(c.Clouds) != 1 {
|
||||
t.Fatal("should contain default cloud")
|
||||
}
|
||||
|
||||
host := hosts["localhost"]
|
||||
if !reflect.DeepEqual(host, Host{Host: "localhost", Enabled: true}) {
|
||||
t.Fatalf("should get %v but got %v", Host{Host: "localhost"}, host)
|
||||
name := "fx_cluster_1"
|
||||
if err := c.Use(name); err == nil {
|
||||
t.Fatal("should get no such cloud error")
|
||||
}
|
||||
|
||||
name := "remote-a"
|
||||
h := Host{
|
||||
Host: "192.168.1.1",
|
||||
User: "user-a",
|
||||
Password: "password-a",
|
||||
Enabled: false,
|
||||
}
|
||||
if err := c.AddMachine(name, h); err != nil {
|
||||
if err := c.AddK8SCloud(name, []byte("sampe kubeconfg")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hosts, err = c.ListMachines()
|
||||
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 len(hosts) != 2 {
|
||||
t.Fatalf("should have %d machines now, but got %d", 2, len(hosts))
|
||||
if conf.CurrentCloud != name {
|
||||
t.Fatalf("should get %s but got %s", name, c.CurrentCloud)
|
||||
}
|
||||
|
||||
lst, err := c.ListActiveMachines()
|
||||
body, err := c.View()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(lst) != 1 {
|
||||
t.Fatalf("should only have %d machine enabled, but got %d", 1, len(lst))
|
||||
}
|
||||
|
||||
if err := c.EnableMachine(name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
lst, err = c.ListActiveMachines()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(lst) != 2 {
|
||||
t.Fatalf("should only have %d machine enabled, but got %d", 2, len(lst))
|
||||
}
|
||||
|
||||
h.Enabled = true
|
||||
if !reflect.DeepEqual(lst[name], h) {
|
||||
t.Fatalf("should get %v but got %v", h, lst[name])
|
||||
}
|
||||
|
||||
if lst[name].Provisioned != false {
|
||||
t.Fatalf("should get %v but got %v", false, lst[name].Provisioned)
|
||||
}
|
||||
|
||||
if err := c.UpdateProvisionedStatus(name, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
updatedHost, err := c.GetMachine(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if updatedHost.Provisioned != true {
|
||||
t.Fatalf("should get %v but got %v", true, updatedHost.Provisioned)
|
||||
}
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package config
|
||||
|
||||
// Host host entity
|
||||
type Host struct {
|
||||
Host string
|
||||
User string
|
||||
Password string
|
||||
Enabled bool
|
||||
Provisioned bool
|
||||
}
|
||||
|
||||
// NewHost new a host
|
||||
func NewHost(addr, user, password string) Host {
|
||||
return Host{
|
||||
Host: addr,
|
||||
User: user,
|
||||
Password: password,
|
||||
Enabled: false,
|
||||
Provisioned: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Valid if host is valid
|
||||
func (h Host) Valid() bool {
|
||||
// TODO stronger check
|
||||
return h.Host != ""
|
||||
}
|
||||
|
||||
// IsLocal if host is localhost
|
||||
func (h Host) IsLocal() bool {
|
||||
if !h.Valid() {
|
||||
return false
|
||||
}
|
||||
return h.Host == "127.0.0.1" || h.Host == "localhost"
|
||||
}
|
||||
|
||||
// IsRemote is host is remote
|
||||
func (h Host) IsRemote() bool {
|
||||
return !h.IsLocal()
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: ./config.go
|
||||
|
||||
// Package mock_config is a generated GoMock package.
|
||||
package mock_config
|
||||
|
||||
import (
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
config "github.com/metrue/fx/config"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockConfiger is a mock of Configer interface
|
||||
type MockConfiger struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockConfigerMockRecorder
|
||||
}
|
||||
|
||||
// MockConfigerMockRecorder is the mock recorder for MockConfiger
|
||||
type MockConfigerMockRecorder struct {
|
||||
mock *MockConfiger
|
||||
}
|
||||
|
||||
// NewMockConfiger creates a new mock instance
|
||||
func NewMockConfiger(ctrl *gomock.Controller) *MockConfiger {
|
||||
mock := &MockConfiger{ctrl: ctrl}
|
||||
mock.recorder = &MockConfigerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockConfiger) EXPECT() *MockConfigerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GetMachine mocks base method
|
||||
func (m *MockConfiger) GetMachine(name string) (config.Host, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetMachine", name)
|
||||
ret0, _ := ret[0].(config.Host)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetMachine indicates an expected call of GetMachine
|
||||
func (mr *MockConfigerMockRecorder) GetMachine(name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMachine", reflect.TypeOf((*MockConfiger)(nil).GetMachine), name)
|
||||
}
|
||||
|
||||
// AddMachine mocks base method
|
||||
func (m *MockConfiger) AddMachine(name string, host config.Host) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddMachine", name, host)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddMachine indicates an expected call of AddMachine
|
||||
func (mr *MockConfigerMockRecorder) AddMachine(name, host interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMachine", reflect.TypeOf((*MockConfiger)(nil).AddMachine), name, host)
|
||||
}
|
||||
|
||||
// RemoveHost mocks base method
|
||||
func (m *MockConfiger) RemoveHost(name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RemoveHost", name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RemoveHost indicates an expected call of RemoveHost
|
||||
func (mr *MockConfigerMockRecorder) RemoveHost(name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveHost", reflect.TypeOf((*MockConfiger)(nil).RemoveHost), name)
|
||||
}
|
||||
|
||||
// ListActiveMachines mocks base method
|
||||
func (m *MockConfiger) ListActiveMachines() (map[string]config.Host, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ListActiveMachines")
|
||||
ret0, _ := ret[0].(map[string]config.Host)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ListActiveMachines indicates an expected call of ListActiveMachines
|
||||
func (mr *MockConfigerMockRecorder) ListActiveMachines() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListActiveMachines", reflect.TypeOf((*MockConfiger)(nil).ListActiveMachines))
|
||||
}
|
||||
|
||||
// ListMachines mocks base method
|
||||
func (m *MockConfiger) ListMachines() (map[string]config.Host, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ListMachines")
|
||||
ret0, _ := ret[0].(map[string]config.Host)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ListMachines indicates an expected call of ListMachines
|
||||
func (mr *MockConfigerMockRecorder) ListMachines() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMachines", reflect.TypeOf((*MockConfiger)(nil).ListMachines))
|
||||
}
|
||||
|
||||
// EnableMachine mocks base method
|
||||
func (m *MockConfiger) EnableMachine(name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EnableMachine", name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// EnableMachine indicates an expected call of EnableMachine
|
||||
func (mr *MockConfigerMockRecorder) EnableMachine(name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableMachine", reflect.TypeOf((*MockConfiger)(nil).EnableMachine), name)
|
||||
}
|
||||
|
||||
// DisableMachine mocks base method
|
||||
func (m *MockConfiger) DisableMachine(name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DisableMachine", name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DisableMachine indicates an expected call of DisableMachine
|
||||
func (mr *MockConfigerMockRecorder) DisableMachine(name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableMachine", reflect.TypeOf((*MockConfiger)(nil).DisableMachine), name)
|
||||
}
|
||||
|
||||
// UpdateProvisionedStatus mocks base method
|
||||
func (m *MockConfiger) UpdateProvisionedStatus(name string, ok bool) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateProvisionedStatus", name, ok)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateProvisionedStatus indicates an expected call of UpdateProvisionedStatus
|
||||
func (mr *MockConfigerMockRecorder) UpdateProvisionedStatus(name, ok interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateProvisionedStatus", reflect.TypeOf((*MockConfiger)(nil).UpdateProvisionedStatus), name, ok)
|
||||
}
|
||||
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"
|
||||
@@ -1,19 +1,32 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
dockerTypesContainer "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/google/uuid"
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// API interact with dockerd http api
|
||||
@@ -24,27 +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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,8 +131,47 @@ func (api *API) post(path string, body []byte, expectStatus int, v interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// List list service
|
||||
func (api *API) list(name string) ([]types.Service, error) {
|
||||
// Version get version of docker engine
|
||||
func (api *API) Version(ctx context.Context) (string, error) {
|
||||
return version(api.endpoint)
|
||||
}
|
||||
|
||||
func version(endpoint string) (string, error) {
|
||||
path := endpoint + "/version"
|
||||
if !strings.HasPrefix(path, "http") {
|
||||
path = "http://" + path
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
client := &http.Client{Timeout: 20 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("request %s failed: %d - %s", path, resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var res dockerTypes.Version
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.APIVersion, nil
|
||||
}
|
||||
|
||||
// ListContainer list service
|
||||
func (api *API) ListContainer(ctx context.Context, name string) ([]types.Service, error) {
|
||||
if name != "" {
|
||||
info, err := api.inspect(name)
|
||||
if err != nil {
|
||||
@@ -141,7 +195,7 @@ func (api *API) list(name string) ([]types.Service, error) {
|
||||
}
|
||||
|
||||
type filterItem struct {
|
||||
Status []string `json:"url,omitempty"`
|
||||
Status []string `json:"status,omitempty"`
|
||||
Label []string `json:"label,omitempty"`
|
||||
Name []string `json:"name,omitempty"`
|
||||
}
|
||||
@@ -193,3 +247,223 @@ func (api *API) list(name string) ([]types.Service, error) {
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// BuildImage build image
|
||||
func (api *API) BuildImage(ctx context.Context, workdir string, name string) error {
|
||||
tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tarDir)
|
||||
|
||||
imageID := uuid.New().String()
|
||||
tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", imageID))
|
||||
|
||||
if err := utils.TarDir(workdir, tarFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dockerBuildContext, err := os.Open(tarFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dockerBuildContext.Close()
|
||||
|
||||
type buildQuery struct {
|
||||
Labels string `url:"labels,omitempty"`
|
||||
Tags string `url:"t,omitempty"`
|
||||
Dockerfile string `url:"dockerfile,omitempty"`
|
||||
}
|
||||
|
||||
// Apply default labels
|
||||
labelsJSON, _ := json.Marshal(map[string]string{
|
||||
"belong-to": "fx",
|
||||
})
|
||||
q := buildQuery{
|
||||
Labels: string(labelsJSON),
|
||||
Dockerfile: "Dockerfile",
|
||||
}
|
||||
|
||||
qs, err := query.Values(q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qs.Add("t", name)
|
||||
qs.Add("t", imageID)
|
||||
|
||||
path := "/build"
|
||||
url := fmt.Sprintf("%s%s?%s", api.endpoint, path, qs.Encode())
|
||||
req, err := http.NewRequest("POST", url, dockerBuildContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-tar")
|
||||
client := &http.Client{Timeout: 600 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
|
||||
for scanner.Scan() {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
log.Infof(scanner.Text())
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushImage push a image
|
||||
func (api *API) PushImage(ctx context.Context, name string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// InspectImage inspect image
|
||||
func (api *API) InspectImage(ctx context.Context, name string, image interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TagImage tag image
|
||||
func (api *API) TagImage(ctx context.Context, name string, tag string) error {
|
||||
query := url.Values{}
|
||||
query.Set("repo", name)
|
||||
query.Set("tag", tag)
|
||||
path := fmt.Sprintf("/images/%s/tag?%s", name, query.Encode())
|
||||
|
||||
url := fmt.Sprintf("%s%s", api.endpoint, path)
|
||||
req, err := http.NewRequest("POST", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
if _, err = client.Do(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartContainer start container
|
||||
func (api *API) StartContainer(ctx context.Context, name string, image string, bindings []types.PortBinding) error {
|
||||
networks, err := api.GetNetwork(fxNetworkName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get network failed: %s", err)
|
||||
}
|
||||
|
||||
if len(networks) == 0 {
|
||||
if err := api.CreateNetwork(fxNetworkName); err != nil {
|
||||
return errors.Wrapf(err, "error create network: %s", err)
|
||||
}
|
||||
}
|
||||
networks, _ = api.GetNetwork(fxNetworkName)
|
||||
|
||||
endpoint := &network.EndpointSettings{
|
||||
NetworkID: networks[0].ID,
|
||||
}
|
||||
networkConfig := &network.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*network.EndpointSettings{
|
||||
"fx-net": endpoint,
|
||||
},
|
||||
}
|
||||
|
||||
portSet := nat.PortSet{}
|
||||
portMap := nat.PortMap{}
|
||||
for _, binding := range bindings {
|
||||
bindings := []nat.PortBinding{
|
||||
nat.PortBinding{
|
||||
HostIP: types.DefaultHost,
|
||||
HostPort: fmt.Sprintf("%d", binding.ServiceBindingPort),
|
||||
},
|
||||
}
|
||||
port := nat.Port(fmt.Sprintf("%d/tcp", binding.ContainerExposePort))
|
||||
portSet[port] = struct{}{}
|
||||
portMap[port] = bindings
|
||||
}
|
||||
config := &dockerTypesContainer.Config{
|
||||
Image: image,
|
||||
ExposedPorts: portSet,
|
||||
}
|
||||
|
||||
hostConfig := &dockerTypesContainer.HostConfig{
|
||||
AutoRemove: true,
|
||||
PortBindings: portMap,
|
||||
}
|
||||
|
||||
req := ContainerCreateRequestPayload{
|
||||
Config: config,
|
||||
HostConfig: hostConfig,
|
||||
NetworkingConfig: networkConfig,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error mashal container create req")
|
||||
}
|
||||
|
||||
// create container
|
||||
path := fmt.Sprintf("/containers/create?name=%s", name)
|
||||
var createRes container.ContainerCreateCreatedBody
|
||||
if err := api.post(path, body, 201, &createRes); err != nil {
|
||||
return errors.Wrap(err, "create container request failed")
|
||||
}
|
||||
|
||||
if createRes.ID == "" {
|
||||
return fmt.Errorf("container id is missing")
|
||||
}
|
||||
|
||||
// start container
|
||||
path = fmt.Sprintf("/containers/%s/start", createRes.ID)
|
||||
url := fmt.Sprintf("%s%s", api.endpoint, path)
|
||||
request, err := http.NewRequest("POST", url, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error new container create request")
|
||||
}
|
||||
client := &http.Client{Timeout: 20 * time.Second}
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error do start container request")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(b) != 0 {
|
||||
msg := fmt.Sprintf("start container met issue: %s", string(b))
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
if _, err = api.inspect(createRes.ID); err != nil {
|
||||
msg := fmt.Sprintf("inspect container %s error", name)
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopContainer stop a container
|
||||
func (api *API) StopContainer(ctx context.Context, name string) error {
|
||||
return api.Stop(name)
|
||||
}
|
||||
|
||||
// InspectContainer inspect container
|
||||
func (api *API) InspectContainer(ctx context.Context, name string, container interface{}) error {
|
||||
path := fmt.Sprintf("/containers/%s/json", name)
|
||||
if err := api.get(path, "", &container); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ containerruntimes.ContainerRuntime = &API{}
|
||||
)
|
||||
|
||||
@@ -1,89 +1,31 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func TestDockerHTTP(t *testing.T) {
|
||||
host := config.Host{Host: "127.0.0.1"}
|
||||
api, err := Create(host.Host, constants.AgentPort)
|
||||
host := os.Getenv("DOCKER_ENGINE_HOST")
|
||||
port := os.Getenv("DOCKER_ENGINE_PORT")
|
||||
if host == "" ||
|
||||
port == "" {
|
||||
t.Skip("DOCKER_ENGINE_HOST and DOCKER_ENGINE_PORT required")
|
||||
}
|
||||
|
||||
api, err := Create(host, port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
serviceName := "a-test-service"
|
||||
project := types.Project{
|
||||
Name: serviceName,
|
||||
Language: "node",
|
||||
Files: []types.ProjectSourceFile{
|
||||
types.ProjectSourceFile{
|
||||
Path: "Dockerfile",
|
||||
Body: `
|
||||
FROM metrue/fx-node-base
|
||||
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["node", "app.js"]`,
|
||||
IsHandler: false,
|
||||
},
|
||||
types.ProjectSourceFile{
|
||||
Path: "app.js",
|
||||
Body: `
|
||||
const Koa = require('koa');
|
||||
const bodyParser = require('koa-bodyparser');
|
||||
const func = require('./fx');
|
||||
|
||||
const app = new Koa();
|
||||
app.use(bodyParser());
|
||||
app.use(ctx => {
|
||||
const msg = func(ctx.request.body);
|
||||
ctx.body = msg;
|
||||
});
|
||||
|
||||
app.listen(3000);`,
|
||||
IsHandler: false,
|
||||
},
|
||||
types.ProjectSourceFile{
|
||||
Path: "fx.js",
|
||||
Body: `
|
||||
module.exports = (input) => {
|
||||
return input.a + input.b
|
||||
}
|
||||
`,
|
||||
IsHandler: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
service, err := api.Build(project)
|
||||
if err != nil {
|
||||
name := "fx-agent"
|
||||
var container types.ContainerJSON
|
||||
if err := api.InspectContainer(context.Background(), name, &container); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if service.Name != serviceName {
|
||||
t.Fatalf("should get %s but got %s", serviceName, service.Name)
|
||||
}
|
||||
|
||||
if err := api.Run(9999, &service); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
services, err := api.list(serviceName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(services) != 1 {
|
||||
t.Fatal("service number should be 1")
|
||||
}
|
||||
|
||||
if err := api.Stop(serviceName); err != nil {
|
||||
t.Fatal(err)
|
||||
if container.Name != "/"+name {
|
||||
t.Fatalf("should get %s but got %s", name, container.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,51 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
// Call function directly with given params
|
||||
func (api *API) Call(file string, param string, project types.Project) error {
|
||||
service, err := api.Build(project)
|
||||
if err != nil {
|
||||
log.Fatalf("Build Service: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Build Service: \u2713")
|
||||
|
||||
if err := api.Run(9999, &service); err != nil {
|
||||
log.Fatalf("Run Service: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Run Service: \u2713")
|
||||
|
||||
params := utils.PairsToParams(strings.Fields(param))
|
||||
body, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait 2 seconds for service startup
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
url := fmt.Sprintf("http://%s:%d", service.Host, service.Port)
|
||||
r, err := http.NewRequest("POST", url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
client := &http.Client{Timeout: 20 * time.Second}
|
||||
resp, err := client.Do(r)
|
||||
if err != nil {
|
||||
log.Fatalf("Call Service: %v", err)
|
||||
return err
|
||||
}
|
||||
buf, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("Call Service: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Call Service: \u2713")
|
||||
return utils.OutputJSON(string(buf))
|
||||
return nil
|
||||
// service, err := api.Build(project)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Build Service: %v", err)
|
||||
// return err
|
||||
// }
|
||||
// log.Info("Build Service: \u2713")
|
||||
//
|
||||
// if err := api.Run(9999, &service); err != nil {
|
||||
// log.Fatalf("Run Service: %v", err)
|
||||
// return err
|
||||
// }
|
||||
// log.Info("Run Service: \u2713")
|
||||
//
|
||||
// params := utils.PairsToParams(strings.Fields(param))
|
||||
// body, err := json.Marshal(params)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // Wait 2 seconds for service startup
|
||||
// time.Sleep(time.Second * 2)
|
||||
//
|
||||
// url := fmt.Sprintf("http://%s:%d", service.Host, service.Port)
|
||||
// r, err := http.NewRequest("POST", url, bytes.NewReader(body))
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// r.Header.Set("Content-Type", "application/json")
|
||||
// client := &http.Client{Timeout: 20 * time.Second}
|
||||
// resp, err := client.Do(r)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Call Service: %v", err)
|
||||
// return err
|
||||
// }
|
||||
// buf, err := ioutil.ReadAll(resp.Body)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Call Service: %v", err)
|
||||
// return err
|
||||
// }
|
||||
// log.Info("Call Service: \u2713")
|
||||
// return utils.OutputJSON(string(buf))
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
// List services
|
||||
func (api *API) List(name string) error {
|
||||
services, err := api.list(name)
|
||||
if err != nil {
|
||||
log.Fatalf("List Services: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, service := range services {
|
||||
if err := utils.OutputJSON(service); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
gock "gopkg.in/h2non/gock.v1"
|
||||
)
|
||||
|
||||
func TestNetwork(t *testing.T) {
|
||||
defer gock.Off()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
host := config.Host{Host: "127.0.0.1"}
|
||||
api, err := Create(host.Host, constants.AgentPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const network = "fx-net"
|
||||
if err := api.CreateNetwork(network); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nws, err := api.GetNetwork(network)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if nws[0].Name != network {
|
||||
t.Fatalf("should get %s but got %s", network, nws[0].Name)
|
||||
}
|
||||
}
|
||||
@@ -1,119 +1,59 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/google/uuid"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
func makeTar(project types.Project, tarFilePath string) error {
|
||||
dir, err := ioutil.TempDir("/tmp", "fx-build-dir")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
for _, file := range project.Files {
|
||||
tmpfn := filepath.Join(dir, file.Path)
|
||||
if err := utils.EnsureFile(tmpfn); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(tmpfn, []byte(file.Body), 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return utils.TarDir(dir, tarFilePath)
|
||||
}
|
||||
|
||||
// Build build a project
|
||||
func (api *API) Build(project types.Project) (types.Service, error) {
|
||||
tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||
if err != nil {
|
||||
return types.Service{}, err
|
||||
}
|
||||
defer os.RemoveAll(tarDir)
|
||||
|
||||
imageID := uuid.New().String()
|
||||
tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", imageID))
|
||||
if err := makeTar(project, tarFilePath); err != nil {
|
||||
return types.Service{}, err
|
||||
}
|
||||
labels := map[string]string{
|
||||
"belong-to": "fx",
|
||||
}
|
||||
if err := api.BuildImage(tarFilePath, imageID, labels); err != nil {
|
||||
return types.Service{}, err
|
||||
}
|
||||
|
||||
return types.Service{
|
||||
Name: project.Name,
|
||||
Image: imageID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BuildImage build docker image
|
||||
func (api *API) BuildImage(tarFile string, tag string, labels map[string]string) error {
|
||||
dockerBuildContext, err := os.Open(tarFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dockerBuildContext.Close()
|
||||
|
||||
type buildQuery struct {
|
||||
Labels string `url:"labels,omitempty"`
|
||||
Tags string `url:"t,omitempty"`
|
||||
Dockerfile string `url:"dockerfile,omitempty"`
|
||||
}
|
||||
|
||||
// Apply default labels
|
||||
labelsJSON, _ := json.Marshal(labels)
|
||||
q := buildQuery{
|
||||
Tags: tag,
|
||||
Labels: string(labelsJSON),
|
||||
Dockerfile: "Dockerfile",
|
||||
}
|
||||
qs, err := query.Values(q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := "/build"
|
||||
url := fmt.Sprintf("%s%s?%s", api.endpoint, path, qs.Encode())
|
||||
req, err := http.NewRequest("POST", url, dockerBuildContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-tar")
|
||||
client := &http.Client{Timeout: 600 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
scanner := bufio.NewScanner(resp.Body)
|
||||
|
||||
for scanner.Scan() {
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
log.Infof(scanner.Text())
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// import (
|
||||
// "fmt"
|
||||
// "io/ioutil"
|
||||
// "os"
|
||||
// "path/filepath"
|
||||
//
|
||||
// "github.com/google/uuid"
|
||||
// "github.com/metrue/fx/types"
|
||||
// "github.com/metrue/fx/utils"
|
||||
// )
|
||||
//
|
||||
// func makeTar(project types.Project, tarFilePath string) error {
|
||||
// dir, err := ioutil.TempDir("/tmp", "fx-build-dir")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// defer os.RemoveAll(dir)
|
||||
//
|
||||
// for _, file := range project.Files {
|
||||
// tmpfn := filepath.Join(dir, file.Path)
|
||||
// if err := utils.EnsureFile(tmpfn); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if err := ioutil.WriteFile(tmpfn, []byte(file.Body), 0666); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return utils.TarDir(dir, tarFilePath)
|
||||
// }
|
||||
//
|
||||
// // Build build a project
|
||||
// func (api *API) Build(project types.Project) (types.Service, error) {
|
||||
// tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||
// if err != nil {
|
||||
// return types.Service{}, err
|
||||
// }
|
||||
// defer os.RemoveAll(tarDir)
|
||||
//
|
||||
// imageID := uuid.New().String()
|
||||
// tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", imageID))
|
||||
// if err := makeTar(project, tarFilePath); err != nil {
|
||||
// return types.Service{}, err
|
||||
// }
|
||||
// labels := map[string]string{
|
||||
// "belong-to": "fx",
|
||||
// }
|
||||
// if err := api.BuildImage(tarFilePath, imageID, labels); err != nil {
|
||||
// return types.Service{}, err
|
||||
// }
|
||||
//
|
||||
// return types.Service{
|
||||
// Name: project.Name,
|
||||
// Image: imageID,
|
||||
// }, nil
|
||||
// }
|
||||
|
||||
@@ -1,166 +1,82 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/types"
|
||||
gock "gopkg.in/h2non/gock.v1"
|
||||
)
|
||||
|
||||
func TestMakeTar(t *testing.T) {
|
||||
serviceName := "mock-service-abc"
|
||||
project := types.Project{
|
||||
Name: serviceName,
|
||||
Language: "node",
|
||||
Files: []types.ProjectSourceFile{
|
||||
types.ProjectSourceFile{
|
||||
Path: "Dockerfile",
|
||||
Body: `
|
||||
FROM metrue/fx-node-base
|
||||
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["node", "app.js"]`,
|
||||
IsHandler: false,
|
||||
},
|
||||
types.ProjectSourceFile{
|
||||
Path: "app.js",
|
||||
Body: `
|
||||
const Koa = require('koa');
|
||||
const bodyParser = require('koa-bodyparser');
|
||||
const func = require('./fx');
|
||||
|
||||
const app = new Koa();
|
||||
app.use(bodyParser());
|
||||
app.use(ctx => {
|
||||
const msg = func(ctx.request.body);
|
||||
ctx.body = msg;
|
||||
});
|
||||
|
||||
app.listen(3000);`,
|
||||
IsHandler: false,
|
||||
},
|
||||
types.ProjectSourceFile{
|
||||
Path: "fx.js",
|
||||
Body: `
|
||||
module.exports = (input) => {
|
||||
return input.a + input.b
|
||||
}
|
||||
`,
|
||||
IsHandler: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tarDir)
|
||||
|
||||
tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", serviceName))
|
||||
if err := makeTar(project, tarFilePath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
file, err := os.Open(tarFilePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stat.Name() != serviceName+".tar" {
|
||||
t.Fatalf("should get %s but got %s", serviceName+".tar", stat.Name())
|
||||
}
|
||||
if stat.Size() <= 0 {
|
||||
t.Fatalf("tarfile invalid: size %d", stat.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
defer gock.Off()
|
||||
|
||||
host := config.Host{Host: "127.0.0.1"}
|
||||
api, err := Create(host.Host, constants.AgentPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
url := "http://" + host.Host + ":" + constants.AgentPort
|
||||
gock.New(url).
|
||||
Post("/v" + api.version + "/build").
|
||||
AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) {
|
||||
if strings.Contains(req.URL.String(), "/v"+api.version+"/build") {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}).
|
||||
Reply(200).
|
||||
JSON(map[string]string{
|
||||
"stream": "Step 1/5...",
|
||||
})
|
||||
|
||||
serviceName := "mock-service-abc"
|
||||
project := types.Project{
|
||||
Name: serviceName,
|
||||
Language: "node",
|
||||
Files: []types.ProjectSourceFile{
|
||||
types.ProjectSourceFile{
|
||||
Path: "Dockerfile",
|
||||
Body: `
|
||||
FROM metrue/fx-node-base
|
||||
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["node", "app.js"]`,
|
||||
IsHandler: false,
|
||||
},
|
||||
types.ProjectSourceFile{
|
||||
Path: "app.js",
|
||||
Body: `
|
||||
const Koa = require('koa');
|
||||
const bodyParser = require('koa-bodyparser');
|
||||
const func = require('./fx');
|
||||
|
||||
const app = new Koa();
|
||||
app.use(bodyParser());
|
||||
app.use(ctx => {
|
||||
const msg = func(ctx.request.body);
|
||||
ctx.body = msg;
|
||||
});
|
||||
|
||||
app.listen(3000);`,
|
||||
IsHandler: false,
|
||||
},
|
||||
types.ProjectSourceFile{
|
||||
Path: "fx.js",
|
||||
Body: `
|
||||
module.exports = (input) => {
|
||||
return input.a + input.b
|
||||
}
|
||||
`,
|
||||
IsHandler: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
service, err := api.Build(project)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if service.Name != serviceName {
|
||||
t.Fatalf("should get %s but got %s", serviceName, service.Name)
|
||||
}
|
||||
if service.Image == "" {
|
||||
t.Fatal("service image should not be empty")
|
||||
}
|
||||
}
|
||||
// import (
|
||||
// "fmt"
|
||||
// "io/ioutil"
|
||||
// "os"
|
||||
// "path/filepath"
|
||||
// "testing"
|
||||
//
|
||||
// "github.com/metrue/fx/types"
|
||||
// )
|
||||
//
|
||||
// func TestMakeTar(t *testing.T) {
|
||||
// serviceName := "mock-service-abc"
|
||||
// project := types.Project{
|
||||
// Name: serviceName,
|
||||
// Language: "node",
|
||||
// Files: []types.ProjectSourceFile{
|
||||
// types.ProjectSourceFile{
|
||||
// Path: "Dockerfile",
|
||||
// Body: `
|
||||
// FROM metrue/fx-node-base
|
||||
//
|
||||
// COPY . .
|
||||
// EXPOSE 3000
|
||||
// CMD ["node", "app.js"]`,
|
||||
// IsHandler: false,
|
||||
// },
|
||||
// types.ProjectSourceFile{
|
||||
// Path: "app.js",
|
||||
// Body: `
|
||||
// const Koa = require('koa');
|
||||
// const bodyParser = require('koa-bodyparser');
|
||||
// const func = require('./fx');
|
||||
//
|
||||
// const app = new Koa();
|
||||
// app.use(bodyParser());
|
||||
// app.use(ctx => {
|
||||
// const msg = func(ctx.request.body);
|
||||
// ctx.body = msg;
|
||||
// });
|
||||
//
|
||||
// app.listen(3000);`,
|
||||
// IsHandler: false,
|
||||
// },
|
||||
// types.ProjectSourceFile{
|
||||
// Path: "fx.js",
|
||||
// Body: `
|
||||
// module.exports = (input) => {
|
||||
// return input.a + input.b
|
||||
// }
|
||||
// `,
|
||||
// IsHandler: true,
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// defer os.RemoveAll(tarDir)
|
||||
//
|
||||
// tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", serviceName))
|
||||
// if err := makeTar(project, tarFilePath); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// file, err := os.Open(tarFilePath)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// stat, err := file.Stat()
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if stat.Name() != serviceName+".tar" {
|
||||
// t.Fatalf("should get %s but got %s", serviceName+".tar", stat.Name())
|
||||
// }
|
||||
// if stat.Size() <= 0 {
|
||||
// t.Fatalf("tarfile invalid: size %d", stat.Size())
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/types"
|
||||
|
||||
gock "gopkg.in/h2non/gock.v1"
|
||||
)
|
||||
|
||||
func TestServiceRun(t *testing.T) {
|
||||
defer gock.Off()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
host := config.Host{Host: "127.0.0.1"}
|
||||
api, err := Create(host.Host, constants.AgentPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
service := types.Service{
|
||||
Name: "a-mock-service",
|
||||
Image: "a-mock-image-id",
|
||||
}
|
||||
|
||||
mockContainerID := "mock-container-id"
|
||||
url := "http://" + host.Host + ":" + constants.AgentPort
|
||||
gock.New(url).
|
||||
Post("/v0.2.1/containers").
|
||||
AddMatcher(func(req *http.Request, ereq *gock.Request) (m bool, e error) {
|
||||
// TODO multiple matching not supported by gock
|
||||
if req.URL.String() == url+"/v0.2.1/containers/"+mockContainerID+"/start" {
|
||||
return true, nil
|
||||
} else if req.URL.String() == url+"/v0.2.1/containers/create?name="+service.Name {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}).
|
||||
Reply(201).
|
||||
JSON(map[string]interface{}{
|
||||
"Id": mockContainerID,
|
||||
"Warnings": []string{},
|
||||
})
|
||||
|
||||
// FIXME
|
||||
if err := api.Run(9999, &service); err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
gock "gopkg.in/h2non/gock.v1"
|
||||
)
|
||||
|
||||
func TestStop(t *testing.T) {
|
||||
defer gock.Off()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
host := config.Host{Host: "127.0.0.1"}
|
||||
api, err := Create(host.Host, constants.AgentPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
mockServiceName := "mock-service-name"
|
||||
url := "http://" + host.Host + ":" + constants.AgentPort
|
||||
gock.New(url).
|
||||
Post("/v" + api.version + "/containers/" + mockServiceName + "/stop").
|
||||
AddMatcher(func(req *http.Request, ereq *gock.Request) (m bool, e error) {
|
||||
if strings.Contains(req.URL.String(), "/v"+api.version+"/containers/"+mockServiceName+"/stop") {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}).
|
||||
Reply(204)
|
||||
if err := api.Stop(mockServiceName); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,81 +1,71 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// UpOptions options for up
|
||||
type UpOptions struct {
|
||||
Body []byte
|
||||
Lang string
|
||||
Name string
|
||||
Port int
|
||||
HealtCheck bool
|
||||
Project types.Project
|
||||
}
|
||||
|
||||
// Up up a source code of function to be a service
|
||||
func (api *API) Up(opt UpOptions) error {
|
||||
service, err := api.Build(opt.Project)
|
||||
if err != nil {
|
||||
log.Fatalf("Build Service %s: %v", opt.Name, err)
|
||||
return err
|
||||
}
|
||||
log.Infof("Build Service %s: %s", opt.Name, constants.CheckedSymbol)
|
||||
|
||||
if err := api.Run(opt.Port, &service); err != nil {
|
||||
log.Fatalf("Run Service: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Infof("Run Service: %s", constants.CheckedSymbol)
|
||||
log.Infof("Service (%s) is running on: %s:%d", service.Name, service.Host, service.Port)
|
||||
|
||||
if opt.HealtCheck {
|
||||
go func() {
|
||||
resultC, errC := api.ContainerWait(
|
||||
context.Background(),
|
||||
service.ID,
|
||||
container.WaitConditionNextExit,
|
||||
20*time.Second,
|
||||
)
|
||||
for {
|
||||
select {
|
||||
case res := <-resultC:
|
||||
var msg string
|
||||
if res.Error != nil {
|
||||
msg = res.Error.Message
|
||||
}
|
||||
log.Warnf("container exited: Code(%d) %s %s", res.StatusCode, msg, constants.UncheckedSymbol)
|
||||
case err := <-errC:
|
||||
log.Fatalf("wait container status exit: %s, %v", constants.UncheckedSymbol, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
trys := 0
|
||||
for {
|
||||
if trys > 2 {
|
||||
break
|
||||
}
|
||||
info, err := api.inspect(service.ID)
|
||||
if err != nil {
|
||||
log.Fatalf("healt checking failed: %v", err)
|
||||
}
|
||||
if info.State.Running {
|
||||
log.Info("service is running")
|
||||
} else {
|
||||
log.Warnf("service is %s", info.State.Status)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
trys++
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
// // UpOptions options for up
|
||||
// type UpOptions struct {
|
||||
// Body []byte
|
||||
// Lang string
|
||||
// Name string
|
||||
// Port int
|
||||
// HealtCheck bool
|
||||
// Project types.Project
|
||||
// }
|
||||
//
|
||||
// // Up up a source code of function to be a service
|
||||
// func (api *API) Up(opt UpOptions) error {
|
||||
// service, err := api.Build(opt.Project)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Build Service %s: %v", opt.Name, err)
|
||||
// return err
|
||||
// }
|
||||
// log.Infof("Build Service %s: %s", opt.Name, constants.CheckedSymbol)
|
||||
//
|
||||
// if err := api.Run(opt.Port, &service); err != nil {
|
||||
// log.Fatalf("Run Service: %v", err)
|
||||
// return err
|
||||
// }
|
||||
// log.Infof("Run Service: %s", constants.CheckedSymbol)
|
||||
// log.Infof("Service (%s) is running on: %s:%d", service.Name, service.Host, service.Port)
|
||||
//
|
||||
// if opt.HealtCheck {
|
||||
// go func() {
|
||||
// resultC, errC := api.ContainerWait(
|
||||
// context.Background(),
|
||||
// service.ID,
|
||||
// container.WaitConditionNextExit,
|
||||
// 20*time.Second,
|
||||
// )
|
||||
// for {
|
||||
// select {
|
||||
// case res := <-resultC:
|
||||
// var msg string
|
||||
// if res.Error != nil {
|
||||
// msg = res.Error.Message
|
||||
// }
|
||||
// log.Warnf("container exited: Code(%d) %s %s", res.StatusCode, msg, constants.UncheckedSymbol)
|
||||
// case err := <-errC:
|
||||
// log.Fatalf("wait container status exit: %s, %v", constants.UncheckedSymbol, err)
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
//
|
||||
// trys := 0
|
||||
// for {
|
||||
// if trys > 2 {
|
||||
// break
|
||||
// }
|
||||
// info, err := api.inspect(service.ID)
|
||||
// if err != nil {
|
||||
// log.Fatalf("healt checking failed: %v", err)
|
||||
// }
|
||||
// if info.State.Running {
|
||||
// log.Info("service is running")
|
||||
// } else {
|
||||
// log.Warnf("service is %s", info.State.Status)
|
||||
// }
|
||||
// time.Sleep(1 * time.Second)
|
||||
// trys++
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
|
||||
@@ -9,10 +9,12 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/apex/log"
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
dockerTypesContainer "github.com/docker/docker/api/types/container"
|
||||
dockerFilters "github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/google/uuid"
|
||||
@@ -69,14 +71,12 @@ func (d *Docker) BuildImage(ctx context.Context, workdir string, name string) er
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info(string(body))
|
||||
}
|
||||
|
||||
@@ -88,10 +88,10 @@ func (d *Docker) PushImage(ctx context.Context, name string) (string, error) {
|
||||
username := os.Getenv("DOCKER_USERNAME")
|
||||
password := os.Getenv("DOCKER_PASSWORD")
|
||||
if username == "" || password == "" {
|
||||
return "", fmt.Errorf("DOCKER_USERNAME and DOCKER_PASSWORD required for push image to registy")
|
||||
return "", fmt.Errorf("DOCKER_USERNAME and DOCKER_PASSWORD required for push image to registry")
|
||||
}
|
||||
|
||||
// TODO support private registy, like Azure Container registry
|
||||
// TODO support private registry, like Azure Container registry
|
||||
authConfig := dockerTypes.AuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
@@ -135,28 +135,34 @@ func (d *Docker) InspectImage(ctx context.Context, name string, img interface{})
|
||||
return json.NewDecoder(rdr).Decode(&img)
|
||||
}
|
||||
|
||||
// StartContainer create and start a container from given image
|
||||
func (d *Docker) StartContainer(ctx context.Context, name string, image string, ports []int32) error {
|
||||
config := &dockerTypesContainer.Config{
|
||||
Image: image,
|
||||
ExposedPorts: nat.PortSet{
|
||||
"3000/tcp": struct{}{},
|
||||
},
|
||||
}
|
||||
// TagImage tag image
|
||||
func (d *Docker) TagImage(ctx context.Context, name string, tag string) error {
|
||||
return d.ImageTag(ctx, name, tag)
|
||||
}
|
||||
|
||||
bindings := []nat.PortBinding{}
|
||||
for _, port := range ports {
|
||||
bindings = append(bindings, nat.PortBinding{
|
||||
HostIP: types.DefaultHost,
|
||||
HostPort: fmt.Sprintf("%d", port),
|
||||
})
|
||||
// StartContainer create and start a container from given image
|
||||
func (d *Docker) StartContainer(ctx context.Context, name string, image string, ports []types.PortBinding) error {
|
||||
portSet := nat.PortSet{}
|
||||
portMap := nat.PortMap{}
|
||||
for _, binding := range ports {
|
||||
bindings := []nat.PortBinding{
|
||||
nat.PortBinding{
|
||||
HostIP: types.DefaultHost,
|
||||
HostPort: fmt.Sprintf("%d", binding.ServiceBindingPort),
|
||||
},
|
||||
}
|
||||
port := nat.Port(fmt.Sprintf("%d/tcp", binding.ContainerExposePort))
|
||||
portSet[port] = struct{}{}
|
||||
portMap[port] = bindings
|
||||
}
|
||||
config := &dockerTypesContainer.Config{
|
||||
Image: image,
|
||||
ExposedPorts: portSet,
|
||||
}
|
||||
|
||||
hostConfig := &dockerTypesContainer.HostConfig{
|
||||
AutoRemove: true,
|
||||
PortBindings: nat.PortMap{
|
||||
"3000/tcp": bindings,
|
||||
},
|
||||
AutoRemove: true,
|
||||
PortBindings: portMap,
|
||||
}
|
||||
resp, err := d.ContainerCreate(ctx, config, hostConfig, nil, name)
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
@@ -183,7 +189,59 @@ func (d *Docker) StopContainer(ctx context.Context, name string) error {
|
||||
|
||||
// InspectContainer inspect a container
|
||||
func (d *Docker) InspectContainer(ctx context.Context, name string, container interface{}) error {
|
||||
return nil
|
||||
res, err := d.ContainerInspect(ctx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(body, &container)
|
||||
}
|
||||
|
||||
// ListContainer list containers
|
||||
func (d *Docker) ListContainer(ctx context.Context, name string) ([]types.Service, error) {
|
||||
args := dockerFilters.NewArgs(
|
||||
dockerFilters.Arg("label", "belong-to=fx"),
|
||||
)
|
||||
containers, err := d.ContainerList(ctx, dockerTypes.ContainerListOptions{
|
||||
Filters: args,
|
||||
})
|
||||
if err != nil {
|
||||
return []types.Service{}, err
|
||||
}
|
||||
|
||||
svs := make(map[string]types.Service)
|
||||
for _, container := range containers {
|
||||
// container name have extra forward slash
|
||||
// https://github.com/moby/moby/issues/6705
|
||||
if strings.HasPrefix(container.Names[0], fmt.Sprintf("/%s", name)) {
|
||||
svs[container.Image] = types.Service{
|
||||
Name: container.Names[0],
|
||||
Image: container.Image,
|
||||
ID: container.ID,
|
||||
Host: container.Ports[0].IP,
|
||||
Port: int(container.Ports[0].PublicPort),
|
||||
State: container.State,
|
||||
}
|
||||
}
|
||||
}
|
||||
services := []types.Service{}
|
||||
for _, s := range svs {
|
||||
services = append(services, s)
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// Version get version of docker engine
|
||||
func (d *Docker) Version(ctx context.Context) (string, error) {
|
||||
ping, err := d.Ping(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ping.APIVersion, nil
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestDocker(t *testing.T) {
|
||||
@@ -42,10 +43,27 @@ 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 == "" {
|
||||
t.Skip("Skip push image test since DOCKER_USERNAME and DOCKER_PASSWORD not set in enviroment variable")
|
||||
t.Skip("Skip push image test since DOCKER_USERNAME and DOCKER_PASSWORD not set in environment variable")
|
||||
}
|
||||
|
||||
img, err := cli.PushImage(ctx, name)
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
package containerruntimes
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// ContainerRuntime interface
|
||||
type ContainerRuntime interface {
|
||||
BuildImage(ctx context.Context, workdir string, name string) error
|
||||
PushImage(ctx context.Context, name string) (string, error)
|
||||
InspectImage(ct context.Context, name string, img interface{}) error
|
||||
StartContainer(ctx context.Context, name string, image string, ports []int32) error
|
||||
InspectImage(ctx context.Context, name string, img interface{}) error
|
||||
TagImage(ctx context.Context, name string, tag string) error
|
||||
StartContainer(ctx context.Context, name string, image string, bindings []types.PortBinding) error
|
||||
StopContainer(ctx context.Context, name string) error
|
||||
InspectContainer(ctx context.Context, name string, container interface{}) error
|
||||
ListContainer(ctx context.Context, filter string) ([]types.Service, error)
|
||||
Version(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
76
context/context.go
Normal file
76
context/context.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type key string
|
||||
|
||||
const (
|
||||
keyCliCtx = key("cmd_cli")
|
||||
)
|
||||
|
||||
// Contexter ctx interface
|
||||
type Contexter interface {
|
||||
Get(k string) interface{}
|
||||
Set(k string, v interface{})
|
||||
Use(fn func(ctx *Context) error) error
|
||||
GetContext() context.Context
|
||||
GetCliContext() *cli.Context
|
||||
}
|
||||
|
||||
// Context fx context
|
||||
type Context struct {
|
||||
context.Context
|
||||
}
|
||||
|
||||
// NewContext new a context
|
||||
func NewContext() *Context {
|
||||
ctx := context.Background()
|
||||
return &Context{ctx}
|
||||
}
|
||||
|
||||
// FromCliContext create context from cli.Context
|
||||
func FromCliContext(c *cli.Context) *Context {
|
||||
ctx := NewContext()
|
||||
ctx.WithCliContext(c)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithCliContext set cli.Context
|
||||
func (ctx *Context) WithCliContext(c *cli.Context) {
|
||||
newCtx := context.WithValue(ctx.Context, keyCliCtx, c)
|
||||
ctx.Context = newCtx
|
||||
}
|
||||
|
||||
// GetCliContext get cli.Context
|
||||
func (ctx *Context) GetCliContext() *cli.Context {
|
||||
return ctx.Value(keyCliCtx).(*cli.Context)
|
||||
}
|
||||
|
||||
// Set a value with name
|
||||
func (ctx *Context) Set(name string, value interface{}) {
|
||||
newCtx := context.WithValue(ctx.Context, name, value)
|
||||
ctx.Context = newCtx
|
||||
}
|
||||
|
||||
// Get a value
|
||||
func (ctx *Context) Get(name string) interface{} {
|
||||
return ctx.Context.Value(name)
|
||||
}
|
||||
|
||||
// Use invole a middle
|
||||
func (ctx *Context) Use(fn func(ctx *Context) error) error {
|
||||
return fn(ctx)
|
||||
}
|
||||
|
||||
// GetContext get context
|
||||
func (ctx *Context) GetContext() context.Context {
|
||||
return ctx.Context
|
||||
}
|
||||
|
||||
var (
|
||||
_ Contexter = &Context{}
|
||||
)
|
||||
29
context/context_test.go
Normal file
29
context/context_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestContext(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
cli := cli.NewContext(nil, nil, nil)
|
||||
ctx.WithCliContext(cli)
|
||||
c := ctx.GetCliContext()
|
||||
if c != cli {
|
||||
t.Fatalf("should get %v but got %v", cli, c)
|
||||
}
|
||||
|
||||
key := "k_1"
|
||||
value := "hello"
|
||||
ctx.Set(key, "hello")
|
||||
v := ctx.Get(key).(string)
|
||||
if v != value {
|
||||
t.Fatalf("should get %v but %v", value, v)
|
||||
}
|
||||
|
||||
if ctx.GetContext() == nil {
|
||||
t.Fatalf("should get context")
|
||||
}
|
||||
}
|
||||
104
context/mocks/context.go
Normal file
104
context/mocks/context.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: context.go
|
||||
|
||||
// Package mock_context is a generated GoMock package.
|
||||
package mock_context
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
context0 "github.com/metrue/fx/context"
|
||||
cli "github.com/urfave/cli"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockContexter is a mock of Contexter interface
|
||||
type MockContexter struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockContexterMockRecorder
|
||||
}
|
||||
|
||||
// MockContexterMockRecorder is the mock recorder for MockContexter
|
||||
type MockContexterMockRecorder struct {
|
||||
mock *MockContexter
|
||||
}
|
||||
|
||||
// NewMockContexter creates a new mock instance
|
||||
func NewMockContexter(ctrl *gomock.Controller) *MockContexter {
|
||||
mock := &MockContexter{ctrl: ctrl}
|
||||
mock.recorder = &MockContexterMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockContexter) EXPECT() *MockContexterMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Get mocks base method
|
||||
func (m *MockContexter) Get(k string) interface{} {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", k)
|
||||
ret0, _ := ret[0].(interface{})
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get
|
||||
func (mr *MockContexterMockRecorder) Get(k interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockContexter)(nil).Get), k)
|
||||
}
|
||||
|
||||
// Set mocks base method
|
||||
func (m *MockContexter) Set(k string, v interface{}) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Set", k, v)
|
||||
}
|
||||
|
||||
// Set indicates an expected call of Set
|
||||
func (mr *MockContexterMockRecorder) Set(k, v interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockContexter)(nil).Set), k, v)
|
||||
}
|
||||
|
||||
// Use mocks base method
|
||||
func (m *MockContexter) Use(fn func(*context0.Context) error) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Use", fn)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Use indicates an expected call of Use
|
||||
func (mr *MockContexterMockRecorder) Use(fn interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Use", reflect.TypeOf((*MockContexter)(nil).Use), fn)
|
||||
}
|
||||
|
||||
// GetContext mocks base method
|
||||
func (m *MockContexter) GetContext() context.Context {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetContext")
|
||||
ret0, _ := ret[0].(context.Context)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetContext indicates an expected call of GetContext
|
||||
func (mr *MockContexterMockRecorder) GetContext() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContext", reflect.TypeOf((*MockContexter)(nil).GetContext))
|
||||
}
|
||||
|
||||
// GetCliContext mocks base method
|
||||
func (m *MockContexter) GetCliContext() *cli.Context {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCliContext")
|
||||
ret0, _ := ret[0].(*cli.Context)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetCliContext indicates an expected call of GetCliContext
|
||||
func (mr *MockContexterMockRecorder) GetCliContext() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCliContext", reflect.TypeOf((*MockContexter)(nil).GetCliContext))
|
||||
}
|
||||
3
contrib/docker_packer/Dockerfile
Normal file
3
contrib/docker_packer/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM docker
|
||||
|
||||
ADD ./build/docker_packer /usr/bin/docker_packer
|
||||
21
contrib/docker_packer/Makefile
Normal file
21
contrib/docker_packer/Makefile
Normal file
@@ -0,0 +1,21 @@
|
||||
GOBIN ?= ./build
|
||||
GIT_VERSION := $(shell git describe --tags)
|
||||
VERSION ?= $(GIT_VERSION)
|
||||
|
||||
REPO ?= "metrue/fx-docker"
|
||||
TAG ?= "latest"
|
||||
|
||||
build:
|
||||
CGO_ENABLED=0 go build -ldflags "-X main.Version=$(VERSION)" -v -o $(GOBIN)/docker_packer main.go
|
||||
linux-build:
|
||||
CGO_ENABLED=0 GOOS=linux go build -ldflags "-X main.Version=$(VERSION)" -v -o $(GOBIN)/docker_packer main.go
|
||||
docker-build:
|
||||
docker build -t ${REPO}:${TAG} .
|
||||
docker-publish:
|
||||
docker push ${REPO}:${TAG}
|
||||
test:
|
||||
docker run -v /var/run/docker.sock:/var/run/docker.sock ${REPO}:${TAG} docker_packer 'eyJEb2NrZXJmaWxlIjoiRlJPTSBtZXRydWUvZngtbm9kZS1iYXNlXG5cbkNPUFkgLiAuXG5FWFBPU0UgMzAwMFxuQ01EIFtcIm5vZGVcIiwgXCJhcHAuanNcIl1cbiIsImFwcC5qcyI6ImNvbnN0IEtvYSA9IHJlcXVpcmUoJ2tvYScpO1xuY29uc3QgYm9keVBhcnNlciA9IHJlcXVpcmUoJ2tvYS1ib2R5cGFyc2VyJyk7XG5jb25zdCBmeCA9IHJlcXVpcmUoJy4vZngnKTtcblxuY29uc3QgYXBwID0gbmV3IEtvYSgpO1xuYXBwLnVzZShib2R5UGFyc2VyKCkpO1xuYXBwLnVzZShmeCk7XG5cbmFwcC5saXN0ZW4oMzAwMCk7XG4iLCJmeC5qcyI6IlxubW9kdWxlLmV4cG9ydHMgPSAoY3R4KSA9XHUwMDNlIHtcblx0Y3R4LmJvZHkgPSAnaGVsbG8gd29ybGQnXG59XG4ifQ==' app-hello
|
||||
docker run --rm -d -p 3000:3000 --name test-app-hello-container app-hello
|
||||
sleep 2
|
||||
curl 127.0.0.1:3000
|
||||
docker stop test-app-hello-container
|
||||
78
contrib/docker_packer/main.go
Normal file
78
contrib/docker_packer/main.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
runtime "github.com/metrue/fx/container_runtimes/docker/sdk"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// TODO clean it up
|
||||
os.Setenv("DEBUG", "true")
|
||||
}
|
||||
|
||||
func main() {
|
||||
args := os.Args
|
||||
|
||||
if len(args) != 3 {
|
||||
fmt.Println(`Usage:
|
||||
docker_packer <encrypt_docker_project_source_tree> <image_name>
|
||||
`)
|
||||
return
|
||||
}
|
||||
|
||||
meta := args[1]
|
||||
name := args[2]
|
||||
|
||||
str, err := base64.StdEncoding.WithPadding(base64.StdPadding).DecodeString(meta)
|
||||
if err != nil {
|
||||
log.Fatalf("could decode meta: %s, %v", meta, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var tree map[string]string
|
||||
//nolint
|
||||
if err := json.Unmarshal([]byte(str), &tree); err != nil {
|
||||
log.Fatalf("could not unmarshal meta: %s", meta)
|
||||
os.Exit(1)
|
||||
}
|
||||
workdir := "/tmp/fx"
|
||||
if err := packer.TreeToDir(tree, workdir); err != nil {
|
||||
log.Fatalf("could not restore to dir: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer os.RemoveAll(workdir)
|
||||
|
||||
ctx := context.Background()
|
||||
dockerClient, err := runtime.CreateClient(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("could not create a docker client: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := dockerClient.BuildImage(ctx, workdir, name); err != nil {
|
||||
log.Fatalf("could not build image: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
nameWithTag := name + ":latest"
|
||||
if err := dockerClient.ImageTag(ctx, name, nameWithTag); err != nil {
|
||||
log.Fatalf("could tag image: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var imgInfo dockerTypes.ImageInspect
|
||||
if err := utils.RunWithRetry(func() error {
|
||||
return dockerClient.InspectImage(context.Background(), name, &imgInfo)
|
||||
}, time.Second*1, 5); err != nil {
|
||||
fmt.Printf("inspect image failed: %s", err)
|
||||
}
|
||||
fmt.Println("image built succcessfully")
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package deploy
|
||||
|
||||
import "context"
|
||||
|
||||
// Deployer make a image a service
|
||||
type Deployer interface {
|
||||
Deploy(ctx context.Context, workdir string, name string, ports []int32) error
|
||||
Destroy(ctx context.Context, name string) error
|
||||
Update(ctx context.Context, name string) error
|
||||
GetStatus(ctx context.Context, name string) error
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
runtime "github.com/metrue/fx/container_runtimes/docker/sdk"
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
// Docker manage container
|
||||
type Docker struct {
|
||||
client *runtime.Docker
|
||||
}
|
||||
|
||||
// CreateClient create a docker instance
|
||||
func CreateClient(ctx context.Context) (*Docker, error) {
|
||||
cli, err := runtime.CreateClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Docker{client: 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, workdir string, name string, ports []int32) error {
|
||||
if err := d.client.BuildImage(ctx, workdir, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// config := &dockerTypesContainer.Config{
|
||||
// Image: image,
|
||||
// ExposedPorts: nat.PortSet{
|
||||
// "3000/tcp": struct{}{},
|
||||
// },
|
||||
// }
|
||||
// when deploy a function on a bare Docker running without Kubernetes,
|
||||
// image would be built on-demand on host locally, so there is no need to
|
||||
// pull image from remote.
|
||||
// But it takes some times waiting image ready after image built, we retry to make sure it ready here
|
||||
var imgInfo dockerTypes.ImageInspect
|
||||
if err := utils.RunWithRetry(func() error {
|
||||
return d.client.InspectImage(ctx, name, &imgInfo)
|
||||
}, time.Second*1, 5); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.client.StartContainer(ctx, name, name, ports)
|
||||
}
|
||||
|
||||
// Update a container
|
||||
func (d *Docker) Update(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy stop and remove container
|
||||
func (d *Docker) Destroy(ctx context.Context, name string) error {
|
||||
return d.client.ContainerStop(ctx, name, nil)
|
||||
}
|
||||
|
||||
// GetStatus get status of container
|
||||
func (d *Docker) GetStatus(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ deploy.Deployer = &Docker{}
|
||||
)
|
||||
@@ -1,28 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDocker(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
cli, err := CreateClient(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
workdir := "./fixture"
|
||||
name := "helloworld"
|
||||
ports := []int32{12345, 12346}
|
||||
if err := cli.Deploy(ctx, workdir, name, ports); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
if err := cli.Destroy(ctx, name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/google/uuid"
|
||||
runtime "github.com/metrue/fx/container_runtimes/docker/sdk"
|
||||
"github.com/metrue/fx/deploy"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// K8S client
|
||||
type K8S struct {
|
||||
*kubernetes.Clientset
|
||||
}
|
||||
|
||||
// Create a k8s cluster client
|
||||
func Create() (*K8S, error) {
|
||||
kubeconfig := os.Getenv("KUBECONFIG")
|
||||
if kubeconfig == "" {
|
||||
return nil, fmt.Errorf("KUBECONFIG not given")
|
||||
}
|
||||
|
||||
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &K8S{clientset}, nil
|
||||
}
|
||||
|
||||
// Deploy a image to be a service
|
||||
func (k *K8S) Deploy(
|
||||
ctx context.Context,
|
||||
workdir string,
|
||||
name string,
|
||||
ports []int32,
|
||||
) error {
|
||||
namespace := "default"
|
||||
|
||||
dockerClient, err := runtime.CreateClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dockerClient.BuildImage(ctx, workdir, name); err != nil {
|
||||
return err
|
||||
}
|
||||
image, err := dockerClient.PushImage(ctx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// By using a label selector between Pod and Service, we can link Service and Pod directly, it means a Endpoint will
|
||||
// be created automatically, then incoming traffic to Service will be forward to Pod.
|
||||
// Then we have no need to create Endpoint manually anymore.
|
||||
labels := map[string]string{
|
||||
"fx-app": "fx-app-" + uuid.New().String(),
|
||||
}
|
||||
if _, err := k.CreatePod(
|
||||
namespace,
|
||||
name,
|
||||
image,
|
||||
labels,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO fx should be able to know what's the target Kubernetes service platform
|
||||
// it's going to deploy to
|
||||
const isOnPublicCloud = true
|
||||
typ := "LoadBalancer"
|
||||
if !isOnPublicCloud {
|
||||
typ = "NodePort"
|
||||
}
|
||||
if _, err := k.CreateService(
|
||||
namespace,
|
||||
name,
|
||||
typ,
|
||||
ports,
|
||||
labels,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update a service
|
||||
func (k *K8S) Update(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy a service
|
||||
func (k *K8S) Destroy(ctx context.Context, name string) error {
|
||||
const namespace = "default"
|
||||
if err := k.DeleteService(namespace, name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := k.DeletePod(namespace, name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStatus get status of a service
|
||||
func (k *K8S) GetStatus(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ deploy.Deployer = &K8S{}
|
||||
)
|
||||
@@ -1,30 +0,0 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestK8SRunner(t *testing.T) {
|
||||
workdir := "./fixture"
|
||||
name := "hello"
|
||||
ports := []int32{32300}
|
||||
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)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if err := k8s.Deploy(ctx, workdir, name, ports); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := k8s.Destroy(ctx, name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"github.com/metrue/fx/constants"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
intstr "k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
// CreateService create a service
|
||||
func (k *K8S) CreateService(
|
||||
namespace string,
|
||||
name string,
|
||||
typ string,
|
||||
ports []int32,
|
||||
podsLabels map[string]string,
|
||||
) (*v1.Service, error) {
|
||||
servicePorts := []v1.ServicePort{
|
||||
v1.ServicePort{
|
||||
Name: "http",
|
||||
Protocol: v1.ProtocolTCP,
|
||||
Port: 80,
|
||||
TargetPort: intstr.FromInt(int(constants.FxContainerExposePort)),
|
||||
},
|
||||
v1.ServicePort{
|
||||
Name: "https",
|
||||
Protocol: v1.ProtocolTCP,
|
||||
Port: 443,
|
||||
TargetPort: intstr.FromInt(int(constants.FxContainerExposePort)),
|
||||
},
|
||||
}
|
||||
// Append custom Port
|
||||
for _, port := range ports {
|
||||
servicePorts = append(servicePorts, v1.ServicePort{
|
||||
Name: "custom",
|
||||
Protocol: v1.ProtocolTCP,
|
||||
Port: port,
|
||||
TargetPort: intstr.FromInt(int(3000)),
|
||||
})
|
||||
}
|
||||
|
||||
service := &v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
ClusterName: namespace,
|
||||
},
|
||||
Spec: v1.ServiceSpec{
|
||||
Ports: servicePorts,
|
||||
Type: v1.ServiceType(typ),
|
||||
Selector: podsLabels,
|
||||
},
|
||||
}
|
||||
|
||||
createdService, err := k.CoreV1().Services(namespace).Create(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createdService, nil
|
||||
}
|
||||
|
||||
// DeleteService a service
|
||||
func (k *K8S) 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)
|
||||
}
|
||||
46
docs/ubuntu.md
Normal file
46
docs/ubuntu.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# fx on Ubuntu
|
||||
|
||||
> The guide is verified on Amazon Lightsail ubuntu 18.08 instance
|
||||
|
||||
## Install Docker
|
||||
|
||||
```shell
|
||||
apt-get remove -y docker docker-engine docker.io containerd runc
|
||||
apt-get update -y
|
||||
apt-get install -y apt-transport-https ca-certificates curl software-properties-common lsb-core
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
|
||||
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
|
||||
apt-get update -y
|
||||
apt-get install -y docker-ce
|
||||
docker run hello-world
|
||||
```
|
||||
|
||||
## Install fx
|
||||
|
||||
```shell
|
||||
$ curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | sudo bash
|
||||
```
|
||||
|
||||
## Deploy a function onto localhost
|
||||
|
||||
```shell
|
||||
$ cat func.js
|
||||
|
||||
module.exports = (ctx) => {
|
||||
ctx.body = 'hello world'
|
||||
}
|
||||
|
||||
$ fx up -n test -p 2000 func.js
|
||||
$ curl 127.0.0.1:2000
|
||||
```
|
||||
|
||||
## Deploy a function onto remote host
|
||||
|
||||
* make sure your instance can be ssh login
|
||||
* make sure your instance accept port 8866
|
||||
|
||||
then you can deploy function to remote host
|
||||
|
||||
```shell
|
||||
DOCKER_REMOTE_HOST_ADDR=<your host> DOCKER_REMOTE_HOST_USER=<your user> DOCKER_REMOTE_HOST_PASSWORD=<your password> fx up -p 2000 test/functions/func.js
|
||||
```
|
||||
@@ -2,7 +2,6 @@ package doctor
|
||||
|
||||
import (
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/pkg/command"
|
||||
"github.com/metrue/go-ssh-client"
|
||||
@@ -10,16 +9,23 @@ import (
|
||||
|
||||
// Doctor health checking
|
||||
type Doctor struct {
|
||||
host config.Host
|
||||
host string
|
||||
|
||||
sshClient ssh.Client
|
||||
}
|
||||
|
||||
func isLocal(host string) bool {
|
||||
if host == "" {
|
||||
return false
|
||||
}
|
||||
return host == "127.0.0.1" || host == "localhost" || host == "0.0.0.0"
|
||||
}
|
||||
|
||||
// New a doctor
|
||||
func New(host config.Host) *Doctor {
|
||||
sshClient := ssh.New(host.Host).
|
||||
WithUser(host.User).
|
||||
WithPassword(host.Password)
|
||||
func New(host, user, password string) *Doctor {
|
||||
sshClient := ssh.New(host).
|
||||
WithUser(user).
|
||||
WithPassword(password)
|
||||
return &Doctor{
|
||||
host: host,
|
||||
sshClient: sshClient,
|
||||
@@ -32,7 +38,7 @@ func (d *Doctor) Start() error {
|
||||
checkAgent := "docker inspect " + constants.AgentContainerName
|
||||
|
||||
cmds := []*command.Command{}
|
||||
if d.host.IsRemote() {
|
||||
if !isLocal(d.host) {
|
||||
cmds = append(cmds,
|
||||
command.New("check if dockerd is running", checkDocker, command.NewRemoteRunner(d.sshClient)),
|
||||
command.New("check if fx agent is running", checkAgent, command.NewRemoteRunner(d.sshClient)),
|
||||
|
||||
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
|
||||
}
|
||||
```
|
||||
321
fx.go
321
fx.go
@@ -1,25 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/google/uuid"
|
||||
"github.com/metrue/fx/config"
|
||||
aurora "github.com/logrusorgru/aurora"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/handlers"
|
||||
"github.com/metrue/fx/middlewares"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var cfg *config.Config
|
||||
const version = "0.8.73"
|
||||
|
||||
func init() {
|
||||
configDir := path.Join(os.Getenv("HOME"), ".fx")
|
||||
cfg := config.New(configDir)
|
||||
go checkForUpdate()
|
||||
}
|
||||
|
||||
if err := cfg.Init(); err != nil {
|
||||
log.Fatalf("Init config failed %s", err)
|
||||
os.Exit(1)
|
||||
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)
|
||||
if err != nil {
|
||||
log.Debugf("Failed to fetch Github release page, error %v", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
var releaseJSON struct {
|
||||
Tag string `json:"tag_name"`
|
||||
URL string `json:"html_url"`
|
||||
}
|
||||
if err := decoder.Decode(&releaseJSON); err != nil {
|
||||
log.Debugf("Failed to decode Github release page JSON, error %v", err)
|
||||
return
|
||||
}
|
||||
if matched, err := regexp.MatchString(`^(\d+\.)(\d+\.)(\d+)$`, releaseJSON.Tag); err != nil || !matched {
|
||||
log.Debugf("Unofficial release %s?", releaseJSON.Tag)
|
||||
return
|
||||
}
|
||||
log.Debugf("Latest release tag is %s", releaseJSON.Tag)
|
||||
if releaseJSON.Tag != version {
|
||||
fmt.Fprintf(os.Stderr, "\nfx %s is available (you're using %s), get the latest release from: %s\n",
|
||||
releaseJSON.Tag, version, releaseJSON.URL)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,69 +67,135 @@ func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "fx"
|
||||
app.Usage = "makes function as a service"
|
||||
app.Version = "0.7.2"
|
||||
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: "infra",
|
||||
Usage: "manage infrastructure of fx",
|
||||
Usage: "manage infrastructure",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
Usage: "add a new machine",
|
||||
Name: "create",
|
||||
Usage: "create a infra for fx service",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name, N",
|
||||
Usage: "a alias name for this machine",
|
||||
Name: "type, t",
|
||||
Usage: "infracture type, 'docker', 'k8s' and 'k3s' support",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "host, H",
|
||||
Usage: "host name or IP address of a machine",
|
||||
Name: "name, n",
|
||||
Usage: "name to identify the infrastructure",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "user, U",
|
||||
Usage: "user name required for SSH login",
|
||||
Name: "host",
|
||||
Usage: "user and ip of your host, eg. 'root@182.12.1.12'",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "password, P",
|
||||
Usage: "password required for SSH login",
|
||||
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: func(c *cli.Context) error {
|
||||
return handlers.AddHost(cfg)(c)
|
||||
},
|
||||
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
handlers.Setup,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "remove an existing machine",
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.RemoveHost(cfg)(c)
|
||||
},
|
||||
Name: "list",
|
||||
Usage: "list all infrastructures",
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
handlers.ListInfra,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list machines",
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.ListHosts(cfg)(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "activate",
|
||||
Usage: "enable a machine be a host of fx infrastructure",
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.Activate(cfg)(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "deactivate",
|
||||
Usage: "disable a machine be a host of fx infrastructure",
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.Deactivate(cfg)(c)
|
||||
},
|
||||
Name: "use",
|
||||
Usage: "set current context to target cloud with given name",
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
handlers.UseInfra,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "up",
|
||||
Usage: "deploy a function",
|
||||
ArgsUsage: "[func.go func.js func.py func.rb ...]",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Value: uuid.New().String(),
|
||||
Usage: "service name",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "port, p",
|
||||
Usage: "port number",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "healthcheck, hc",
|
||||
Usage: "do a health check after service up",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "force deploy a function or functions",
|
||||
},
|
||||
},
|
||||
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: handle(
|
||||
middlewares.Parse("down"),
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Provision,
|
||||
handlers.Down,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list deployed services",
|
||||
Action: handle(
|
||||
middlewares.Parse("list"),
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Provision,
|
||||
handlers.List,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "call",
|
||||
Usage: "run a function instantly",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "host, H",
|
||||
Usage: "fx server host, default is localhost",
|
||||
},
|
||||
},
|
||||
Action: handle(handlers.Call),
|
||||
},
|
||||
{
|
||||
Name: "image",
|
||||
Usage: "manage image of service",
|
||||
@@ -103,9 +209,12 @@ func main() {
|
||||
Usage: "image tag",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.BuildImage(cfg)(c)
|
||||
},
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Provision,
|
||||
middlewares.Parse("image_build"),
|
||||
handlers.BuildImage,
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "export",
|
||||
@@ -116,113 +225,23 @@ func main() {
|
||||
Usage: "output directory",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.ExportImage()(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(cfg)(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "up",
|
||||
Usage: "deploy a function or a group of functions",
|
||||
ArgsUsage: "[func.go func.js func.py func.rb ...]",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Value: uuid.New().String(),
|
||||
Usage: "service name",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "port, p",
|
||||
Usage: "port number",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "healthcheck, hc",
|
||||
Usage: "do a health check after service up",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "force deploy a function or functions",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.Up(cfg)(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "deploy",
|
||||
Usage: "deploy a function or a group of functions",
|
||||
ArgsUsage: "[func.go func.js func.py func.rb ...]",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Value: uuid.New().String(),
|
||||
Usage: "service name",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "port, p",
|
||||
Usage: "port number",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "healthcheck, hc",
|
||||
Usage: "do a health check after service up",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "force deploy a function or functions",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.Deploy(cfg)(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "down",
|
||||
Usage: "destroy a service",
|
||||
ArgsUsage: "[service 1, service 2, ....]",
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.Down(cfg)(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "destroy",
|
||||
Usage: "destroy a service",
|
||||
ArgsUsage: "[service 1, service 2, ....]",
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.Destroy(cfg)(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list deployed services",
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.List(cfg)(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "call",
|
||||
Usage: "run a function instantly",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "host, H",
|
||||
Usage: "fx server host, default is localhost",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return handlers.Call(cfg)(c)
|
||||
},
|
||||
Name: "doctor",
|
||||
Usage: "health check for fx",
|
||||
Action: handle(handlers.Doctor),
|
||||
},
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatalf("fx startup with fatal: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
18
go.mod
18
go.mod
@@ -6,6 +6,7 @@ require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||
github.com/apex/log v1.1.1
|
||||
github.com/briandowns/spinner v1.7.0
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker v0.0.0-20190313072916-46036c230805
|
||||
github.com/docker/go-connections v0.4.0
|
||||
@@ -20,25 +21,28 @@ require (
|
||||
github.com/googleapis/gnostic v0.3.1 // indirect
|
||||
github.com/gorilla/mux v1.7.3 // indirect
|
||||
github.com/imdario/mergo v0.3.7 // indirect
|
||||
github.com/magiconair/properties v1.8.1 // indirect
|
||||
github.com/metrue/go-ssh-client v0.0.0-20190810064746-98a7a27048f3
|
||||
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434
|
||||
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/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/viper v1.4.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
|
||||
|
||||
108
go.sum
108
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,16 +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/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
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/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=
|
||||
@@ -46,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=
|
||||
@@ -55,6 +47,7 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||
@@ -63,24 +56,21 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
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=
|
||||
@@ -89,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=
|
||||
@@ -110,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=
|
||||
@@ -130,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=
|
||||
@@ -149,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=
|
||||
@@ -173,23 +149,28 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434 h1:im9kkmH0WWwxzegiv18gSUJbuXR9y028rXrWuPp6Jug=
|
||||
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/magiconair/properties v1.8.0/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=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
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/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=
|
||||
@@ -203,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=
|
||||
@@ -219,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=
|
||||
@@ -253,15 +227,12 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions 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=
|
||||
@@ -270,14 +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/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=
|
||||
@@ -291,7 +258,6 @@ github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLD
|
||||
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=
|
||||
@@ -302,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=
|
||||
@@ -329,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=
|
||||
@@ -357,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=
|
||||
@@ -378,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=
|
||||
@@ -387,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=
|
||||
@@ -400,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=
|
||||
@@ -410,21 +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=
|
||||
|
||||
13
hack/install_docker.sh
Executable file
13
hack/install_docker.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
# ++
|
||||
# verified on Ubuntu 16.04 x64
|
||||
# ++
|
||||
apt-get remove -y docker docker-engine docker.io containerd runc
|
||||
apt-get update -y
|
||||
apt-get install -y apt-transport-https ca-certificates curl software-properties-common lsb-core
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
|
||||
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
|
||||
apt-get update -y
|
||||
apt-get install -y docker-ce
|
||||
docker run hello-world
|
||||
@@ -1,52 +1,11 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
api "github.com/metrue/fx/container_runtimes/docker/http"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/metrue/fx/context"
|
||||
)
|
||||
|
||||
// Call command handle
|
||||
func Call(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) error {
|
||||
params := strings.Join(ctx.Args()[1:], " ")
|
||||
hosts, err := cfg.ListActiveMachines()
|
||||
if err != nil {
|
||||
log.Fatalf("list active machines failed: %v", err)
|
||||
}
|
||||
|
||||
file := ctx.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.ServiceFunctionSource{
|
||||
Language: lang,
|
||||
Source: string(src),
|
||||
}
|
||||
project, err := packer.Pack(file, fn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for name, host := range hosts {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
api "github.com/metrue/fx/container_runtimes/docker/http"
|
||||
"github.com/metrue/fx/deploy"
|
||||
dockerDeployer "github.com/metrue/fx/deploy/docker"
|
||||
k8sDeployer "github.com/metrue/fx/deploy/kubernetes"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Deploy deploy handle function
|
||||
func Deploy(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) (err error) {
|
||||
funcFile := ctx.Args().First()
|
||||
name := ctx.String("name")
|
||||
port := ctx.Int("port")
|
||||
force := ctx.Bool("force")
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Fatalf("fatal error happened: %v", r)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("deploy function %s (%s) failed: %v", err)
|
||||
}
|
||||
log.Infof("function %s (%s) deployed successfully", name, funcFile)
|
||||
}()
|
||||
|
||||
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)
|
||||
}
|
||||
hosts, err := cfg.ListActiveMachines()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "list active machines failed")
|
||||
}
|
||||
|
||||
if len(hosts) == 0 {
|
||||
log.Warnf("no active machines")
|
||||
return nil
|
||||
}
|
||||
|
||||
// try to stop service firt
|
||||
if force {
|
||||
for n, host := range hosts {
|
||||
if err := api.MustCreate(host.Host, constants.AgentPort).
|
||||
Stop(name); err != nil {
|
||||
log.Infof("stop function %s on machine %s failed: %v", name, n, err)
|
||||
} else {
|
||||
log.Infof("stop function %s on machine %s: %v", name, n, constants.CheckedSymbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadFile(funcFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read source failed")
|
||||
}
|
||||
lang := utils.GetLangFromFileName(funcFile)
|
||||
|
||||
workdir, err := ioutil.TempDir("/tmp", "fx-wd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := packer.PackIntoDir(lang, string(body), workdir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var deployer deploy.Deployer
|
||||
if os.Getenv("KUBECONFIG") != "" {
|
||||
deployer, err = k8sDeployer.Create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
bctx := context.Background()
|
||||
deployer, err = dockerDeployer.CreateClient(bctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// TODO multiple ports support
|
||||
return deployer.Deploy(
|
||||
context.Background(),
|
||||
workdir,
|
||||
name,
|
||||
[]int32{int32(port)},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/deploy"
|
||||
dockerDeployer "github.com/metrue/fx/deploy/docker"
|
||||
k8sDeployer "github.com/metrue/fx/deploy/kubernetes"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Destroy command handle
|
||||
func Destroy(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) (err error) {
|
||||
services := ctx.Args()
|
||||
c := context.Background()
|
||||
var runner deploy.Deployer
|
||||
if os.Getenv("KUBECONFIG") != "" {
|
||||
runner, err = k8sDeployer.Create()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
runner, err = dockerDeployer.CreateClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, svc := range services {
|
||||
if err := runner.Destroy(c, svc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,26 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/doctor"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Doctor command handle
|
||||
func Doctor(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) error {
|
||||
hosts, err := cfg.ListMachines()
|
||||
if err != nil {
|
||||
log.Fatalf("list machines failed %v", err)
|
||||
return nil
|
||||
}
|
||||
for name, h := range hosts {
|
||||
if err := doctor.New(h).Start(); err != nil {
|
||||
log.Warnf("machine %s is in dirty state: %v", name, err)
|
||||
} else {
|
||||
log.Infof("machine %s is in healthy state: %s", name, 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
|
||||
}
|
||||
|
||||
@@ -1,29 +1,18 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
api "github.com/metrue/fx/container_runtimes/docker/http"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/infra"
|
||||
)
|
||||
|
||||
// Down command handle
|
||||
func Down(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) error {
|
||||
containerID := ctx.Args()
|
||||
hosts, err := cfg.ListActiveMachines()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "list active machines failed: %v", 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
|
||||
}
|
||||
for name, host := range hosts {
|
||||
if err := api.MustCreate(host.Host, constants.AgentPort).
|
||||
Down(containerID); err != nil {
|
||||
return errors.Wrapf(err, "stop function on machine %s failed: %v", name, err)
|
||||
}
|
||||
log.Infof("stop function on machine %s: %v", name, constants.CheckedSymbol)
|
||||
}
|
||||
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,6 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
// HandleFunc command handle function
|
||||
type HandleFunc func(ctx *cli.Context) error
|
||||
@@ -1,56 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// AddHost add a host
|
||||
func AddHost(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) error {
|
||||
name := ctx.String("name")
|
||||
addr := ctx.String("host")
|
||||
user := ctx.String("user")
|
||||
password := ctx.String("password")
|
||||
host := config.NewHost(addr, user, password)
|
||||
if !host.Valid() {
|
||||
log.Fatalf("invaid host %v", host)
|
||||
return nil
|
||||
}
|
||||
|
||||
if host.IsRemote() {
|
||||
if host.User == "" || host.Password == "" {
|
||||
log.Fatalf("the host to add is a remote, user and password for SSH login is required")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return cfg.AddMachine(name, host)
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveHost remove a host
|
||||
func RemoveHost(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) error {
|
||||
name := ctx.Args().First()
|
||||
if name == "" {
|
||||
log.Fatalf("no name given: fx infra remove <name>")
|
||||
return nil
|
||||
}
|
||||
return cfg.RemoveHost(name)
|
||||
}
|
||||
}
|
||||
|
||||
// ListHosts list hosts
|
||||
func ListHosts(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) error {
|
||||
hosts, err := cfg.ListMachines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utils.OutputJSON(hosts)
|
||||
}
|
||||
}
|
||||
@@ -2,104 +2,74 @@ package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/google/uuid"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
api "github.com/metrue/fx/container_runtimes/docker/http"
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/provision"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/otiai10/copy"
|
||||
)
|
||||
|
||||
// BuildImage build image
|
||||
func BuildImage(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) error {
|
||||
funcFile := ctx.Args().First()
|
||||
tag := ctx.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)
|
||||
|
||||
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)
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("could not get current work directory: %v", err)
|
||||
return err
|
||||
}
|
||||
tarFile := fmt.Sprintf("%s.%s.tar", pwd, tag)
|
||||
defer os.RemoveAll(tarFile)
|
||||
sources := ctx.Get("sources").([]string)
|
||||
|
||||
if err := packer.PackIntoTar(lang, string(body), tarFile); err != nil {
|
||||
log.Fatalf("could not pack function: %v", err)
|
||||
return err
|
||||
}
|
||||
log.Infof("function packed: %v", constants.CheckedSymbol)
|
||||
|
||||
hosts, err := cfg.ListActiveMachines()
|
||||
if err != nil {
|
||||
log.Fatalf("could not list active machine: %v", err)
|
||||
return errors.Wrap(err, "list active machines failed")
|
||||
}
|
||||
|
||||
if len(hosts) == 0 {
|
||||
log.Warnf("no active machines")
|
||||
return nil
|
||||
}
|
||||
for n, host := range hosts {
|
||||
if !host.Provisioned {
|
||||
provisionor := provision.New(host)
|
||||
if err := provisionor.Start(); err != nil {
|
||||
return errors.Wrapf(err, "could not provision %s", n)
|
||||
}
|
||||
log.Infof("provision machine %v: %s", n, constants.CheckedSymbol)
|
||||
if err := cfg.UpdateProvisionedStatus(n, true); err != nil {
|
||||
return errors.Wrap(err, "update machine provision status failed")
|
||||
}
|
||||
}
|
||||
|
||||
if err := api.MustCreate(host.Host, constants.AgentPort).
|
||||
BuildImage(tarFile, tag, map[string]string{}); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("image built on machine %s: %v", n, constants.CheckedSymbol)
|
||||
}
|
||||
return nil
|
||||
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 *cli.Context) (err error) {
|
||||
funcFile := ctx.Args().First()
|
||||
outputDir := ctx.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(lang, 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
|
||||
}
|
||||
|
||||
96
handlers/infra.go
Normal file
96
handlers/infra.go
Normal file
@@ -0,0 +1,96 @@
|
||||
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],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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,46 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/provision"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Activate a machine to be fx server
|
||||
func Activate(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) error {
|
||||
name := ctx.Args().First()
|
||||
if name == "" {
|
||||
log.Fatalf("name required for: fx infra activate <name>")
|
||||
return nil
|
||||
}
|
||||
|
||||
host, err := cfg.GetMachine(name)
|
||||
if err != nil {
|
||||
log.Fatalf("could get host %v, make sure you add it first", err)
|
||||
log.Info("You can add a machine by: \n fx infra add -Name <name> -H <ip or hostname> -U <user> -P <password>")
|
||||
return nil
|
||||
}
|
||||
if !host.Provisioned {
|
||||
provisionor := provision.New(host)
|
||||
if err := provisionor.Start(); err != nil {
|
||||
log.Fatalf("could not provision %s: %v", name, err)
|
||||
return nil
|
||||
}
|
||||
log.Infof("provision machine %v: %s", name, constants.CheckedSymbol)
|
||||
if err := cfg.UpdateProvisionedStatus(name, true); err != nil {
|
||||
log.Fatalf("update machine provision status failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := cfg.EnableMachine(name); err != nil {
|
||||
log.Fatalf("could not enable %s: %v", name, err)
|
||||
return nil
|
||||
}
|
||||
log.Infof("enble machine %v: %s", name, constants.CheckedSymbol)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Deactivate a machine
|
||||
func Deactivate(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) error {
|
||||
name := ctx.Args().First()
|
||||
if name == "" {
|
||||
log.Fatalf("name required for: fx infra activate <name>")
|
||||
return nil
|
||||
}
|
||||
if err := cfg.DisableMachine(name); err != nil {
|
||||
log.Fatalf("could not disable %s: %v", name, err)
|
||||
return nil
|
||||
}
|
||||
log.Infof("machine %s deactive: %v", name, constants.CheckedSymbol)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,21 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
api "github.com/metrue/fx/container_runtimes/docker/http"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/pkg/render"
|
||||
)
|
||||
|
||||
// List command handle
|
||||
func List(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) error {
|
||||
hosts, err := cfg.ListActiveMachines()
|
||||
if err != nil {
|
||||
log.Fatalf("list active machines failed: %v", err)
|
||||
}
|
||||
for name, host := range hosts {
|
||||
if err := api.MustCreate(host.Host, constants.AgentPort).List(ctx.Args().First()); err != nil {
|
||||
log.Fatalf("list functions on machine %s failed: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
func List(ctx context.Contexter) (err error) {
|
||||
cli := ctx.GetCliContext()
|
||||
deployer := ctx.Get("deployer").(infra.Deployer)
|
||||
|
||||
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
|
||||
}
|
||||
138
handlers/up.go
138
handlers/up.go
@@ -1,116 +1,40 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/constants"
|
||||
api "github.com/metrue/fx/container_runtimes/docker/http"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/provision"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/pkg/render"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// 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(cfg config.Configer) HandleFunc {
|
||||
return func(ctx *cli.Context) (err error) {
|
||||
funcFile := ctx.Args().First()
|
||||
name := ctx.String("name")
|
||||
port := ctx.Int("port")
|
||||
healtcheck := ctx.Bool("healthcheck")
|
||||
force := ctx.Bool("force")
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Fatalf("fatal error happened: %v", r)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("deploy function %s (%s) failed: %v", err)
|
||||
}
|
||||
log.Infof("function %s (%s) deployed successfully", name, funcFile)
|
||||
}()
|
||||
|
||||
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)
|
||||
}
|
||||
hosts, err := cfg.ListActiveMachines()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "list active machines failed")
|
||||
}
|
||||
|
||||
if len(hosts) == 0 {
|
||||
log.Warnf("no active machines")
|
||||
return nil
|
||||
}
|
||||
|
||||
// try to stop service firt
|
||||
if force {
|
||||
for n, host := range hosts {
|
||||
if err := api.MustCreate(host.Host, constants.AgentPort).
|
||||
Stop(name); err != nil {
|
||||
log.Infof("stop function %s on machine %s failed: %v", name, n, err)
|
||||
} else {
|
||||
log.Infof("stop function %s on machine %s: %v", name, n, constants.CheckedSymbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadFile(funcFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read source failed")
|
||||
}
|
||||
lang := utils.GetLangFromFileName(funcFile)
|
||||
|
||||
fn := types.ServiceFunctionSource{
|
||||
Language: lang,
|
||||
Source: string(body),
|
||||
}
|
||||
|
||||
project, err := packer.Pack(name, fn)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could pack function %s (%s)", name, funcFile)
|
||||
}
|
||||
|
||||
for n, host := range hosts {
|
||||
if !host.Provisioned {
|
||||
provisionor := provision.New(host)
|
||||
if err := provisionor.Start(); err != nil {
|
||||
return errors.Wrapf(err, "could not provision %s", n)
|
||||
}
|
||||
log.Infof("provision machine %v: %s", n, constants.CheckedSymbol)
|
||||
if err := cfg.UpdateProvisionedStatus(n, true); err != nil {
|
||||
return errors.Wrap(err, "update machine provision status failed")
|
||||
}
|
||||
}
|
||||
|
||||
if err := api.MustCreate(host.Host, constants.AgentPort).
|
||||
Up(api.UpOptions{
|
||||
Body: body,
|
||||
Lang: lang,
|
||||
Name: name,
|
||||
Port: port,
|
||||
HealtCheck: healtcheck,
|
||||
Project: project,
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "up function %s(%s) to machine %s failed", name, funcFile, n)
|
||||
}
|
||||
log.Infof("up function %s(%s) to machine %s: %v", name, funcFile, n, constants.CheckedSymbol)
|
||||
}
|
||||
return nil
|
||||
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{}
|
||||
)
|
||||
43
infra/docker/deployer_test.go
Normal file
43
infra/docker/deployer_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDocker(t *testing.T) {
|
||||
// ctx := context.Background()
|
||||
// cli, err := CreateClient(ctx)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// name := "helloworld"
|
||||
// bindings := []types.PortBinding{
|
||||
// types.PortBinding{
|
||||
// ServiceBindingPort: 80,
|
||||
// ContainerExposePort: 3000,
|
||||
// },
|
||||
// types.PortBinding{
|
||||
// ServiceBindingPort: 443,
|
||||
// ContainerExposePort: 3000,
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// fn := types.Func{
|
||||
// Language: "node",
|
||||
// Source: `
|
||||
// module.exports = (ctx) => {
|
||||
// ctx.body = 'hello world'
|
||||
// }
|
||||
// `,
|
||||
// }
|
||||
// if err := cli.Deploy(ctx, fn, name, name, bindings); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// time.Sleep(1 * time.Second)
|
||||
//
|
||||
// if err := cli.Destroy(ctx, name); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
}
|
||||
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
|
||||
}
|
||||
47
infra/k8s/configmap.go
Normal file
47
infra/k8s/configmap.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// CreateConfigMap create a config map with data
|
||||
func (k *K8S) CreateConfigMap(namespace string, name string, data map[string]string) (*apiv1.ConfigMap, error) {
|
||||
cm := &apiv1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Data: data,
|
||||
}
|
||||
return k.CoreV1().ConfigMaps(namespace).Create(cm)
|
||||
}
|
||||
|
||||
// DeleteConfigMap delete a config map
|
||||
func (k *K8S) DeleteConfigMap(namespace string, name string) error {
|
||||
return k.CoreV1().ConfigMaps(namespace).Delete(name, &metav1.DeleteOptions{})
|
||||
}
|
||||
|
||||
// UpdateConfigMap update a config map
|
||||
func (k *K8S) UpdateConfigMap(namespace string, name string, data map[string]string) (*apiv1.ConfigMap, error) {
|
||||
cm := &apiv1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Data: data,
|
||||
}
|
||||
return k.CoreV1().ConfigMaps(namespace).Update(cm)
|
||||
}
|
||||
|
||||
// GetConfigMap get a config map
|
||||
func (k *K8S) GetConfigMap(namespace string, name string) (*apiv1.ConfigMap, error) {
|
||||
return k.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
// CreateOrUpdateConfigMap create or update a config map
|
||||
func (k *K8S) CreateOrUpdateConfigMap(namespace string, name string, data map[string]string) (*apiv1.ConfigMap, error) {
|
||||
_, err := k.GetConfigMap(namespace, name)
|
||||
if err != nil {
|
||||
return k.CreateConfigMap(namespace, name, data)
|
||||
}
|
||||
return k.UpdateConfigMap(namespace, name, data)
|
||||
}
|
||||
35
infra/k8s/configmap_test.go
Normal file
35
infra/k8s/configmap_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfigMap(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
||||
namespace := "default"
|
||||
name := "test-configmap"
|
||||
data := map[string]string{
|
||||
"message": "hello world",
|
||||
}
|
||||
cm, err := k8s.CreateConfigMap(namespace, name, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cm.Name != name {
|
||||
t.Fatalf("should get %s but got %s", name, cm.Name)
|
||||
}
|
||||
|
||||
if err != k8s.DeleteConfigMap(namespace, name) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
8
infra/k8s/constants.go
Normal file
8
infra/k8s/constants.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package k8s
|
||||
|
||||
// ConfigMap is the key to function docker project source code in configmap
|
||||
var ConfigMap = struct {
|
||||
AppMetaEnvName string
|
||||
}{
|
||||
AppMetaEnvName: "APP_META",
|
||||
}
|
||||
47
infra/k8s/deploy_test.go
Normal file
47
infra/k8s/deploy_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestK8SDeployer(t *testing.T) {
|
||||
name := "hellohello"
|
||||
bindings := []types.PortBinding{
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 80,
|
||||
ContainerExposePort: 3000,
|
||||
},
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 443,
|
||||
ContainerExposePort: 3000,
|
||||
},
|
||||
}
|
||||
kubeconfig := os.Getenv("KUBECONFIG")
|
||||
username := os.Getenv("DOCKER_USERNAME")
|
||||
password := os.Getenv("DOCKER_PASSWORD")
|
||||
if kubeconfig == "" || username == "" || password == "" {
|
||||
t.Skip("skip test since no KUBECONFIG, DOCKER_USERNAME and DOCKER_PASSWORD given in environment variable")
|
||||
}
|
||||
k8s, err := Create("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := packer.PackIntoK8SConfigMapFile("./fixture")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
if err := k8s.Deploy(ctx, data, name, name, bindings); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := k8s.Destroy(ctx, name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
190
infra/k8s/deployer.go
Normal file
190
infra/k8s/deployer.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// K8S client
|
||||
type K8S struct {
|
||||
*kubernetes.Clientset
|
||||
}
|
||||
|
||||
const namespace = "default"
|
||||
|
||||
// Create a k8s cluster client
|
||||
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
|
||||
}
|
||||
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &K8S{clientset}, nil
|
||||
}
|
||||
|
||||
// Deploy a image to be a service
|
||||
func (k *K8S) Deploy(
|
||||
ctx context.Context,
|
||||
fn string,
|
||||
name string,
|
||||
image string,
|
||||
ports []types.PortBinding,
|
||||
) error {
|
||||
data := map[string]string{}
|
||||
data[ConfigMap.AppMetaEnvName] = fn
|
||||
if _, err := k.CreateOrUpdateConfigMap(namespace, name, data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 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(
|
||||
namespace,
|
||||
name,
|
||||
name,
|
||||
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 *K8S) Update(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy a service
|
||||
func (k *K8S) 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 *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) (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 (
|
||||
_ infra.Deployer = &K8S{}
|
||||
)
|
||||
102
infra/k8s/deployment.go
Normal file
102
infra/k8s/deployment.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package k8s
|
||||
|
||||
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 *K8S) GetDeployment(namespace string, name string) (*appsv1.Deployment, error) {
|
||||
return k.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
// CreateDeployment create a deployment
|
||||
func (k *K8S) 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 *K8S) 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 *K8S) 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 *K8S) 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)
|
||||
}
|
||||
61
infra/k8s/deployment_test.go
Normal file
61
infra/k8s/deployment_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package k8s
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
70
infra/k8s/init_container.go
Normal file
70
infra/k8s/init_container.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package k8s
|
||||
|
||||
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
|
||||
}
|
||||
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 --docker --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 = "--docker"
|
||||
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)
|
||||
}
|
||||
}
|
||||
87
infra/k8s/service.go
Normal file
87
infra/k8s/service.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package k8s
|
||||
|
||||
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 *K8S) 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 *K8S) 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 *K8S) 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 *K8S) GetService(namespace string, name string) (*apiv1.Service, error) {
|
||||
return k.CoreV1().Services(namespace).Get(name, metav1.GetOptions{})
|
||||
}
|
||||
@@ -1,21 +1,33 @@
|
||||
package kubernetes
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestK8S(t *testing.T) {
|
||||
namespace := "default"
|
||||
// TODO image is ready on hub.docker.com
|
||||
image := "metrue/kube-hello"
|
||||
ports := []int32{32300}
|
||||
bindings := []types.PortBinding{
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 80,
|
||||
ContainerExposePort: 3000,
|
||||
},
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 443,
|
||||
ContainerExposePort: 3000,
|
||||
},
|
||||
}
|
||||
podName := "test-fx-pod"
|
||||
kubeconfig := os.Getenv("KUBECONFIG")
|
||||
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)
|
||||
}
|
||||
@@ -49,13 +61,37 @@ func TestK8S(t *testing.T) {
|
||||
}
|
||||
|
||||
serviceName := podName + "-svc"
|
||||
svc, err := k8s.CreateService(namespace, serviceName, "NodePort", ports, labels)
|
||||
if _, err := k8s.GetService(namespace, serviceName); err == nil {
|
||||
t.Fatalf("should get no service name %s", serviceName)
|
||||
}
|
||||
|
||||
svc, err := k8s.CreateService(namespace, serviceName, "NodePort", bindings, labels)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if svc.Name != serviceName {
|
||||
t.Fatalf("should get %s but got %s", serviceName, svc.Name)
|
||||
}
|
||||
svc, err = k8s.GetService(namespace, serviceName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if svc.Name != serviceName {
|
||||
t.Fatalf("should get %s but got %v", serviceName, svc.Name)
|
||||
}
|
||||
|
||||
selector := map[string]string{"hello": "world"}
|
||||
svc, err = k8s.UpdateService(namespace, serviceName, "NodePort", bindings, selector)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if svc.Name != serviceName {
|
||||
t.Fatalf("should get %s but got %v", serviceName, svc.Name)
|
||||
}
|
||||
if !reflect.DeepEqual(svc.Spec.Selector, selector) {
|
||||
t.Fatalf("should get %v but got %v", selector, svc.Spec.Selector)
|
||||
}
|
||||
|
||||
// TODO check service status
|
||||
if err := k8s.DeleteService(namespace, serviceName); 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",
|
||||
],
|
||||
61
middlewares/binding.go
Normal file
61
middlewares/binding.go
Normal file
@@ -0,0 +1,61 @@
|
||||
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.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") != "" {
|
||||
bindings = []types.PortBinding{
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 80,
|
||||
ContainerExposePort: constants.FxContainerExposePort,
|
||||
},
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 443,
|
||||
ContainerExposePort: constants.FxContainerExposePort,
|
||||
},
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: int32(port),
|
||||
ContainerExposePort: constants.FxContainerExposePort,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
bindings = []types.PortBinding{
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: int32(port),
|
||||
ContainerExposePort: constants.FxContainerExposePort,
|
||||
},
|
||||
}
|
||||
}
|
||||
ctx.Set("bindings", bindings)
|
||||
return nil
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
89
middlewares/build.go
Normal file
89
middlewares/build.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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/utils"
|
||||
"github.com/otiai10/copy"
|
||||
)
|
||||
|
||||
// Build image
|
||||
func Build(ctx context.Contexter) (err error) {
|
||||
const task = "building"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
|
||||
defer os.RemoveAll(workdir)
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
64
middlewares/parse.go
Normal file
64
middlewares/parse.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/metrue/fx/context"
|
||||
)
|
||||
|
||||
// Parse parse input
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user