Compare commits

...

20 Commits

Author SHA1 Message Date
Minghe
05ac2441da list service as table format (#382) 2019-12-03 15:41:03 +08:00
Minghe
c0009b1b64 request a port when not port given (#379)
* request a port when not port given
* make context an interface, for easy testing (#380)
2019-12-03 15:14:13 +08:00
dependabot-preview[bot]
82960824ef Bump github.com/urfave/cli from 1.22.1 to 1.22.2 (#370)
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.1 to 1.22.2.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.22.1...v1.22.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-29 08:54:02 +08:00
dependabot-preview[bot]
64b63cbd0f Bump gopkg.in/yaml.v2 from 2.2.4 to 2.2.7 (#378)
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.4 to 2.2.7.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.4...v2.2.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-28 08:41:26 +08:00
Minghe
05771fb07f read kubeconfig from .fx/config or env (#377) 2019-11-27 20:47:49 +08:00
Minghe
d1f680dacd auto load config (#376) 2019-11-27 18:09:06 +08:00
Minghe
14c9397b70 use ssh to install, no dependency on k3sup (#367) 2019-11-27 15:40:18 +08:00
Minghe
eb5e724899 disable aks ci since environment not ready (#362) 2019-11-17 13:07:23 +08:00
Minghe
80619bd800 release 0.8.4 (#361) 2019-11-17 12:17:58 +08:00
Minghe
8e2cdfc607 update docs (#360) 2019-11-17 11:29:08 +08:00
Minghe
58f416b7b2 support custom cluster setup by k3s (#359)
* support custom cluster setup by k3s
* clean up
* fix lint issue
2019-11-17 10:50:55 +08:00
Minghe
b6cf39e3e5 support k8s operator (#358) 2019-11-17 08:56:58 +08:00
Minghe
41bc98ab64 fx on k3s (#357) 2019-11-17 00:10:55 +08:00
Minghe
b007ac315a refactor output (#356) 2019-11-15 22:23:27 +08:00
Minghe
940f6b8f72 enable progress bar (#355)
* enable progress bar
* add spinner pkg
* fix lint
2019-11-15 21:17:38 +08:00
Minghe
f9690b74a5 auto startup fx agent during setup progress (#354)
* auto start fx agent during setup progress

* bump version
2019-11-15 19:41:17 +08:00
Minghe
f2c58d545a add document on ubunut (#352)
* add document on ubunut

* fix typo
2019-11-15 13:29:31 +08:00
Siddhesh Poyarekar
4732426629 Point to instructions to build from source for non-x86 targets (#350)
* Fix typos
* Point to instructions to build from source for non-x86 targets

Installation instructions do not specify clearly enough the fact that
they are supported only on x86. Make it clearer and point to the Build
and Test section in Contribute for instructions on building fx.
2019-11-14 15:22:25 +08:00
Minghe
d4af4f67b2 fix syntax error (#349) 2019-11-12 12:29:25 +08:00
Minghe
6420e8b6c6 add workflow graph (#348) 2019-11-12 12:26:33 +08:00
84 changed files with 2544 additions and 589 deletions

View File

@@ -13,31 +13,21 @@ jobs:
- name: check out
uses: actions/checkout@master
- name: setup docker
- name: kind create a k8s cluster
run: |
./scripts/provision.sh
kind create cluster
- name: lint
run: |
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint \
golangci-lint run -v
- name: setup k8s and kind
run: |
export GOBIN=$(go env GOPATH)/bin
export PATH=$PATH:$GOBIN
mkdir -p $GOBIN
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x kubectl && mv kubectl $GOBIN
wget https://github.com/kubernetes-sigs/kind/releases/download/v0.5.0/kind-linux-amd64 && chmod +x kind-linux-amd64 && mv kind-linux-amd64 $GOBIN/kind
./scripts/setup_kind.sh
- name: unit test
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: |
export KUBECONFIG=/home/runner/.kube/kind-config-fx-test
export KUBECONFIG="$(kind get kubeconfig-path)"
DEBUG=true go test -v ./...
- name: code cov
env:
@@ -57,6 +47,11 @@ jobs:
make docker-build
make test
# make docker-publish #TODO in release workflow
- name: test fx docker cloud
run: |
make start_docker_infra
make test_docker_infra
make stop_docker_infra
- name: test fx cli
env:
@@ -66,21 +61,22 @@ jobs:
run: |
echo $KUBECONFIG
unset KUBECONFIG
make cli-test
make cli-test-ci
- name: test AKS
env:
AKS_KUBECONFIG: ${{ secrets.AKS_KUBECONFIG }}
run: |
export KUBECONFIG=${HOME}/.kube/aks
echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
if [[ -z "$AKS_KUBECONFIG" ]];then
echo "skip deploy test since no valid KUBECONFIG"
else
DEBUG=true ./build/fx up -n hello -p 12345 examples/functions/JavaScript/func.js
./build/fx down hello
rm ${KUBECONFIG}
fi
echo "skip since aks environment not ready yet"
# export KUBECONFIG=${HOME}/.kube/aks
# echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
# if [[ -z "$AKS_KUBECONFIG" ]];then
# echo "skip deploy test since no valid KUBECONFIG"
# else
# DEBUG=true ./build/fx up -n hello -p 12345 examples/functions/JavaScript/func.js
# ./build/fx down hello
# rm ${KUBECONFIG}
# fi
Installation:
runs-on: ${{ matrix.os }}

View File

@@ -18,31 +18,21 @@ jobs:
- name: check out
uses: actions/checkout@master
- name: setup docker
- name: kind create a k8s cluster
run: |
./scripts/provision.sh
kind create cluster
- name: lint
run: |
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint \
golangci-lint run -v
- name: setup k8s and kind
run: |
export GOBIN=$(go env GOPATH)/bin
export PATH=$PATH:$GOBIN
mkdir -p $GOBIN
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x kubectl && mv kubectl $GOBIN
wget https://github.com/kubernetes-sigs/kind/releases/download/v0.5.0/kind-linux-amd64 && chmod +x kind-linux-amd64 && mv kind-linux-amd64 $GOBIN/kind
./scripts/setup_kind.sh
- name: unit test
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: |
export KUBECONFIG=/home/runner/.kube/kind-config-fx-test
export KUBECONFIG="$(kind get kubeconfig-path)"
DEBUG=true go test -v ./container_runtimes/... ./deploy/...
- name: build fx
@@ -61,11 +51,12 @@ jobs:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
export KUBECONFIG=${HOME}/.kube/aks
echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
DEBUG=true ./build/fx up -n hello -p 12345 examples/functions/JavaScript/func.js
./build/fx down hello
rm ${KUBECONFIG}
echo "skip since aks environment not ready yet"
# export KUBECONFIG=${HOME}/.kube/aks
# echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
# DEBUG=true ./build/fx up -n hello -p 12345 examples/functions/JavaScript/func.js
# ./build/fx down hello
# rm ${KUBECONFIG}
Release:
runs-on: ${{ matrix.os }}
needs: [Test]

View File

@@ -1,5 +1,7 @@
OUTPUT_DIR=./build
DIST_DIR=./dist
OUTPUT_DIR ?=./build
DIST_DIR ?=./dist
DOCKER_REMOTE_HOST_ADDR ?= "127.0.0.1"
DOCKER_REMOTE_HOST_USER ?= $(whoami)
lint:
golangci-lint run
@@ -26,12 +28,11 @@ clean:
unit-test:
./scripts/coverage.sh
cli-test-ci:
./scripts/test_cli.sh 'js'
cli-test:
echo 'run testing on localhost'
./scripts/test_cli.sh
# TODO enable remote test
echo 'run testing on remote host'
DOCKER_REMOTE_HOST_ADDR=${REMOTE_HOST_ADDR} DOCKER_REMOTE_HOST_USER=${REMOTE_HOST_USER} DOCKER_REMOTE_HOST_PASSWORD=${REMOTE_HOST_PASSWORD} ./scripts/test_cli.sh
./scripts/test_cli.sh 'js rb py go php java d'
http-test:
./scripts/http_test.sh
@@ -39,3 +40,27 @@ http-test:
zip:
zip -r images.zip images/
.PHONY: test build start list clean generate
start_docker_infra:
docker build -t fx-docker-infra -f test/Dockerfile ./test
docker run --rm --name fx-docker-infra -p 2222:22 -v /var/run/docker.sock:/var/run/docker.sock -d fx-docker-infra
test_docker_infra:
CICD=true SSH_PORT=2222 SSH_KEY_FILE=./test/id_rsa ./build/fx infra create --name docker-local -t docker --host root@127.0.0.1
stop_docker_infra:
docker stop fx-docker-infra
start_k3s_infra:
multipass launch --name k3s-master --cpus 1 --mem 512M --disk 3G --cloud-init ./test/k3s/ssh-cloud-init.yaml
multipass launch --name k3s-worker1 --cpus 1 --mem 512M --disk 3G --cloud-init ./test/k3s/ssh-cloud-init.yaml
multipass launch --name k3s-worker2 --cpus 1 --mem 512M --disk 3G --cloud-init ./test/k3s/ssh-cloud-init.yaml
test_k3s_infra:
./scripts/test_k3s_infra.sh
stop_k3s_infra:
multipass delete k3s-master
multipass delete k3s-worker1
multipass delete k3s-worker2
multipass purge

View File

@@ -18,6 +18,8 @@ Poor man's function as a service.
## Introduction
![workflow](https://raw.githubusercontent.com/metrue/fx/master/docs/fx-workflow.png)
fx is a tool to help you do Function as a Service on your own server, fx can make your stateless function a service in seconds, both Docker host and Kubernetes cluster supported. The most exciting thing is that you can write your functions with most programming languages.
Feel free hacking fx to support the languages not listed. Welcome to tweet me [@_metrue](https://twitter.com/_metrue) on Twitter, [@metrue](https://www.weibo.com/u/2165714507) on Weibo.
@@ -38,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
```
@@ -57,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;
@@ -75,10 +79,10 @@ USAGE:
fx [global options] command [command options] [arguments...]
VERSION:
0.8.1
0.8.4
COMMANDS:
init start fx agent on host
infra manage infrastructure
up deploy a function
down destroy a service
list, ls list deployed services
@@ -218,6 +222,14 @@ But we would suggest you run `kubectl config current-context` to check if the cu
* Google Kubernetes Engine (GKET)
TODO
* Setup your own Kubernetes cluster
![init workflow](https://raw.githubusercontent.com/metrue/fx/master/docs/fx-init-cluster.png)
```shell
fx infra create --type k3s --name fx-cluster-1 --master root@123.11.2.3 --agents 'root@1.1.1.1,root@2.2.2.2'
```
## Contribute
fx uses [Project](https://github.com/metrue/fx/projects/4) to manage the development.
@@ -227,6 +239,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
```

195
config/config.go Normal file
View File

@@ -0,0 +1,195 @@
package config
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/user"
"path"
"sync"
"github.com/metrue/fx/utils"
"github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v2"
)
// Items data of config file
type Items struct {
Clouds map[string]map[string]string `json:"clouds"`
CurrentCloud string `json:"current_cloud"`
}
// Config config of fx
type Config struct {
mux sync.Mutex
configFile string
Items
}
// LoadDefault load default config
func LoadDefault() (*Config, error) {
configFile, err := homedir.Expand("~/.fx/config.yml")
if err != nil {
return nil, err
}
if os.Getenv("FX_CONFIG") != "" {
configFile = os.Getenv("FX_CONFIG")
}
if _, err := os.Stat(configFile); os.IsNotExist(err) {
if err := utils.EnsureFile(configFile); err != nil {
return nil, err
}
if err := writeDefaultConfig(configFile); err != nil {
return nil, err
}
}
return load(configFile)
}
// Load config
func Load(configFile string) (*Config, error) {
if configFile == "" {
return nil, fmt.Errorf("invalid config file")
}
if _, err := os.Stat(configFile); os.IsNotExist(err) {
if err := utils.EnsureFile(configFile); err != nil {
return nil, err
}
if err := writeDefaultConfig(configFile); err != nil {
return nil, err
}
}
return load(configFile)
}
// AddCloud add a cloud
func (c *Config) addCloud(name string, cloud map[string]string) error {
c.Items.Clouds[name] = cloud
return save(c)
}
// AddDockerCloud add docker cloud
func (c *Config) AddDockerCloud(name string, config []byte) error {
c.mux.Lock()
defer c.mux.Unlock()
var conf map[string]string
err := json.Unmarshal(config, &conf)
if err != nil {
return err
}
cloud := map[string]string{
"type": "docker",
"host": conf["ip"],
"user": conf["user"],
}
return c.addCloud(name, cloud)
}
// AddK8SCloud add k8s cloud
func (c *Config) AddK8SCloud(name string, kubeconfig []byte) error {
c.mux.Lock()
defer c.mux.Unlock()
dir := path.Dir(c.configFile)
kubecfg := path.Join(dir, name+".kubeconfig")
if err := utils.EnsureFile(kubecfg); err != nil {
return err
}
if err := ioutil.WriteFile(kubecfg, kubeconfig, 0666); err != nil {
return err
}
cloud := map[string]string{
"type": "k8s",
"kubeConfig": kubecfg,
}
return c.addCloud(name, cloud)
}
// Use set cloud instance with name as current context
func (c *Config) Use(name string) error {
c.mux.Lock()
defer c.mux.Unlock()
has := false
for n := range c.Clouds {
if n == name {
has = true
break
}
}
if !has {
return fmt.Errorf("no cloud with name = %s", name)
}
c.Items.CurrentCloud = name
return save(c)
}
// View view current config
func (c *Config) View() ([]byte, error) {
c.mux.Lock()
defer c.mux.Unlock()
return ioutil.ReadFile(c.configFile)
}
func load(configFile string) (*Config, error) {
conf, err := ioutil.ReadFile(configFile)
if err != nil {
return nil, err
}
var items Items
if err := yaml.Unmarshal(conf, &items); err != nil {
return nil, err
}
var c = Config{
configFile: configFile,
Items: items,
}
return &c, nil
}
func save(c *Config) error {
conf, err := yaml.Marshal(c.Items)
if err != nil {
return err
}
if err := ioutil.WriteFile(c.configFile, conf, 0666); err != nil {
return err
}
return nil
}
func writeDefaultConfig(configFile string) error {
me, err := user.Current()
if err != nil {
return err
}
items := Items{
Clouds: map[string]map[string]string{
"default": map[string]string{
"type": "docker",
"host": "127.0.0.1",
"user": me.Username,
},
},
CurrentCloud: "default",
}
body, err := yaml.Marshal(items)
if err != nil {
return err
}
if err := ioutil.WriteFile(configFile, body, 0666); err != nil {
return err
}
return nil
}

65
config/config_test.go Normal file
View File

@@ -0,0 +1,65 @@
package config
import (
"encoding/json"
"fmt"
"os"
"testing"
)
func TestConfig(t *testing.T) {
configPath := "./tmp/config.yml"
defer func() {
if err := os.RemoveAll("./tmp"); err != nil {
t.Fatal(err)
}
}()
c, err := Load(configPath)
if err != nil {
t.Fatal(err)
}
if len(c.Clouds) != 1 {
t.Fatal("should contain default cloud")
}
name := "fx_cluster_1"
if err := c.Use(name); err == nil {
t.Fatal("should get no such cloud error")
}
if err := c.AddK8SCloud(name, []byte("sampe kubeconfg")); err != nil {
t.Fatal(err)
}
config := map[string]string{
"ip": "127.0.0.1",
"user": "use1",
}
configData, _ := json.Marshal(config)
if err := c.AddDockerCloud("docker-1", configData); err != nil {
t.Fatal(err)
}
if err := c.Use(name); err != nil {
t.Fatal(err)
}
if c.CurrentCloud != name {
t.Fatalf("should get %s but got %s", name, c.CurrentCloud)
}
conf, err := Load(configPath)
if err != nil {
t.Fatal(err)
}
if conf.CurrentCloud != name {
t.Fatalf("should get %s but got %s", name, c.CurrentCloud)
}
body, err := c.View()
if err != nil {
t.Fatal(err)
}
fmt.Println(string(body))
}

7
config/types.go Normal file
View File

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

View File

@@ -377,8 +377,6 @@ func (api *API) StartContainer(ctx context.Context, name string, image string, b
return fmt.Errorf("container id is missing")
}
log.Infof("container %s created", name)
// start container
path = fmt.Sprintf("/containers/%s/start", createRes.ID)
url := fmt.Sprintf("%s%s", api.endpoint, path)
@@ -402,7 +400,6 @@ func (api *API) StartContainer(ctx context.Context, name string, image string, b
msg := fmt.Sprintf("start container met issue: %s", string(b))
return errors.New(msg)
}
log.Infof("container %s started", name)
if _, err = api.inspect(createRes.ID); err != nil {
msg := fmt.Sprintf("inspect container %s error", name)

View File

@@ -76,7 +76,9 @@ func (d *Docker) BuildImage(ctx context.Context, workdir string, name string) er
if err != nil {
return err
}
log.Info(string(body))
if os.Getenv("DEBUG") != "" {
log.Info(string(body))
}
return nil
}

View File

@@ -12,6 +12,15 @@ const (
keyCliCtx = key("cmd_cli")
)
// Contexter ctx interface
type Contexter interface {
Get(k string) interface{}
Set(k string, v interface{})
Use(fn func(ctx *Context) error) error
GetContext() context.Context
GetCliContext() *cli.Context
}
// Context fx context
type Context struct {
context.Context
@@ -56,3 +65,12 @@ func (ctx *Context) Get(name string) interface{} {
func (ctx *Context) Use(fn func(ctx *Context) error) error {
return fn(ctx)
}
// GetContext get context
func (ctx *Context) GetContext() context.Context {
return ctx.Context
}
var (
_ Contexter = &Context{}
)

View File

@@ -22,4 +22,8 @@ func TestContext(t *testing.T) {
if v != value {
t.Fatalf("should get %v but %v", value, v)
}
if ctx.GetContext() == nil {
t.Fatalf("should get context")
}
}

104
context/mocks/context.go Normal file
View 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))
}

View File

@@ -8,7 +8,7 @@ import (
// Deployer make a image a service
type Deployer interface {
Deploy(ctx context.Context, fn types.Func, name string, bindings []types.PortBinding) error
Deploy(ctx context.Context, fn types.Func, name string, image string, bindings []types.PortBinding) error
Destroy(ctx context.Context, name string) error
Update(ctx context.Context, name string) error
GetStatus(ctx context.Context, name string) error

View File

@@ -2,20 +2,10 @@ package docker
import (
"context"
"fmt"
"log"
"os"
"time"
dockerTypes "github.com/docker/docker/api/types"
"github.com/metrue/fx/constants"
containerruntimes "github.com/metrue/fx/container_runtimes"
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
dockerSDK "github.com/metrue/fx/container_runtimes/docker/sdk"
"github.com/metrue/fx/deploy"
"github.com/metrue/fx/packer"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
)
// Docker manage container
@@ -24,58 +14,13 @@ type Docker struct {
}
// CreateClient create a docker instance
func CreateClient(ctx context.Context) (d *Docker, err error) {
var cli containerruntimes.ContainerRuntime
host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
user := os.Getenv("DOCKER_REMOTE_HOST_USER")
if host != "" && user != "" {
cli, err = dockerHTTP.Create(host, constants.AgentPort)
if err != nil {
return nil, err
}
} else {
cli, err = dockerSDK.CreateClient(ctx)
if err != nil {
return nil, err
}
}
return &Docker{cli: cli}, nil
func CreateClient(client containerruntimes.ContainerRuntime) (d *Docker, err error) {
return &Docker{cli: client}, nil
}
// Deploy create a Docker container from given image, and bind the constants.FxContainerExposePort to given port
func (d *Docker) Deploy(ctx context.Context, fn types.Func, name string, ports []types.PortBinding) error {
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
defer os.RemoveAll(workdir)
if err := packer.PackIntoDir(fn, workdir); err != nil {
log.Fatalf("could not pack function %v: %v", fn, err)
return err
}
if err := d.cli.BuildImage(ctx, workdir, name); err != nil {
log.Fatalf("could not build image: %v", err)
return err
}
nameWithTag := name + ":latest"
if err := d.cli.TagImage(ctx, name, nameWithTag); err != nil {
log.Fatalf("could not tag image: %v", err)
return err
}
// when deploy a function on a bare Docker running without Kubernetes,
// image would be built on-demand on host locally, so there is no need to
// pull image from remote.
// But it takes some times waiting image ready after image built, we retry to make sure it ready here
var imgInfo dockerTypes.ImageInspect
if err := utils.RunWithRetry(func() error {
return d.cli.InspectImage(ctx, name, &imgInfo)
}, time.Second*1, 5); err != nil {
return err
}
return d.cli.StartContainer(ctx, name, name, ports)
func (d *Docker) Deploy(ctx context.Context, fn types.Func, name string, image string, ports []types.PortBinding) error {
return d.cli.StartContainer(ctx, name, image, ports)
}
// Update a container

View File

@@ -1,47 +1,43 @@
package docker
import (
"context"
"testing"
"time"
"github.com/metrue/fx/types"
)
func TestDocker(t *testing.T) {
ctx := context.Background()
cli, err := CreateClient(ctx)
if err != nil {
t.Fatal(err)
}
name := "helloworld"
bindings := []types.PortBinding{
types.PortBinding{
ServiceBindingPort: 80,
ContainerExposePort: 3000,
},
types.PortBinding{
ServiceBindingPort: 443,
ContainerExposePort: 3000,
},
}
fn := types.Func{
Language: "node",
Source: `
module.exports = (ctx) => {
ctx.body = 'hello world'
}
`,
}
if err := cli.Deploy(ctx, fn, name, bindings); err != nil {
t.Fatal(err)
}
time.Sleep(1 * time.Second)
if err := cli.Destroy(ctx, name); err != nil {
t.Fatal(err)
}
// 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)
// }
}

View File

@@ -1,4 +1,4 @@
package kubernetes
package k3s
// ConfigMap is the key to function docker project source code in configmap
var ConfigMap = struct {

102
deploy/k3s/deployment.go Normal file
View File

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

View File

@@ -1,4 +1,4 @@
package kubernetes
package k3s
import (
"os"
@@ -20,7 +20,7 @@ func TestDeployment(t *testing.T) {
t.Skip("skip test since no KUBECONFIG given in environment variable")
}
k8s, err := Create()
k8s, err := Create("")
if err != nil {
t.Fatal(err)
}

View File

@@ -1,4 +1,4 @@
package kubernetes
package k3s
import (
appsv1 "k8s.io/api/apps/v1"

135
deploy/k3s/k3s.go Normal file
View File

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

87
deploy/k3s/service.go Normal file
View File

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

View File

@@ -1,4 +1,4 @@
package kubernetes
package k8s
import (
apiv1 "k8s.io/api/core/v1"

View File

@@ -1,4 +1,4 @@
package kubernetes
package k8s
import (
"os"
@@ -11,7 +11,7 @@ func TestConfigMap(t *testing.T) {
t.Skip("skip test since no KUBECONFIG given in environment variable")
}
k8s, err := Create()
k8s, err := Create("")
if err != nil {
t.Fatal(err)
}

8
deploy/k8s/constants.go Normal file
View 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",
}

View File

@@ -1,4 +1,4 @@
package kubernetes
package k8s
import (
"fmt"
@@ -29,7 +29,7 @@ func generateDeploymentSpec(
Name: "fx-placeholder-container-name",
Image: image,
Ports: ports,
ImagePullPolicy: v1.PullNever,
ImagePullPolicy: v1.PullIfNotPresent,
}
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
@@ -98,6 +98,5 @@ func (k *K8S) CreateDeploymentWithInitContainer(
) (*appsv1.Deployment, error) {
deployment := generateDeploymentSpec(name, name, ports, replicas, selector)
updatedDeployment := injectInitContainer(name, deployment)
fmt.Println(updatedDeployment)
return k.AppsV1().Deployments(namespace).Create(updatedDeployment)
}

View 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)
}
}

View 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
}

View File

@@ -1,7 +1,8 @@
package kubernetes
package k8s
import (
"context"
"os"
"github.com/metrue/fx/deploy"
"github.com/metrue/fx/packer"
@@ -18,8 +19,12 @@ type K8S struct {
const namespace = "default"
// Create a k8s cluster client
func Create() (*K8S, error) {
config, err := clientcmd.BuildConfigFromKubeconfigGetter("", clientcmd.NewDefaultClientConfigLoadingRules().Load)
func Create(kubeconfig string) (*K8S, error) {
if os.Getenv("KUBECONFIG") != "" {
kubeconfig = os.Getenv("KUBECONFIG")
}
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, err
}
@@ -36,6 +41,7 @@ func (k *K8S) Deploy(
ctx context.Context,
fn types.Func,
name string,
image string,
ports []types.PortBinding,
) error {
// put source code of function docker project into k8s config map
@@ -80,10 +86,9 @@ func (k *K8S) Deploy(
// 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 os.Getenv("SERVICE_TYPE") != "" {
typ = os.Getenv("SERVICE_TYPE")
}
if _, err := k.GetService(namespace, name); err != nil {

View File

@@ -1,4 +1,4 @@
package kubernetes
package k8s
import (
"context"
@@ -26,7 +26,7 @@ func TestK8SDeployer(t *testing.T) {
if kubeconfig == "" || username == "" || password == "" {
t.Skip("skip test since no KUBECONFIG, DOCKER_USERNAME and DOCKER_PASSWORD given in environment variable")
}
k8s, err := Create()
k8s, err := Create("")
if err != nil {
t.Fatal(err)
}
@@ -40,7 +40,7 @@ module.exports = (ctx) => {
`,
}
ctx := context.Background()
if err := k8s.Deploy(ctx, fn, name, bindings); err != nil {
if err := k8s.Deploy(ctx, fn, name, name, bindings); err != nil {
t.Fatal(err)
}

View File

@@ -1,4 +1,4 @@
package kubernetes
package k8s
import (
"github.com/metrue/fx/constants"

View File

@@ -1,4 +1,4 @@
package kubernetes
package k8s
import (
"strconv"

View File

@@ -1,4 +1,4 @@
package kubernetes
package k8s
import (
"os"
@@ -27,7 +27,7 @@ func TestK8S(t *testing.T) {
if kubeconfig == "" {
t.Skip("skip test since no KUBECONFIG given in environment variable")
}
k8s, err := Create()
k8s, err := Create("")
if err != nil {
t.Fatal(err)
}

106
deploy/mocks/deployer.go Normal file
View File

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

BIN
docs/fx-init-cluster.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

BIN
docs/fx-workflow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -1,16 +0,0 @@
# fx on Amazon Lightsai
* make sure your instance have docker installed and running,
* make sure your instance can be ssh login (with user and password)
```
ssh <user>@<host>
```
* make sure your instance accept port 8866
* then you can deploy function to remote host
```
DOCKER_REMOTE_HOST_ADDR=<your host> DOCKER_REMOTE_HOST_USER=<your user> DOCKER_REMOTE_HOST_PASSWORD=<your password> ./build/fx up -p 1234 test/functions/func.js
```

46
docs/ubuntu.md Normal file
View 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
```

153
fx.go
View File

@@ -9,18 +9,31 @@ import (
"github.com/apex/log"
"github.com/google/uuid"
aurora "github.com/logrusorgru/aurora"
"github.com/metrue/fx/context"
"github.com/metrue/fx/handlers"
"github.com/metrue/fx/middlewares"
"github.com/urfave/cli"
)
const version = "0.8.1"
const version = "0.8.5"
func init() {
go checkForUpdate()
}
func handle(fns ...func(ctx context.Contexter) error) func(ctx *cli.Context) error {
return func(c *cli.Context) error {
ctx := context.FromCliContext(c)
for _, fn := range fns {
if err := fn(ctx); err != nil {
panic(err)
}
}
return nil
}
}
func checkForUpdate() {
const releaseURL = "https://api.github.com/repos/metrue/fx/releases/latest"
resp, err := http.Get(releaseURL)
@@ -56,12 +69,66 @@ func main() {
app.Usage = "makes function as a service"
app.Version = version
defer func() {
if r := recover(); r != nil {
fmt.Println(aurora.Red("*****************"))
fmt.Println(r)
fmt.Println(aurora.Red("*****************"))
}
}()
app.Commands = []cli.Command{
{
Name: "init",
Usage: "start fx agent on host",
Action: func(c *cli.Context) error {
return handlers.Init()(context.FromCliContext(c))
Name: "infra",
Usage: "manage infrastructure",
Subcommands: []cli.Command{
{
Name: "create",
Usage: "create a infra for fx service",
Flags: []cli.Flag{
cli.StringFlag{
Name: "type, t",
Usage: "infracture type, 'docker', 'k8s' and 'k3s' support",
},
cli.StringFlag{
Name: "name, n",
Usage: "name to identify the infrastructure",
},
cli.StringFlag{
Name: "host",
Usage: "user and ip of your host, eg. 'root@182.12.1.12'",
},
cli.StringFlag{
Name: "master",
Usage: "serve as master node in K3S cluster, eg. 'root@182.12.1.12'",
},
cli.StringFlag{
Name: "agents",
Usage: "serve as agent node in K3S cluster, eg. 'root@187.1. 2. 3,root@123.3.2.1'",
},
},
Action: handle(
middlewares.LoadConfig,
handlers.Setup,
),
},
{
Name: "list",
Usage: "list all infrastructures",
Action: handle(
middlewares.LoadConfig,
handlers.ListInfra,
),
},
{
Name: "use",
Usage: "set current context to target cloud with given name",
Action: handle(
middlewares.LoadConfig,
handlers.UseInfra,
),
},
},
},
{
@@ -87,40 +154,34 @@ func main() {
Usage: "force deploy a function or functions",
},
},
Action: func(c *cli.Context) error {
ctx := context.FromCliContext(c)
if err := ctx.Use(middlewares.Setup); err != nil {
log.Fatalf("%v", err)
}
if err := ctx.Use(middlewares.Binding); err != nil {
log.Fatalf("%v", err)
}
return handlers.Up()(ctx)
},
Action: handle(
middlewares.LoadConfig,
middlewares.Setup,
middlewares.Parse,
middlewares.Binding,
middlewares.Build,
handlers.Up,
),
},
{
Name: "down",
Usage: "destroy a service",
ArgsUsage: "[service 1, service 2, ....]",
Action: func(c *cli.Context) error {
ctx := context.FromCliContext(c)
if err := ctx.Use(middlewares.Setup); err != nil {
log.Fatalf("%v", err)
}
return handlers.Down()(ctx)
},
Action: handle(
middlewares.LoadConfig,
middlewares.Setup,
handlers.Down,
),
},
{
Name: "list",
Aliases: []string{"ls"},
Usage: "list deployed services",
Action: func(c *cli.Context) error {
ctx := context.FromCliContext(c)
if err := ctx.Use(middlewares.Setup); err != nil {
log.Fatalf("%v", err)
}
return handlers.List()(ctx)
},
Action: handle(
middlewares.LoadConfig,
middlewares.Setup,
handlers.List,
),
},
{
Name: "call",
@@ -131,9 +192,7 @@ func main() {
Usage: "fx server host, default is localhost",
},
},
Action: func(c *cli.Context) error {
return handlers.Call()(context.FromCliContext(c))
},
Action: handle(handlers.Call),
},
{
Name: "image",
@@ -148,13 +207,11 @@ func main() {
Usage: "image tag",
},
},
Action: func(c *cli.Context) error {
ctx := context.FromCliContext(c)
if err := ctx.Use(middlewares.Setup); err != nil {
log.Fatalf("%v", err)
}
return handlers.BuildImage()(ctx)
},
Action: handle(
middlewares.LoadConfig,
middlewares.Setup,
handlers.BuildImage,
),
},
{
Name: "export",
@@ -165,22 +222,22 @@ func main() {
Usage: "output directory",
},
},
Action: func(c *cli.Context) error {
return handlers.ExportImage()(context.FromCliContext(c))
},
Action: handle(
middlewares.LoadConfig,
middlewares.Setup,
handlers.ExportImage,
),
},
},
},
{
Name: "doctor",
Usage: "health check for fx",
Action: func(c *cli.Context) error {
return handlers.Doctor()(context.FromCliContext(c))
},
Name: "doctor",
Usage: "health check for fx",
Action: handle(handlers.Doctor),
},
}
if err := app.Run(os.Args); err != nil {
log.Fatalf("fx startup with fatal: %v", err)
os.Exit(1)
}
}

16
go.mod
View File

@@ -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,24 +21,27 @@ 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/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.3
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/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.5.0
github.com/stretchr/testify v1.4.0
github.com/ugorji/go v1.1.7 // indirect
github.com/urfave/cli v1.22.1
github.com/urfave/cli v1.22.2
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect
gopkg.in/h2non/gock.v1 v1.0.15
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
google.golang.org/grpc v1.21.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.7
gotest.tools v2.2.0+incompatible // indirect
k8s.io/api v0.0.0-20190925180651-d58b53da08f5
k8s.io/apimachinery v0.0.0-20190925235427-62598f38f24e

101
go.sum
View File

@@ -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=
@@ -64,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=
@@ -90,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=
@@ -111,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=
@@ -131,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=
@@ -150,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=
@@ -174,23 +149,26 @@ 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/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=
@@ -204,13 +182,11 @@ 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/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=
@@ -221,28 +197,17 @@ github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQ
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/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=
@@ -254,15 +219,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=
@@ -271,16 +233,10 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4=
github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -290,13 +246,10 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
@@ -307,16 +260,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=
@@ -334,15 +283,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=
@@ -362,8 +307,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=
@@ -383,7 +326,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=
@@ -392,6 +334,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=
@@ -405,7 +348,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=
@@ -415,23 +357,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=

View File

@@ -3,15 +3,11 @@ set -e
# ++
# verified on Ubuntu 16.04 x64
# ++
user_host=$1
ssh ${user_host} 'bash -s' <<EOF
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
EOF
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

View File

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

View File

@@ -10,19 +10,17 @@ import (
)
// Doctor command handle
func Doctor() HandleFunc {
return func(ctx *context.Context) error {
host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
user := os.Getenv("DOCKER_REMOTE_HOST_USER")
password := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD")
if host == "" {
host = "localhost"
}
if err := doctor.New(host, user, password).Start(); err != nil {
log.Warnf("machine %s is in dirty state: %v", host, err)
} else {
log.Infof("machine %s is in healthy state: %s", host, constants.CheckedSymbol)
}
return nil
func Doctor(ctx context.Contexter) error {
host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
user := os.Getenv("DOCKER_REMOTE_HOST_USER")
password := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD")
if host == "" {
host = "localhost"
}
if err := doctor.New(host, user, password).Start(); err != nil {
log.Warnf("machine %s is in dirty state: %v", host, err)
} else {
log.Infof("machine %s is in healthy state: %s", host, constants.CheckedSymbol)
}
return nil
}

View File

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

View File

@@ -1,8 +0,0 @@
package handlers
import (
"github.com/metrue/fx/context"
)
// HandleFunc command handle function
type HandleFunc func(ctx *context.Context) error

View File

@@ -18,68 +18,64 @@ import (
)
// BuildImage build image
func BuildImage() HandleFunc {
return func(ctx *context.Context) error {
cli := ctx.GetCliContext()
funcFile := cli.Args().First()
tag := cli.String("tag")
if tag == "" {
tag = uuid.New().String()
}
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)
fn := types.Func{Language: lang, Source: string(body)}
if err := packer.PackIntoDir(fn, workdir); err != nil {
log.Fatalf("could not pack function %v: %v", fn, err)
return err
}
docker, ok := ctx.Get("docker").(containerruntimes.ContainerRuntime)
if ok {
nameWithTag := tag + ":latest"
if err := docker.BuildImage(ctx.Context, workdir, nameWithTag); err != nil {
return err
}
log.Infof("image built: %v", constants.CheckedSymbol)
return nil
}
return fmt.Errorf("no available docker cli")
func BuildImage(ctx context.Contexter) error {
cli := ctx.GetCliContext()
funcFile := cli.Args().First()
tag := cli.String("tag")
if tag == "" {
tag = uuid.New().String()
}
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)
fn := types.Func{Language: lang, Source: string(body)}
if err := packer.PackIntoDir(fn, workdir); err != nil {
log.Fatalf("could not pack function %v: %v", fn, err)
return err
}
docker, ok := ctx.Get("docker").(containerruntimes.ContainerRuntime)
if ok {
nameWithTag := tag + ":latest"
if err := docker.BuildImage(ctx.GetContext(), workdir, nameWithTag); err != nil {
return err
}
log.Infof("image built: %v", constants.CheckedSymbol)
return nil
}
return fmt.Errorf("no available docker cli")
}
// ExportImage export service's code into a directory
func ExportImage() HandleFunc {
return func(ctx *context.Context) (err error) {
cli := ctx.GetCliContext()
funcFile := cli.Args().First()
outputDir := cli.String("output")
if outputDir == "" {
log.Fatalf("output directory required")
return nil
}
body, err := ioutil.ReadFile(funcFile)
if err != nil {
return errors.Wrap(err, "read source failed")
}
lang := utils.GetLangFromFileName(funcFile)
if err := packer.PackIntoDir(types.Func{Language: lang, Source: string(body)}, outputDir); err != nil {
log.Fatalf("write source code to file failed: %v", constants.UncheckedSymbol)
return err
}
log.Infof("exported to %v: %v", outputDir, constants.CheckedSymbol)
func ExportImage(ctx context.Contexter) (err error) {
cli := ctx.GetCliContext()
funcFile := cli.Args().First()
outputDir := cli.String("output")
if outputDir == "" {
log.Fatalf("output directory required")
return nil
}
body, err := ioutil.ReadFile(funcFile)
if err != nil {
return errors.Wrap(err, "read source failed")
}
lang := utils.GetLangFromFileName(funcFile)
if err := packer.PackIntoDir(types.Func{Language: lang, Source: string(body)}, outputDir); err != nil {
log.Fatalf("write source code to file failed: %v", constants.UncheckedSymbol)
return err
}
log.Infof("exported to %v: %v", outputDir, constants.CheckedSymbol)
return nil
}

100
handlers/infra.go Normal file
View File

@@ -0,0 +1,100 @@
package handlers
import (
"fmt"
"strings"
"github.com/metrue/fx/config"
"github.com/metrue/fx/context"
"github.com/metrue/fx/infra/docker"
"github.com/metrue/fx/infra/k3s"
"github.com/metrue/fx/pkg/spinner"
)
func setupK3S(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 := k3s.MasterNode{
User: info[0],
IP: info[1],
}
agents := []k3s.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, k3s.AgentNode{
User: info[0],
IP: info[1],
})
}
}
fmt.Println(master, agents, len(agents))
k3sOperator := k3s.New(master, agents)
return k3sOperator.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 := docker.New(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 == "k3s" {
if cli.String("master") == "" {
return fmt.Errorf("master required, eg. 'root@123.1.2.12'")
}
} else if typ == "k8s" {
} else {
return fmt.Errorf("invalid type, 'docker', 'k3s' and 'k8s' support")
}
fxConfig := ctx.Get("config").(*config.Config)
switch strings.ToLower(typ) {
case "k3s":
kubeconf, err := setupK3S(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)
case "k8s":
fmt.Println("WIP")
}
return nil
}

View File

@@ -1,31 +0,0 @@
package handlers
import (
"os"
"github.com/apex/log"
"github.com/metrue/fx/context"
"github.com/metrue/fx/provision"
)
// Init start fx-agent
func Init() HandleFunc {
return func(ctx *context.Context) error {
host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
user := os.Getenv("DOCKER_REMOTE_HOST_USER")
passord := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD")
if host == "" {
host = "127.0.0.1"
}
provisioner := provision.NewWithHost(host, user, passord)
if !provisioner.IsFxAgentRunning() {
if err := provisioner.StartFxAgent(); err != nil {
log.Fatalf("could not start fx agent on host: %s", err)
return err
}
log.Info("fx agent started")
}
log.Info("fx agent already started")
return nil
}
}

View File

@@ -3,26 +3,26 @@ package handlers
import (
"github.com/metrue/fx/context"
"github.com/metrue/fx/deploy"
"github.com/metrue/fx/utils"
"github.com/metrue/fx/pkg/render"
"github.com/metrue/fx/pkg/spinner"
)
// List command handle
func List() HandleFunc {
return func(ctx *context.Context) error {
cli := ctx.GetCliContext()
deployer := ctx.Get("deployer").(deploy.Deployer)
func List(ctx context.Contexter) (err error) {
const task = "deploying"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
services, err := deployer.List(ctx.Context, cli.Args().First())
if err != nil {
return err
}
cli := ctx.GetCliContext()
deployer := ctx.Get("deployer").(deploy.Deployer)
for _, service := range services {
if err := utils.OutputJSON(service); err != nil {
return err
}
}
return nil
services, err := deployer.List(ctx.GetContext(), cli.Args().First())
if err != nil {
return err
}
render.Table(services)
return nil
}

19
handlers/list_infra.go Normal file
View 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
}

View File

@@ -1,61 +1,30 @@
package handlers
import (
"fmt"
"io/ioutil"
"github.com/apex/log"
"github.com/metrue/fx/context"
"github.com/metrue/fx/deploy"
"github.com/metrue/fx/pkg/spinner"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
"github.com/pkg/errors"
)
// PortRange usable port range https: //en.wikipedia.org/wiki/Ephemeral_port
var PortRange = struct {
min int
max int
}{
min: 1023,
max: 65535,
}
// Up command handle
func Up() HandleFunc {
return func(ctx *context.Context) (err error) {
cli := ctx.GetCliContext()
funcFile := cli.Args().First()
name := cli.String("name")
port := cli.Int("port")
func Up(ctx context.Contexter) (err error) {
const task = "deploying"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
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)
}
body, err := ioutil.ReadFile(funcFile)
if err != nil {
return errors.Wrap(err, "read source failed")
}
lang := utils.GetLangFromFileName(funcFile)
deployer := ctx.Get("deployer").(deploy.Deployer)
bindings := ctx.Get("bindings").([]types.PortBinding)
return deployer.Deploy(
ctx.Context,
types.Func{Language: lang, Source: string(body)},
name,
bindings,
)
}
fn := ctx.Get("fn").(types.Func)
image := ctx.Get("image").(string)
name := ctx.Get("name").(string)
deployer := ctx.Get("deployer").(deploy.Deployer)
bindings := ctx.Get("bindings").([]types.PortBinding)
return deployer.Deploy(
ctx.GetContext(),
fn,
name,
image,
bindings,
)
}

View File

@@ -1,12 +1,35 @@
package handlers
import (
"context"
"testing"
"github.com/golang/mock/gomock"
mockCtx "github.com/metrue/fx/context/mocks"
mockDeployer "github.com/metrue/fx/deploy/mocks"
"github.com/metrue/fx/types"
fxTypes "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)
fn := fxTypes.Func{}
bindings := []types.PortBinding{}
name := "sample-name"
image := "sample-image"
ctx.EXPECT().Get("fn").Return(fn)
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().GetContext().Return(context.Background())
deployer.EXPECT().Deploy(gomock.Any(), fn, name, image, bindings).Return(nil)
if err := Up(ctx); err != nil {
t.Fatal(err)
}
}

13
handlers/use_infra.go Normal file
View 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())
}

171
infra/docker/docker.go Normal file
View File

@@ -0,0 +1,171 @@
package docker
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
"github.com/metrue/fx/constants"
"github.com/metrue/fx/infra"
sshOperator "github.com/metrue/go-ssh-client"
)
// Docker docker host
type Docker struct {
IP string
User string
}
// New new a docker object
func New(ip string, user string) *Docker {
return &Docker{
IP: ip,
User: user,
}
}
// Provision provision a host, install docker and start dockerd
func (d *Docker) Provision() ([]byte, error) {
// TODO clean up, skip check localhost or not if in CICD env
if d.isLocalHost() {
if os.Getenv("CICD") == "" {
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
}
config, _ := json.Marshal(map[string]string{
"ip": d.IP,
"user": d.User,
})
return config, nil
}
func (d *Docker) isLocalHost() bool {
return strings.ToLower(d.IP) == "localhost" || d.IP == "127.0.0.1"
}
func (d *Docker) 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 *Docker) HealthCheck() (bool, error) {
// TODO
return true, nil
}
// Install docker on host
func (d *Docker) 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 *Docker) 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 *Docker) 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 *Docker) 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, " ")
fmt.Println(params)
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
}
var _ infra.Infra = &Docker{}

View File

@@ -0,0 +1,23 @@
package docker
import (
"os"
"testing"
)
func TestDocker(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 := New(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)
}
}

7
infra/infra.go Normal file
View File

@@ -0,0 +1,7 @@
package infra
// Infra infrastructure provision interface
type Infra interface {
Provision() (config []byte, err error)
HealthCheck() (bool, error)
}

153
infra/k3s/k3s.go Normal file
View File

@@ -0,0 +1,153 @@
package k3s
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
}
// K3S k3s operator
type K3S 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) *K3S {
return &K3S{
master: master,
agents: agents,
}
}
// Provision provision k3s cluster
func (k *K3S) 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 *K3S) HealthCheck() (bool, error) {
// TODO
return true, nil
}
// SetupMaster setup master node
func (k *K3S) SetupMaster() error {
sshKeyFile, _ := infra.GetSSHKeyFile()
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
installCmd := fmt.Sprintf("curl -sLS https://get.k3s.io | INSTALL_K3S_EXEC='server --tls-san %s' INSTALL_K3S_VERSION='%s' sh -", k.master.IP, version)
if err := ssh.RunCommand(infra.Sudo(installCmd, k.master.IP), 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 *K3S) 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.IP), 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 *K3S) SetupAgent() error {
sshKeyFile, _ := infra.GetSSHKeyFile()
tok, err := k.getToken()
if err != nil {
return err
}
const k3sExtraArgs = ""
joinCmd := fmt.Sprintf("curl -fL https://get.k3s.io/ | K3S_URL='https://%s:6443' K3S_TOKEN='%s' INSTALL_K3S_VERSION='%s' sh -s - %s", k.master.IP, tok, version, k3sExtraArgs)
for _, agent := range k.agents {
ssh := sshOperator.New(agent.IP).WithUser(agent.User).WithKey(sshKeyFile)
if err := ssh.RunCommand(joinCmd, sshOperator.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}); err != nil {
fmt.Println("setup agent failed \n================")
fmt.Println(err)
fmt.Println("================")
return err
}
}
return nil
}
// GetKubeConfig get kubeconfig of k3s cluster
func (k *K3S) 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.IP), 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 == "" {
context = "default"
}
kubeconfigReplacer := strings.NewReplacer(
"127.0.0.1", ip,
"localhost", ip,
"default", context,
)
return []byte(kubeconfigReplacer.Replace(kubeconfig))
}
var _ infra.Infra = &K3S{}

45
infra/k3s/k3s_test.go Normal file
View File

@@ -0,0 +1,45 @@
package k3s
import (
"fmt"
"os"
"testing"
)
func TestK3S(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)
}
}

63
infra/mocks/infra.go Normal file
View File

@@ -0,0 +1,63 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: infra.go
// Package mock_infra is a generated GoMock package.
package mock_infra
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// 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))
}

35
infra/ssh.go Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1,5 @@
{
"master": "52.78.196.250",
"agents": [
"13.125.243.192",
],

View File

@@ -1,17 +1,36 @@
package middlewares
import (
"fmt"
"os"
"github.com/metrue/fx/constants"
"github.com/metrue/fx/context"
"github.com/metrue/fx/types"
"github.com/phayes/freeport"
)
// PortRange usable port range https: //en.wikipedia.org/wiki/Ephemeral_port
var PortRange = struct {
min int
max int
}{
min: 1023,
max: 65535,
}
// Binding create bindings
func Binding(ctx *context.Context) error {
cli := ctx.GetCliContext()
port := cli.Int("port")
func Binding(ctx context.Contexter) (err error) {
port := ctx.Get("port").(int)
if port == 0 {
port, err = freeport.GetFreePort()
if err != nil {
return err
}
}
if port < PortRange.min || port > PortRange.max {
return fmt.Errorf("invalid port number: %d, port number should in range of %d - %d", port, PortRange.min, PortRange.max)
}
var bindings []types.PortBinding
if os.Getenv("KUBECONFIG") != "" {
@@ -24,6 +43,10 @@ func Binding(ctx *context.Context) error {
ServiceBindingPort: 443,
ContainerExposePort: constants.FxContainerExposePort,
},
types.PortBinding{
ServiceBindingPort: int32(port),
ContainerExposePort: constants.FxContainerExposePort,
},
}
} else {
bindings = []types.PortBinding{

View 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)
}
}

55
middlewares/build.go Normal file
View File

@@ -0,0 +1,55 @@
package middlewares
import (
"fmt"
"os"
"time"
containerruntimes "github.com/metrue/fx/container_runtimes"
"github.com/metrue/fx/context"
"github.com/metrue/fx/packer"
"github.com/metrue/fx/pkg/spinner"
"github.com/metrue/fx/types"
)
// Build image
func Build(ctx context.Contexter) (err error) {
const task = "building"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
cli := ctx.GetCliContext()
name := cli.String("name")
fn := ctx.Get("fn").(types.Func)
docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
defer os.RemoveAll(workdir)
if err := packer.PackIntoDir(fn, workdir); err != nil {
return err
}
if err := docker.BuildImage(ctx.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") != "" {
name := cli.String("name")
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
}

View 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
}

40
middlewares/parse.go Normal file
View File

@@ -0,0 +1,40 @@
package middlewares
import (
"io/ioutil"
"github.com/metrue/fx/context"
"github.com/metrue/fx/pkg/spinner"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
"github.com/pkg/errors"
)
// Parse parse input
func Parse(ctx context.Contexter) (err error) {
const task = "parsing"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
cli := ctx.GetCliContext()
funcFile := cli.Args().First()
lang := utils.GetLangFromFileName(funcFile)
body, err := ioutil.ReadFile(funcFile)
if err != nil {
return errors.Wrap(err, "read source failed")
}
fn := types.Func{
Language: lang,
Source: string(body),
}
ctx.Set("fn", fn)
name := cli.String("name")
ctx.Set("name", name)
port := cli.Int("port")
ctx.Set("port", port)
return nil
}

View File

@@ -1,49 +1,60 @@
package middlewares
import (
"fmt"
"os"
"github.com/metrue/fx/config"
"github.com/metrue/fx/constants"
containerruntimes "github.com/metrue/fx/container_runtimes"
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
dockerSDK "github.com/metrue/fx/container_runtimes/docker/sdk"
"github.com/metrue/fx/context"
"github.com/metrue/fx/deploy"
dockerDeployer "github.com/metrue/fx/deploy/docker"
k8sDeployer "github.com/metrue/fx/deploy/kubernetes"
k3sDeployer "github.com/metrue/fx/deploy/k3s"
k8sDeployer "github.com/metrue/fx/deploy/k8s"
"github.com/metrue/fx/pkg/spinner"
)
// Setup create k8s or docker cli
func Setup(ctx *context.Context) (err error) {
var deployer deploy.Deployer
if os.Getenv("KUBECONFIG") != "" {
deployer, err = k8sDeployer.Create()
if err != nil {
return err
}
} else {
deployer, err = dockerDeployer.CreateClient(ctx.Context)
if err != nil {
return err
}
}
ctx.Set("deployer", deployer)
func Setup(ctx context.Contexter) (err error) {
const task = "setup"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
user := os.Getenv("DOCKER_REMOTE_HOST_USER")
var docker containerruntimes.ContainerRuntime
if host != "" && user != "" {
docker, err = dockerHTTP.Create(host, constants.AgentPort)
fxConfig := ctx.Get("config").(*config.Config)
cloud := fxConfig.Clouds[fxConfig.CurrentCloud]
var deployer deploy.Deployer
if cloud["type"] == config.CloudTypeDocker {
docker, err := dockerHTTP.Create(cloud["host"], constants.AgentPort)
if err != nil {
return err
}
// TODO should clean up, but it needed in middlewares.Build
ctx.Set("docker", docker)
deployer, err = dockerDeployer.CreateClient(docker)
if err != nil {
return err
}
} else if cloud["type"] == config.CloudTypeK8S {
if os.Getenv("K3S") != "" {
deployer, err = k3sDeployer.Create(cloud["kubeconfig"])
if err != nil {
return err
}
} else if os.Getenv("KUBECONFIG") != "" {
deployer, err = k8sDeployer.Create(cloud["kubeconfig"])
if err != nil {
return err
}
}
} else {
docker, err = dockerSDK.CreateClient(ctx)
if err != nil {
return err
}
return fmt.Errorf("unsupport cloud type %s, please make sure you config is correct", cloud["type"])
}
ctx.Set("docker", docker)
ctx.Set("deployer", deployer)
return nil
}

View File

@@ -1,6 +1,8 @@
package command
import (
"bufio"
"bytes"
"os/exec"
"strings"
@@ -24,8 +26,13 @@ func NewRemoteRunner(sshClient ssh.Client) *RemoteRunner {
// Run script on remote host
func (r *RemoteRunner) Run(script string) ([]byte, error) {
stdout, stderr, err := r.sshClient.RunCommand(script)
output := string(stdout) + string(stderr)
var outPipe bytes.Buffer
var errPipe bytes.Buffer
err := r.sshClient.RunCommand(script, ssh.CommandOptions{
Stdout: bufio.NewWriter(&outPipe),
Stderr: bufio.NewWriter(&errPipe),
})
output := outPipe.String() + errPipe.String()
return []byte(output), err
}

27
pkg/render/render.go Normal file
View File

@@ -0,0 +1,27 @@
package render
import (
"fmt"
"os"
"github.com/metrue/fx/types"
"github.com/olekukonko/tablewriter"
)
// Table output services as table format
func Table(services []types.Service) {
data := [][]string{}
for _, s := range services {
col := []string{
s.ID,
s.Name,
fmt.Sprintf("%s:%d", s.Host, +s.Port),
}
data = append(data, col)
}
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "Name", "Endpoint"})
table.AppendBulk(data)
table.Render()
}

19
pkg/render/render_test.go Normal file
View File

@@ -0,0 +1,19 @@
package render
import (
"testing"
"github.com/metrue/fx/types"
)
func TestTable(t *testing.T) {
services := []types.Service{
types.Service{
ID: "id-1",
Name: "name-1",
Host: "127.0.0.1",
Port: 1000,
},
}
Table(services)
}

49
pkg/spinner/spiner.go Normal file
View File

@@ -0,0 +1,49 @@
package spinner
import (
"fmt"
"math/rand"
"time"
"github.com/briandowns/spinner"
aurora "github.com/logrusorgru/aurora"
)
var s *spinner.Spinner
func init() {
style := spinner.CharSets[36]
interval := 100 * time.Millisecond
s = spinner.New(style, interval)
}
// Start spinner
func Start(task string) {
colors := []string{
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"white",
}
rand.Seed(time.Now().UnixNano())
// nolint
s.Color(colors[rand.Intn(len(colors))])
s.Prefix = task + " "
if s.Active() {
s.Restart()
} else {
s.Start()
}
}
// Stop spinner
func Stop(task string, err error) {
if err != nil {
fmt.Println(aurora.Red("\u2717"))
}
s.Stop()
}

View File

@@ -0,0 +1,20 @@
package spinner
import (
"fmt"
"testing"
"time"
)
func TestSpinner(t *testing.T) {
t.Run("failure", func(t *testing.T) {
Start("task 2")
time.Sleep(1 * time.Second)
Stop("task 2", fmt.Errorf("error happened"))
})
t.Run("success", func(t *testing.T) {
Start("task 1")
time.Sleep(1 * time.Second)
Stop("task 1", nil)
})
}

View File

@@ -5,7 +5,8 @@ sudo apt-get remove -y docker docker-engine docker.io containerd runc
apt-get update -y
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common lsb-core curl
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu \$(lsb_release -cs) stable"
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7EA0A9C3F273FCD8
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update -y
sudo apt-get install -y docker-ce

View File

@@ -29,9 +29,14 @@ export_image() {
# main
# clean up
# docker stop fx-agent || true && docker rm fx-agent || true
if [[ "$DOCKER_REMOTE_HOST_ADDR" != "" ]];then
cloud_name='fx-remote-docker-host'
$fx infra create --name ${cloud_name} --type docker --host ${DOCKER_REMOTE_HOST_USER}@${DOCKER_REMOTE_HOST_ADDR}
$fx use ${cloud_name}
fi
port=20000
for lang in 'js' 'rb' 'py' 'go' 'php' 'java' 'd'; do
for lang in ${1}; do
run $lang $port
((port++))

9
scripts/test_k3s_infra.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -e
master_ip=$(multipass list | tail -3 | grep k3s-master | awk -F' ' '{print $3}')
agent_1_ip=$(multipass list | tail -3 | grep k3s-worker1 | awk -F' ' '{print $3}')
agent_2_ip=$(multipass list | tail -3 | grep k3s-worker2 | awk -F' ' '{print $3}')
user="multipass"
echo SSH_KEY_FILE=./test/id_rsa ./build/fx infra create -name k3s-test-cloud -t k3s --master ${user}@${master_ip} #--agents ${user}@${agent_1_ip},${user}@${agent_2_ip}
SSH_KEY_FILE=./test/id_rsa ./build/fx infra create -name k3s-test-cloud -t k3s --master ${user}@${master_ip} #--agents ${user}@${agent_1_ip},${user}@${agent_2_ip}

17
test/Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
FROM ubuntu:16.04
RUN apt-get update && apt-get install -y openssh-server curl
RUN mkdir -p ~/.ssh
RUN echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPVROluD9aW8YEsHiMefr0Yk70TzMJ+yRXkTN0DSDQje6fycffZaxI4vb5JO/tfXkTQCg+uo3t9YVQU3ceFAPpnznCnCr3jnOo7s2BqV5zDRjIW/fG3MLuVyZKvecA5RDIj2WLfvlsev+J6LI/Q/kMr9i8dI9BHp5B3u8Nv3sePEzKU9YRnTd/UTbSdAHKqfpGhgwZEI00q3iiP6f5DKVXZ4b7ZVEsV3cPVrRskurYClSMd32/yaJ+68mFlpwTKI/aq7tZBd5lLsAsd2IxshGE23g4bU04GeeJ76tFT7BvDyL8woshECisRHSdEsdlY9MXIcC/a4hIV4baHXJDkFrf minghe@oldmac.local' >> ~/.ssh/authorized_keys
RUN mkdir /var/run/sshd
RUN echo 'root:THEPASSWORDYOUCREATED' | chpasswd
RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
# SSH login fix. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
ENV NOTVISIBLE "in users profile"
RUN echo "export VISIBLE=now" >> /etc/profile
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]

27
test/id_rsa Normal file
View File

@@ -0,0 +1,27 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEAz1UTpbg/WlvGBLB4jHn69GJO9E8zCfskV5EzdA0g0I3un8nH32Ws
SOL2+STv7X15E0AoPrqN7fWFUFN3HhQD6Z85wpwq945zqO7Ngalecw0YyFv3xtzC7lcmSr
3nAOUQyI9li375bHr/ieiyP0P5DK/YvHSPQR6eQd7vDb97HjxMylPWEZ03f1E20nQByqn6
RoYMGRCNNKt4oj+n+QylV2eG+2VRLFd3D1a0bJLq2ApUjHd9v8mifuvJhZacEyiP2qu7WQ
XeZS7ALHdiMbIRhNt4OG1NOBnnie+rRU+wbw8i/MKLIRAorER0nRLHZWPTFyHAv2uISFeG
2h1yQ5Ba3wAAA9AgPfD9ID3w/QAAAAdzc2gtcnNhAAABAQDPVROluD9aW8YEsHiMefr0Yk
70TzMJ+yRXkTN0DSDQje6fycffZaxI4vb5JO/tfXkTQCg+uo3t9YVQU3ceFAPpnznCnCr3
jnOo7s2BqV5zDRjIW/fG3MLuVyZKvecA5RDIj2WLfvlsev+J6LI/Q/kMr9i8dI9BHp5B3u
8Nv3sePEzKU9YRnTd/UTbSdAHKqfpGhgwZEI00q3iiP6f5DKVXZ4b7ZVEsV3cPVrRskurY
ClSMd32/yaJ+68mFlpwTKI/aq7tZBd5lLsAsd2IxshGE23g4bU04GeeJ76tFT7BvDyL8wo
shECisRHSdEsdlY9MXIcC/a4hIV4baHXJDkFrfAAAAAwEAAQAAAQBngaOzYg5Ov+5VvPwR
tXvxsXqVQUzLuNNl3BmB4GP3ekQdBZGBF7MxGA4QR754I+HkGG1/E4dzutT5SxH2tCtX4K
PnYiuZN0bKmZ2DE9kROwKaVD+YyfGPJ3b4bWH78l+0oNIjGBrRa35TjRYfu4GEMe3T96Tk
77I2VGOny2ZdqvpM98tzbmtOkaZm+ixRfYJ2Yx01clYENh2PMKexsJ2YNwwQKyEv7GCZjm
xa2WOuzBs7r/WUdPTHRbGCxfvpWEreeTJqMXcSFNW6uf//oXVbQp4BFeePEdSZSVC8aBDE
lILoaBsVhWaQFIWU50EQaqbtWGfq/DhxzU610M2iV5LhAAAAgQDzkzz3Jw2goPZVKYjWyg
NfzLx+CV2EP0J6IK+Ot3c7RFadWNY0mnMY6bfar6j3aU2A9g/n7ili2sx0cvruYp3DXnRF
srVd3tMDb6/PBAlJp0UHrZo37sSeJeRpzIVAVjssgrryXRBxs2XQUf0Zy0DTOlf9Kp6S1Z
GTzOB/cI9/YQAAAIEA9+BLSqcOQl4TZ+btaLy16duMaPlNlsc6kp+2EWqYxP5JAPB3SwO9
SCaJ2Pab4xt3V51WX4n3HbIJBAobTYweSVJKBY6sKqVEXRiuXjLTC6ywutS3U8iNJh1bj+
ro+V/aKHHyiM7b1AxbQiYyo94uhQi/gzHz2lqQQUjigjmbRhkAAACBANYgnazVKNvFXADF
vrBfvFrAN8g1IDft7tOowhbb4YCaDptEyj14GhvVe0PEKhaeA3jP7ovr2IyvglpAqVcjvx
S7ZYF/JJB3ChvY1ofTGwNNvLfiJ4yDDZhqU2YIIPl320yJAUaVnbGdRf9kEx+CS/Q3S44q
LJ2/aaf5bJyLDRe3AAAAE21pbmdoZUBvbGRtYWMubG9jYWwBAgMEBQYH
-----END OPENSSH PRIVATE KEY-----

1
test/id_rsa.pub Normal file
View File

@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPVROluD9aW8YEsHiMefr0Yk70TzMJ+yRXkTN0DSDQje6fycffZaxI4vb5JO/tfXkTQCg+uo3t9YVQU3ceFAPpnznCnCr3jnOo7s2BqV5zDRjIW/fG3MLuVyZKvecA5RDIj2WLfvlsev+J6LI/Q/kMr9i8dI9BHp5B3u8Nv3sePEzKU9YRnTd/UTbSdAHKqfpGhgwZEI00q3iiP6f5DKVXZ4b7ZVEsV3cPVrRskurYClSMd32/yaJ+68mFlpwTKI/aq7tZBd5lLsAsd2IxshGE23g4bU04GeeJ76tFT7BvDyL8woshECisRHSdEsdlY9MXIcC/a4hIV4baHXJDkFrf minghe@oldmac.local

View File

@@ -0,0 +1,6 @@
#cloud-config
# add each entry to ~/.ssh/authorized_keys for the configured user or the
# first user defined in the user definition directive.
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPVROluD9aW8YEsHiMefr0Yk70TzMJ+yRXkTN0DSDQje6fycffZaxI4vb5JO/tfXkTQCg+uo3t9YVQU3ceFAPpnznCnCr3jnOo7s2BqV5zDRjIW/fG3MLuVyZKvecA5RDIj2WLfvlsev+J6LI/Q/kMr9i8dI9BHp5B3u8Nv3sePEzKU9YRnTd/UTbSdAHKqfpGhgwZEI00q3iiP6f5DKVXZ4b7ZVEsV3cPVrRskurYClSMd32/yaJ+68mFlpwTKI/aq7tZBd5lLsAsd2IxshGE23g4bU04GeeJ76tFT7BvDyL8woshECisRHSdEsdlY9MXIcC/a4hIV4baHXJDkFrf minghe@oldmac.local