Compare commits
11 Commits
0.8.5-alph
...
0.8.6-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0522690472 | ||
|
|
a8a0fbed32 | ||
|
|
26ae9585f6 | ||
|
|
b69bd699c8 | ||
|
|
650ee5f63a | ||
|
|
e3c60cbb77 | ||
|
|
0daca43d10 | ||
|
|
d3c239dc54 | ||
|
|
05ac2441da | ||
|
|
c0009b1b64 | ||
|
|
82960824ef |
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -26,13 +26,9 @@ jobs:
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
run: |
|
||||
export KUBECONFIG="$(kind get kubeconfig-path)"
|
||||
DEBUG=true go test -v ./...
|
||||
- name: code cov
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: |
|
||||
export KUBECONFIG="$(kind get kubeconfig-path)"
|
||||
./scripts/coverage.sh
|
||||
bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}
|
||||
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
run: |
|
||||
export KUBECONFIG="$(kind get kubeconfig-path)"
|
||||
DEBUG=true go test -v ./container_runtimes/... ./deploy/...
|
||||
DEBUG=true go test -v ./...
|
||||
|
||||
- name: build fx
|
||||
run: |
|
||||
|
||||
@@ -129,6 +129,41 @@ func (api *API) post(path string, body []byte, expectStatus int, v interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Version get version of docker engine
|
||||
func (api *API) Version(ctx context.Context) (string, error) {
|
||||
path := api.endpoint + "/version"
|
||||
if !strings.HasPrefix(path, "http") {
|
||||
path = "http://" + path
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
client := &http.Client{Timeout: 20 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("request %s failed: %d - %s", path, resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var res dockerTypes.Version
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.APIVersion, nil
|
||||
}
|
||||
|
||||
// ListContainer list service
|
||||
func (api *API) ListContainer(ctx context.Context, name string) ([]types.Service, error) {
|
||||
if name != "" {
|
||||
@@ -416,6 +451,10 @@ func (api *API) StopContainer(ctx context.Context, name string) error {
|
||||
|
||||
// InspectContainer inspect container
|
||||
func (api *API) InspectContainer(ctx context.Context, name string, container interface{}) error {
|
||||
path := fmt.Sprintf("/containers/%s/json", name)
|
||||
if err := api.get(path, "", &container); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,111 +1,31 @@
|
||||
package api
|
||||
|
||||
// func TestDockerHTTP(t *testing.T) {
|
||||
// const addr = "127.0.0.1"
|
||||
// const user = ""
|
||||
// const passord = ""
|
||||
// provisioner := provision.NewWithHost(addr, user, passord)
|
||||
// if err := utils.RunWithRetry(func() error {
|
||||
// if !provisioner.IsFxAgentRunning() {
|
||||
// if err := provisioner.StartFxAgent(); err != nil {
|
||||
// log.Infof("could not start fx agent on host: %s", err)
|
||||
// return err
|
||||
// }
|
||||
// log.Infof("fx agent started")
|
||||
// } else {
|
||||
// log.Infof("fx agent is running")
|
||||
// }
|
||||
// return nil
|
||||
// }, 2*time.Second, 10); err != nil {
|
||||
// t.Fatal(err)
|
||||
// } else {
|
||||
// defer provisioner.StopFxAgent()
|
||||
// }
|
||||
//
|
||||
// host := config.Host{Host: "127.0.0.1"}
|
||||
// api, err := Create(host.Host, constants.AgentPort)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// serviceName := "a-test-service"
|
||||
// project := types.Project{
|
||||
// Name: serviceName,
|
||||
// Language: "node",
|
||||
// Files: []types.ProjectSourceFile{
|
||||
// types.ProjectSourceFile{
|
||||
// Path: "Dockerfile",
|
||||
// Body: `
|
||||
// FROM metrue/fx-node-base
|
||||
//
|
||||
// COPY . .
|
||||
// EXPOSE 3000
|
||||
// CMD ["node", "app.js"]`,
|
||||
// IsHandler: false,
|
||||
// },
|
||||
// types.ProjectSourceFile{
|
||||
// Path: "app.js",
|
||||
// Body: `
|
||||
// const Koa = require('koa');
|
||||
// const bodyParser = require('koa-bodyparser');
|
||||
// const func = require('./fx');
|
||||
//
|
||||
// const app = new Koa();
|
||||
// app.use(bodyParser());
|
||||
// app.use(ctx => {
|
||||
// const msg = func(ctx.request.body);
|
||||
// ctx.body = msg;
|
||||
// });
|
||||
//
|
||||
// app.listen(3000);`,
|
||||
// IsHandler: false,
|
||||
// },
|
||||
// types.ProjectSourceFile{
|
||||
// Path: "fx.js",
|
||||
// Body: `
|
||||
// module.exports = (input) => {
|
||||
// return input.a + input.b
|
||||
// }
|
||||
// `,
|
||||
// IsHandler: true,
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// service, err := api.Build(project)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if service.Name != serviceName {
|
||||
// t.Fatalf("should get %s but got %s", serviceName, service.Name)
|
||||
// }
|
||||
//
|
||||
// if err := api.Run(9999, &service); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// services, err := api.ListContainer(serviceName)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if len(services) != 1 {
|
||||
// t.Fatal("service number should be 1")
|
||||
// }
|
||||
//
|
||||
// if err := api.Stop(serviceName); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// const network = "fx-net"
|
||||
// if err := api.CreateNetwork(network); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// nws, err := api.GetNetwork(network)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
// if nws[0].Name != network {
|
||||
// t.Fatalf("should get %s but got %s", network, nws[0].Name)
|
||||
// }
|
||||
// }
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func TestDockerHTTP(t *testing.T) {
|
||||
host := os.Getenv("DOCKER_ENGINE_HOST")
|
||||
port := os.Getenv("DOCKER_ENGINE_PORT")
|
||||
if host == "" ||
|
||||
port == "" {
|
||||
t.Skip("DOCKER_ENGINE_HOST and DOCKER_ENGINE_PORT required")
|
||||
}
|
||||
|
||||
api, err := Create(host, port)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
name := "fx-agent"
|
||||
var container types.ContainerJSON
|
||||
if err := api.InspectContainer(context.Background(), name, &container); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.Name != "/"+name {
|
||||
t.Fatalf("should get %s but got %s", name, container.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,16 @@ func (d *Docker) StopContainer(ctx context.Context, name string) error {
|
||||
|
||||
// InspectContainer inspect a container
|
||||
func (d *Docker) InspectContainer(ctx context.Context, name string, container interface{}) error {
|
||||
return nil
|
||||
res, err := d.ContainerInspect(ctx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(body, &container)
|
||||
}
|
||||
|
||||
// ListContainer list containers
|
||||
@@ -226,6 +235,15 @@ func (d *Docker) ListContainer(ctx context.Context, name string) ([]types.Servic
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// Version get version of docker engine
|
||||
func (d *Docker) Version(ctx context.Context) (string, error) {
|
||||
ping, err := d.Ping(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ping.APIVersion, nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ containerruntimes.ContainerRuntime = &Docker{}
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestDocker(t *testing.T) {
|
||||
@@ -42,6 +43,23 @@ func TestDocker(t *testing.T) {
|
||||
t.Fatalf("should have built image with tag %s", name)
|
||||
}
|
||||
|
||||
if err := cli.StartContainer(ctx, name, name, []types.PortBinding{
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 9000,
|
||||
ContainerExposePort: 3000,
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var container dockerTypes.ContainerJSON
|
||||
if err := cli.InspectContainer(ctx, name, &container); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.Name != "/"+name {
|
||||
t.Fatalf("should get %s but got %s", "/"+name, container.Name)
|
||||
}
|
||||
|
||||
username := os.Getenv("DOCKER_USERNAME")
|
||||
password := os.Getenv("DOCKER_PASSWORD")
|
||||
if username == "" || password == "" {
|
||||
|
||||
@@ -16,4 +16,5 @@ type ContainerRuntime interface {
|
||||
StopContainer(ctx context.Context, name string) error
|
||||
InspectContainer(ctx context.Context, name string, container interface{}) error
|
||||
ListContainer(ctx context.Context, filter string) ([]types.Service, error)
|
||||
Version(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,15 @@ const (
|
||||
keyCliCtx = key("cmd_cli")
|
||||
)
|
||||
|
||||
// Contexter ctx interface
|
||||
type Contexter interface {
|
||||
Get(k string) interface{}
|
||||
Set(k string, v interface{})
|
||||
Use(fn func(ctx *Context) error) error
|
||||
GetContext() context.Context
|
||||
GetCliContext() *cli.Context
|
||||
}
|
||||
|
||||
// Context fx context
|
||||
type Context struct {
|
||||
context.Context
|
||||
@@ -56,3 +65,12 @@ func (ctx *Context) Get(name string) interface{} {
|
||||
func (ctx *Context) Use(fn func(ctx *Context) error) error {
|
||||
return fn(ctx)
|
||||
}
|
||||
|
||||
// GetContext get context
|
||||
func (ctx *Context) GetContext() context.Context {
|
||||
return ctx.Context
|
||||
}
|
||||
|
||||
var (
|
||||
_ Contexter = &Context{}
|
||||
)
|
||||
|
||||
@@ -22,4 +22,8 @@ func TestContext(t *testing.T) {
|
||||
if v != value {
|
||||
t.Fatalf("should get %v but %v", value, v)
|
||||
}
|
||||
|
||||
if ctx.GetContext() == nil {
|
||||
t.Fatalf("should get context")
|
||||
}
|
||||
}
|
||||
|
||||
104
context/mocks/context.go
Normal file
104
context/mocks/context.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: context.go
|
||||
|
||||
// Package mock_context is a generated GoMock package.
|
||||
package mock_context
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
context0 "github.com/metrue/fx/context"
|
||||
cli "github.com/urfave/cli"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockContexter is a mock of Contexter interface
|
||||
type MockContexter struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockContexterMockRecorder
|
||||
}
|
||||
|
||||
// MockContexterMockRecorder is the mock recorder for MockContexter
|
||||
type MockContexterMockRecorder struct {
|
||||
mock *MockContexter
|
||||
}
|
||||
|
||||
// NewMockContexter creates a new mock instance
|
||||
func NewMockContexter(ctrl *gomock.Controller) *MockContexter {
|
||||
mock := &MockContexter{ctrl: ctrl}
|
||||
mock.recorder = &MockContexterMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockContexter) EXPECT() *MockContexterMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Get mocks base method
|
||||
func (m *MockContexter) Get(k string) interface{} {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", k)
|
||||
ret0, _ := ret[0].(interface{})
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get
|
||||
func (mr *MockContexterMockRecorder) Get(k interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockContexter)(nil).Get), k)
|
||||
}
|
||||
|
||||
// Set mocks base method
|
||||
func (m *MockContexter) Set(k string, v interface{}) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Set", k, v)
|
||||
}
|
||||
|
||||
// Set indicates an expected call of Set
|
||||
func (mr *MockContexterMockRecorder) Set(k, v interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockContexter)(nil).Set), k, v)
|
||||
}
|
||||
|
||||
// Use mocks base method
|
||||
func (m *MockContexter) Use(fn func(*context0.Context) error) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Use", fn)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Use indicates an expected call of Use
|
||||
func (mr *MockContexterMockRecorder) Use(fn interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Use", reflect.TypeOf((*MockContexter)(nil).Use), fn)
|
||||
}
|
||||
|
||||
// GetContext mocks base method
|
||||
func (m *MockContexter) GetContext() context.Context {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetContext")
|
||||
ret0, _ := ret[0].(context.Context)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetContext indicates an expected call of GetContext
|
||||
func (mr *MockContexterMockRecorder) GetContext() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContext", reflect.TypeOf((*MockContexter)(nil).GetContext))
|
||||
}
|
||||
|
||||
// GetCliContext mocks base method
|
||||
func (m *MockContexter) GetCliContext() *cli.Context {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCliContext")
|
||||
ret0, _ := ret[0].(*cli.Context)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetCliContext indicates an expected call of GetCliContext
|
||||
func (mr *MockContexterMockRecorder) GetCliContext() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCliContext", reflect.TypeOf((*MockContexter)(nil).GetCliContext))
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
types "github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// Deployer make a image a service
|
||||
type Deployer interface {
|
||||
Deploy(ctx context.Context, fn types.Func, name string, image string, bindings []types.PortBinding) error
|
||||
Destroy(ctx context.Context, name string) error
|
||||
Update(ctx context.Context, name string) error
|
||||
GetStatus(ctx context.Context, name string) error
|
||||
List(ctx context.Context, name string) ([]types.Service, error)
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// Docker manage container
|
||||
type Docker struct {
|
||||
cli containerruntimes.ContainerRuntime
|
||||
}
|
||||
|
||||
// CreateClient create a docker instance
|
||||
func CreateClient(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, image string, ports []types.PortBinding) error {
|
||||
return d.cli.StartContainer(ctx, name, image, ports)
|
||||
}
|
||||
|
||||
// Update a container
|
||||
func (d *Docker) Update(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy stop and remove container
|
||||
func (d *Docker) Destroy(ctx context.Context, name string) error {
|
||||
return d.cli.StopContainer(ctx, name)
|
||||
}
|
||||
|
||||
// GetStatus get status of container
|
||||
func (d *Docker) GetStatus(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// List services
|
||||
func (d *Docker) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||
// FIXME support remote host
|
||||
return d.cli.ListContainer(ctx, name)
|
||||
}
|
||||
|
||||
var (
|
||||
_ deploy.Deployer = &Docker{}
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
package k3s
|
||||
|
||||
// ConfigMap is the key to function docker project source code in configmap
|
||||
var ConfigMap = struct {
|
||||
AppMetaEnvName string
|
||||
}{
|
||||
AppMetaEnvName: "APP_META",
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package k3s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func generateDeploymentSpec(
|
||||
name string,
|
||||
image string,
|
||||
bindPorts []types.PortBinding,
|
||||
replicas int32,
|
||||
selector map[string]string,
|
||||
) *appsv1.Deployment {
|
||||
ports := []apiv1.ContainerPort{}
|
||||
for index, binding := range bindPorts {
|
||||
ports = append(ports, apiv1.ContainerPort{
|
||||
Name: fmt.Sprintf("fx-container-%d", index),
|
||||
ContainerPort: binding.ContainerExposePort,
|
||||
})
|
||||
}
|
||||
|
||||
container := apiv1.Container{
|
||||
Name: "fx-placeholder-container-name",
|
||||
Image: image,
|
||||
Ports: ports,
|
||||
ImagePullPolicy: v1.PullIfNotPresent,
|
||||
}
|
||||
return &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: appsv1.DeploymentSpec{
|
||||
Replicas: &replicas,
|
||||
Selector: &metav1.LabelSelector{
|
||||
MatchLabels: selector,
|
||||
},
|
||||
Template: apiv1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: selector,
|
||||
},
|
||||
Spec: apiv1.PodSpec{
|
||||
Containers: []apiv1.Container{container},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetDeployment get a deployment
|
||||
func (k *K3S) GetDeployment(namespace string, name string) (*appsv1.Deployment, error) {
|
||||
return k.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
// CreateDeployment create a deployment
|
||||
func (k *K3S) CreateDeployment(
|
||||
namespace string,
|
||||
name string,
|
||||
image string,
|
||||
ports []types.PortBinding,
|
||||
replicas int32,
|
||||
selector map[string]string,
|
||||
) (*appsv1.Deployment, error) {
|
||||
deployment := generateDeploymentSpec(name, image, ports, replicas, selector)
|
||||
return k.AppsV1().Deployments(namespace).Create(deployment)
|
||||
}
|
||||
|
||||
// UpdateDeployment update a deployment
|
||||
func (k *K3S) UpdateDeployment(
|
||||
namespace string,
|
||||
name string,
|
||||
image string,
|
||||
ports []types.PortBinding,
|
||||
replicas int32,
|
||||
selector map[string]string,
|
||||
) (*appsv1.Deployment, error) {
|
||||
deployment := generateDeploymentSpec(name, image, ports, replicas, selector)
|
||||
return k.AppsV1().Deployments(namespace).Update(deployment)
|
||||
}
|
||||
|
||||
// DeleteDeployment delete a deployment
|
||||
func (k *K3S) DeleteDeployment(namespace string, name string) error {
|
||||
return k.AppsV1().Deployments(namespace).Delete(name, &metav1.DeleteOptions{})
|
||||
}
|
||||
|
||||
// CreateDeploymentWithInitContainer create a deployment which will wait InitContainer to do the image build before function container start
|
||||
func (k *K3S) CreateDeploymentWithInitContainer(
|
||||
namespace string,
|
||||
name string,
|
||||
ports []types.PortBinding,
|
||||
replicas int32,
|
||||
selector map[string]string,
|
||||
) (*appsv1.Deployment, error) {
|
||||
deployment := generateDeploymentSpec(name, name, ports, replicas, selector)
|
||||
updatedDeployment := injectInitContainer(name, deployment)
|
||||
return k.AppsV1().Deployments(namespace).Create(updatedDeployment)
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package k3s
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestDeployment(t *testing.T) {
|
||||
namespace := "default"
|
||||
name := "fx-hello-world"
|
||||
image := "metrue/kube-hello"
|
||||
selector := map[string]string{
|
||||
"app": "fx-app",
|
||||
}
|
||||
|
||||
kubeconfig := os.Getenv("KUBECONFIG")
|
||||
if kubeconfig == "" {
|
||||
t.Skip("skip test since no KUBECONFIG given in environment variable")
|
||||
}
|
||||
|
||||
k8s, err := Create("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := k8s.GetDeployment(namespace, name); err == nil {
|
||||
t.Fatalf("should get not found error")
|
||||
}
|
||||
|
||||
replicas := int32(2)
|
||||
bindings := []types.PortBinding{
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 80,
|
||||
ContainerExposePort: 3000,
|
||||
},
|
||||
types.PortBinding{
|
||||
ServiceBindingPort: 443,
|
||||
ContainerExposePort: 3000,
|
||||
},
|
||||
}
|
||||
deployment, err := k8s.CreateDeployment(namespace, name, image, bindings, replicas, selector)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if deployment == nil {
|
||||
t.Fatalf("deploymetn should not be %v", nil)
|
||||
}
|
||||
|
||||
if deployment.Name != name {
|
||||
t.Fatalf("should get %s but got %s", name, deployment.Name)
|
||||
}
|
||||
|
||||
if *deployment.Spec.Replicas != replicas {
|
||||
t.Fatalf("should get %v but got %v", replicas, deployment.Spec.Replicas)
|
||||
}
|
||||
|
||||
if err := k8s.DeleteDeployment(namespace, name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package k3s
|
||||
|
||||
import (
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// This is docker image provided by fx/contrib/docker_packer
|
||||
// it can build a Docker image with give Docker project source codes encoded with base64
|
||||
// check the detail fx/contrib/docker_packer/main.go
|
||||
const image = "metrue/fx-docker"
|
||||
|
||||
func injectInitContainer(name string, deployment *appsv1.Deployment) *appsv1.Deployment {
|
||||
configMapHasToBeReady := true
|
||||
valueInConfigMapHasToBeReady := true
|
||||
initContainer := v1.Container{
|
||||
Name: "fx-docker-build-c",
|
||||
Image: image,
|
||||
ImagePullPolicy: v1.PullAlways,
|
||||
Command: []string{
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"/usr/bin/docker_packer $(APP_META) " + name,
|
||||
}, // Maybe it can be passed by Binary data from config map
|
||||
// Args: []string{"${APP_META}"}, // function source codes and name
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
v1.VolumeMount{
|
||||
Name: "dockersock",
|
||||
MountPath: "/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
Env: []v1.EnvVar{
|
||||
v1.EnvVar{
|
||||
Name: ConfigMap.AppMetaEnvName,
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{Name: name},
|
||||
Key: ConfigMap.AppMetaEnvName,
|
||||
Optional: &valueInConfigMapHasToBeReady,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
EnvFrom: []v1.EnvFromSource{
|
||||
v1.EnvFromSource{
|
||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: name,
|
||||
},
|
||||
Optional: &configMapHasToBeReady,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
volumes := []v1.Volume{
|
||||
v1.Volume{
|
||||
Name: "dockersock",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{
|
||||
Path: "/var/run/docker.sock",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
deployment.Spec.Template.Spec.InitContainers = []apiv1.Container{initContainer}
|
||||
deployment.Spec.Template.Spec.Volumes = volumes
|
||||
return deployment
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package k3s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
)
|
||||
|
||||
// K3S client
|
||||
type K3S struct {
|
||||
*kubernetes.Clientset
|
||||
}
|
||||
|
||||
const namespace = "default"
|
||||
|
||||
// Create a k8s cluster client
|
||||
func Create(kubeconfig string) (*K3S, error) {
|
||||
if os.Getenv("KUBECONFIG") != "" {
|
||||
kubeconfig = os.Getenv("KUBECONFIG")
|
||||
}
|
||||
|
||||
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &K3S{clientset}, nil
|
||||
}
|
||||
|
||||
// Deploy a image to be a service
|
||||
func (k *K3S) Deploy(
|
||||
ctx context.Context,
|
||||
fn types.Func,
|
||||
name string,
|
||||
image string,
|
||||
ports []types.PortBinding,
|
||||
) error {
|
||||
selector := map[string]string{
|
||||
"app": "fx-app-" + name,
|
||||
}
|
||||
|
||||
const replicas = int32(3)
|
||||
if _, err := k.GetDeployment(namespace, name); err != nil {
|
||||
// TODO enable passing replica from fx CLI
|
||||
if _, err := k.CreateDeployment(
|
||||
namespace,
|
||||
name,
|
||||
image,
|
||||
ports,
|
||||
replicas,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err := k.UpdateDeployment(
|
||||
namespace,
|
||||
name,
|
||||
image,
|
||||
ports,
|
||||
replicas,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO fx should be able to know what's the target Kubernetes service platform
|
||||
// it's going to deploy to
|
||||
typ := "LoadBalancer"
|
||||
if os.Getenv("SERVICE_TYPE") != "" {
|
||||
typ = os.Getenv("SERVICE_TYPE")
|
||||
}
|
||||
|
||||
if _, err := k.GetService(namespace, name); err != nil {
|
||||
if _, err := k.CreateService(
|
||||
namespace,
|
||||
name,
|
||||
typ,
|
||||
ports,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err := k.UpdateService(
|
||||
namespace,
|
||||
name,
|
||||
typ,
|
||||
ports,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update a service
|
||||
func (k *K3S) Update(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy a service
|
||||
func (k *K3S) Destroy(ctx context.Context, name string) error {
|
||||
if err := k.DeleteService(namespace, name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := k.DeleteDeployment(namespace, name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStatus get status of a service
|
||||
func (k *K3S) GetStatus(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// List services
|
||||
func (k *K3S) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||
return []types.Service{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ deploy.Deployer = &K3S{}
|
||||
)
|
||||
@@ -1,87 +0,0 @@
|
||||
package k3s
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
intstr "k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
func generateServiceSpec(
|
||||
namespace string,
|
||||
name string,
|
||||
typ string,
|
||||
bindings []types.PortBinding,
|
||||
selector map[string]string,
|
||||
) *apiv1.Service {
|
||||
servicePorts := []apiv1.ServicePort{}
|
||||
for index, binding := range bindings {
|
||||
servicePorts = append(servicePorts, apiv1.ServicePort{
|
||||
Name: "port-" + strconv.Itoa(index),
|
||||
Protocol: apiv1.ProtocolTCP,
|
||||
Port: binding.ServiceBindingPort,
|
||||
TargetPort: intstr.FromInt(int(binding.ContainerExposePort)),
|
||||
})
|
||||
}
|
||||
|
||||
return &apiv1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
ClusterName: namespace,
|
||||
},
|
||||
Spec: apiv1.ServiceSpec{
|
||||
Ports: servicePorts,
|
||||
Type: apiv1.ServiceType(typ),
|
||||
Selector: selector,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CreateService create a service
|
||||
func (k *K3S) CreateService(
|
||||
namespace string,
|
||||
name string,
|
||||
typ string,
|
||||
bindings []types.PortBinding,
|
||||
selector map[string]string,
|
||||
) (*apiv1.Service, error) {
|
||||
service := generateServiceSpec(namespace, name, typ, bindings, selector)
|
||||
createdService, err := k.CoreV1().Services(namespace).Create(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return createdService, nil
|
||||
}
|
||||
|
||||
// UpdateService update a service
|
||||
// TODO this method is not perfect yet, should refactor later
|
||||
func (k *K3S) UpdateService(
|
||||
namespace string,
|
||||
name string,
|
||||
typ string,
|
||||
bindings []types.PortBinding,
|
||||
selector map[string]string,
|
||||
) (*apiv1.Service, error) {
|
||||
svc, err := k.GetService(namespace, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
svc.Spec.Selector = selector
|
||||
svc.Spec.Type = apiv1.ServiceType(typ)
|
||||
return k.CoreV1().Services(namespace).Update(svc)
|
||||
}
|
||||
|
||||
// DeleteService a service
|
||||
func (k *K3S) DeleteService(namespace string, name string) error {
|
||||
// TODO figure out the elegant way to delete a service
|
||||
options := &metav1.DeleteOptions{}
|
||||
return k.CoreV1().Services(namespace).Delete(name, options)
|
||||
}
|
||||
|
||||
// GetService get a service
|
||||
func (k *K3S) GetService(namespace string, name string) (*apiv1.Service, error) {
|
||||
return k.CoreV1().Services(namespace).Get(name, metav1.GetOptions{})
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
FROM metrue/fx-node-base
|
||||
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["node", "app.js"]
|
||||
@@ -1,9 +0,0 @@
|
||||
const Koa = require('koa');
|
||||
const bodyParser = require('koa-bodyparser');
|
||||
const fx = require('./fx');
|
||||
|
||||
const app = new Koa();
|
||||
app.use(bodyParser());
|
||||
app.use(fx);
|
||||
|
||||
app.listen(3000);
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = (ctx) => {
|
||||
ctx.body = 'hello world'
|
||||
}
|
||||
8
fx.go
8
fx.go
@@ -16,13 +16,13 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const version = "0.8.5"
|
||||
const version = "0.8.6"
|
||||
|
||||
func init() {
|
||||
go checkForUpdate()
|
||||
}
|
||||
|
||||
func handle(fns ...func(ctx *context.Context) error) func(ctx *cli.Context) error {
|
||||
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 {
|
||||
@@ -157,8 +157,8 @@ func main() {
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Setup,
|
||||
middlewares.Parse("up"),
|
||||
middlewares.Binding,
|
||||
middlewares.Parse,
|
||||
middlewares.Build,
|
||||
handlers.Up,
|
||||
),
|
||||
@@ -168,6 +168,7 @@ func main() {
|
||||
Usage: "destroy a service",
|
||||
ArgsUsage: "[service 1, service 2, ....]",
|
||||
Action: handle(
|
||||
middlewares.Parse("down"),
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Setup,
|
||||
handlers.Down,
|
||||
@@ -178,6 +179,7 @@ func main() {
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list deployed services",
|
||||
Action: handle(
|
||||
middlewares.Parse("list"),
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Setup,
|
||||
handlers.List,
|
||||
|
||||
4
go.mod
4
go.mod
@@ -27,13 +27,15 @@ require (
|
||||
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/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
|
||||
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/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
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
||||
|
||||
9
go.sum
9
go.sum
@@ -161,6 +161,8 @@ github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
|
||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b h1:JGD0sJ44XzhsT1voOg00zji4ubuMNcVNK3m7d9GI88k=
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b/go.mod h1:ERHOEBrDy6+8vfoJjjmhdmBpOzdvvP7bLtwYTTK6LOs=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
@@ -183,6 +185,8 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
|
||||
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/olekukonko/tablewriter v0.0.3 h1:i0LBnzgiChAWHJYTQAZJDOgf8MNxAVYZJ2m63SIDimI=
|
||||
github.com/olekukonko/tablewriter v0.0.3/go.mod h1:YZeBtGzYYEsCHp2LST/u/0NDwGkRoBtmn1cIWCJiS6M=
|
||||
github.com/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=
|
||||
@@ -194,6 +198,8 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo
|
||||
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/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.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
@@ -254,6 +260,8 @@ 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/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
@@ -326,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=
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// Call command handle
|
||||
func Call(ctx *context.Context) error {
|
||||
func Call(ctx context.Contexter) error {
|
||||
cli := ctx.GetCliContext()
|
||||
_ = strings.Join(cli.Args()[1:], " ")
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// Doctor command handle
|
||||
func Doctor(ctx *context.Context) error {
|
||||
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")
|
||||
|
||||
@@ -2,23 +2,15 @@ package handlers
|
||||
|
||||
import (
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/infra"
|
||||
)
|
||||
|
||||
// Down command handle
|
||||
func Down(ctx *context.Context) (err error) {
|
||||
const task = "destroying"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
cli := ctx.GetCliContext()
|
||||
services := cli.Args()
|
||||
runner := ctx.Get("deployer").(deploy.Deployer)
|
||||
func Down(ctx context.Contexter) (err error) {
|
||||
services := ctx.Get("services").([]string)
|
||||
runner := ctx.Get("deployer").(infra.Deployer)
|
||||
for _, svc := range services {
|
||||
if err := runner.Destroy(ctx.Context, svc); err != nil {
|
||||
if err := runner.Destroy(ctx.GetContext(), svc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
27
handlers/down_test.go
Normal file
27
handlers/down_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
mockCtx "github.com/metrue/fx/context/mocks"
|
||||
mockDeployer "github.com/metrue/fx/infra/mocks"
|
||||
)
|
||||
|
||||
func TestDown(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ctx := mockCtx.NewMockContexter(ctrl)
|
||||
deployer := mockDeployer.NewMockDeployer(ctrl)
|
||||
|
||||
services := []string{"sample-name"}
|
||||
ctx.EXPECT().Get("services").Return(services)
|
||||
ctx.EXPECT().Get("deployer").Return(deployer)
|
||||
ctx.EXPECT().GetContext().Return(context.Background())
|
||||
deployer.EXPECT().Destroy(gomock.Any(), services[0]).Return(nil)
|
||||
if err := Down(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
// BuildImage build image
|
||||
func BuildImage(ctx *context.Context) error {
|
||||
func BuildImage(ctx context.Contexter) error {
|
||||
cli := ctx.GetCliContext()
|
||||
funcFile := cli.Args().First()
|
||||
tag := cli.String("tag")
|
||||
@@ -47,7 +47,7 @@ func BuildImage(ctx *context.Context) error {
|
||||
docker, ok := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
||||
if ok {
|
||||
nameWithTag := tag + ":latest"
|
||||
if err := docker.BuildImage(ctx.Context, workdir, nameWithTag); err != nil {
|
||||
if err := docker.BuildImage(ctx.GetContext(), workdir, nameWithTag); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("image built: %v", constants.CheckedSymbol)
|
||||
@@ -57,7 +57,7 @@ func BuildImage(ctx *context.Context) error {
|
||||
}
|
||||
|
||||
// ExportImage export service's code into a directory
|
||||
func ExportImage(ctx *context.Context) (err error) {
|
||||
func ExportImage(ctx context.Contexter) (err error) {
|
||||
cli := ctx.GetCliContext()
|
||||
funcFile := cli.Args().First()
|
||||
outputDir := cli.String("output")
|
||||
|
||||
@@ -6,21 +6,21 @@ import (
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/infra/docker"
|
||||
"github.com/metrue/fx/infra/k3s"
|
||||
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||
"github.com/metrue/fx/infra/k8s"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
)
|
||||
|
||||
func setupK3S(masterInfo string, agentsInfo string) ([]byte, error) {
|
||||
func setupK8S(masterInfo string, agentsInfo string) ([]byte, error) {
|
||||
info := strings.Split(masterInfo, "@")
|
||||
if len(info) != 2 {
|
||||
return nil, fmt.Errorf("incorrect master info, should be <user>@<ip> format")
|
||||
}
|
||||
master := k3s.MasterNode{
|
||||
master := k8s.MasterNode{
|
||||
User: info[0],
|
||||
IP: info[1],
|
||||
}
|
||||
agents := []k3s.AgentNode{}
|
||||
agents := []k8s.AgentNode{}
|
||||
if agentsInfo != "" {
|
||||
agentsInfoList := strings.Split(agentsInfo, ",")
|
||||
for _, agent := range agentsInfoList {
|
||||
@@ -28,7 +28,7 @@ func setupK3S(masterInfo string, agentsInfo string) ([]byte, error) {
|
||||
if len(info) != 2 {
|
||||
return nil, fmt.Errorf("incorrect agent info, should be <user>@<ip> format")
|
||||
}
|
||||
agents = append(agents, k3s.AgentNode{
|
||||
agents = append(agents, k8s.AgentNode{
|
||||
User: info[0],
|
||||
IP: info[1],
|
||||
})
|
||||
@@ -36,8 +36,8 @@ func setupK3S(masterInfo string, agentsInfo string) ([]byte, error) {
|
||||
}
|
||||
|
||||
fmt.Println(master, agents, len(agents))
|
||||
k3sOperator := k3s.New(master, agents)
|
||||
return k3sOperator.Provision()
|
||||
k8sOperator := k8s.New(master, agents)
|
||||
return k8sOperator.Provision()
|
||||
}
|
||||
|
||||
func setupDocker(hostInfo string) ([]byte, error) {
|
||||
@@ -47,12 +47,12 @@ func setupDocker(hostInfo string) ([]byte, error) {
|
||||
}
|
||||
user := info[1]
|
||||
host := info[0]
|
||||
dockr := docker.New(user, host)
|
||||
dockr := dockerInfra.CreateProvisioner(user, host)
|
||||
return dockr.Provision()
|
||||
}
|
||||
|
||||
// Setup infra
|
||||
func Setup(ctx *context.Context) (err error) {
|
||||
func Setup(ctx context.Contexter) (err error) {
|
||||
const task = "setup infra"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
@@ -69,20 +69,19 @@ func Setup(ctx *context.Context) (err error) {
|
||||
if cli.String("host") == "" {
|
||||
return fmt.Errorf("host required, eg. 'root@123.1.2.12'")
|
||||
}
|
||||
} else if typ == "k3s" {
|
||||
} else if typ == "k8s" {
|
||||
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")
|
||||
return fmt.Errorf("invalid type, 'docker' and 'k8s' support")
|
||||
}
|
||||
|
||||
fxConfig := ctx.Get("config").(*config.Config)
|
||||
|
||||
switch strings.ToLower(typ) {
|
||||
case "k3s":
|
||||
kubeconf, err := setupK3S(cli.String("master"), cli.String("agents"))
|
||||
case "k8s":
|
||||
kubeconf, err := setupK8S(cli.String("master"), cli.String("agents"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -93,8 +92,6 @@ func Setup(ctx *context.Context) (err error) {
|
||||
return err
|
||||
}
|
||||
return fxConfig.AddDockerCloud(name, config)
|
||||
case "k8s":
|
||||
fmt.Println("WIP")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,32 +2,20 @@ package handlers
|
||||
|
||||
import (
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/pkg/render"
|
||||
)
|
||||
|
||||
// List command handle
|
||||
func List(ctx *context.Context) (err error) {
|
||||
const task = "deploying"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
func List(ctx context.Contexter) (err error) {
|
||||
cli := ctx.GetCliContext()
|
||||
deployer := ctx.Get("deployer").(deploy.Deployer)
|
||||
deployer := ctx.Get("deployer").(infra.Deployer)
|
||||
|
||||
services, err := deployer.List(ctx.Context, cli.Args().First())
|
||||
services, err := deployer.List(ctx.GetContext(), cli.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, service := range services {
|
||||
if err := utils.OutputJSON(service); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
render.Table(services)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// ListInfra list infra
|
||||
func ListInfra(ctx *context.Context) (err error) {
|
||||
func ListInfra(ctx context.Contexter) (err error) {
|
||||
fxConfig := ctx.Get("config").(*config.Config)
|
||||
conf, err := fxConfig.View()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,48 +1,34 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/pkg/render"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// PortRange usable port range https: //en.wikipedia.org/wiki/Ephemeral_port
|
||||
var PortRange = struct {
|
||||
min int
|
||||
max int
|
||||
}{
|
||||
min: 1023,
|
||||
max: 65535,
|
||||
}
|
||||
|
||||
// Up command handle
|
||||
func Up(ctx *context.Context) (err error) {
|
||||
const task = "deploying"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
cli := ctx.GetCliContext()
|
||||
name := cli.String("name")
|
||||
port := cli.Int("port")
|
||||
|
||||
if port < PortRange.min || port > PortRange.max {
|
||||
return fmt.Errorf("invalid port number: %d, port number should in range of %d - %d", port, PortRange.min, PortRange.max)
|
||||
}
|
||||
|
||||
func Up(ctx context.Contexter) (err error) {
|
||||
fn := ctx.Get("fn").(types.Func)
|
||||
image := ctx.Get("image").(string)
|
||||
deployer := ctx.Get("deployer").(deploy.Deployer)
|
||||
name := ctx.Get("name").(string)
|
||||
deployer := ctx.Get("deployer").(infra.Deployer)
|
||||
bindings := ctx.Get("bindings").([]types.PortBinding)
|
||||
return deployer.Deploy(
|
||||
ctx.Context,
|
||||
|
||||
if err := deployer.Deploy(
|
||||
ctx.GetContext(),
|
||||
fn,
|
||||
name,
|
||||
image,
|
||||
bindings,
|
||||
)
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service, err := deployer.GetStatus(ctx.GetContext(), name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
render.Table([]types.Service{service})
|
||||
return nil
|
||||
}
|
||||
|
||||
41
handlers/up_test.go
Normal file
41
handlers/up_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
mockCtx "github.com/metrue/fx/context/mocks"
|
||||
mockDeployer "github.com/metrue/fx/infra/mocks"
|
||||
"github.com/metrue/fx/types"
|
||||
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()).Times(2)
|
||||
deployer.EXPECT().Deploy(gomock.Any(), fn, name, image, bindings).Return(nil)
|
||||
deployer.EXPECT().GetStatus(gomock.Any(), name).Return(types.Service{
|
||||
ID: "id-1",
|
||||
Name: name,
|
||||
Host: "127.0.0.1",
|
||||
Port: 2100,
|
||||
}, nil)
|
||||
if err := Up(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// UseInfra use infra
|
||||
func UseInfra(ctx *context.Context) error {
|
||||
func UseInfra(ctx context.Contexter) error {
|
||||
fxConfig := ctx.Get("config").(*config.Config)
|
||||
cli := ctx.GetCliContext()
|
||||
return fxConfig.Use(cli.Args().First())
|
||||
|
||||
101
infra/docker/deployer.go
Normal file
101
infra/docker/deployer.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// Deployer manage container
|
||||
type Deployer struct {
|
||||
cli containerruntimes.ContainerRuntime
|
||||
}
|
||||
|
||||
// CreateClient create a docker instance
|
||||
func CreateClient(client containerruntimes.ContainerRuntime) (d *Deployer, err error) {
|
||||
return &Deployer{cli: client}, nil
|
||||
}
|
||||
|
||||
// Deploy create a Docker container from given image, and bind the constants.FxContainerExposePort to given port
|
||||
func (d *Deployer) Deploy(ctx context.Context, fn types.Func, name string, image string, ports []types.PortBinding) (err error) {
|
||||
spinner.Start("deploying " + name)
|
||||
defer func() {
|
||||
spinner.Stop("deploying "+name, err)
|
||||
}()
|
||||
return d.cli.StartContainer(ctx, name, image, ports)
|
||||
}
|
||||
|
||||
// Update a container
|
||||
func (d *Deployer) Update(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy stop and remove container
|
||||
func (d *Deployer) Destroy(ctx context.Context, name string) (err error) {
|
||||
spinner.Start("destroying " + name)
|
||||
defer func() {
|
||||
spinner.Stop("destroying "+name, err)
|
||||
}()
|
||||
return d.cli.StopContainer(ctx, name)
|
||||
}
|
||||
|
||||
// GetStatus get a service status
|
||||
func (d *Deployer) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||
var container dockerTypes.ContainerJSON
|
||||
if err := d.cli.InspectContainer(ctx, name, &container); err != nil {
|
||||
return types.Service{}, err
|
||||
}
|
||||
|
||||
service := types.Service{
|
||||
ID: container.ID,
|
||||
Name: container.Name,
|
||||
}
|
||||
for _, bindings := range container.NetworkSettings.Ports {
|
||||
if len(bindings) > 0 {
|
||||
binding := bindings[0]
|
||||
port, err := strconv.Atoi(binding.HostPort)
|
||||
if err != nil {
|
||||
return service, err
|
||||
}
|
||||
service.Port = port
|
||||
service.Host = binding.HostIP
|
||||
service.State = container.State.Status
|
||||
service.Image = container.Image
|
||||
break
|
||||
}
|
||||
if service.Port != 0 && service.Host != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
// Ping check healty status of infra
|
||||
func (d *Deployer) Ping(ctx context.Context) error {
|
||||
if _, err := d.cli.Version(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List services
|
||||
func (d *Deployer) List(ctx context.Context, name string) (svcs []types.Service, err error) {
|
||||
const task = "listing"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
// FIXME support remote host
|
||||
return d.cli.ListContainer(ctx, name)
|
||||
}
|
||||
|
||||
var (
|
||||
_ infra.Deployer = &Deployer{}
|
||||
)
|
||||
@@ -1,171 +1,13 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
import containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
|
||||
"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
|
||||
// CreateProvisioner create a provisioner
|
||||
func CreateProvisioner(ip string, user string) *Provisioner {
|
||||
return NewProvisioner(ip, user)
|
||||
}
|
||||
|
||||
// New new a docker object
|
||||
func New(ip string, user string) *Docker {
|
||||
return &Docker{
|
||||
IP: ip,
|
||||
User: user,
|
||||
}
|
||||
// CreateDeployer create a deployer
|
||||
func CreateDeployer(client containerruntimes.ContainerRuntime) (*Deployer, error) {
|
||||
return &Deployer{cli: client}, nil
|
||||
}
|
||||
|
||||
// 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{}
|
||||
|
||||
171
infra/docker/provisioner.go
Normal file
171
infra/docker/provisioner.go
Normal 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"
|
||||
)
|
||||
|
||||
// Provisioner docker host
|
||||
type Provisioner struct {
|
||||
IP string
|
||||
User string
|
||||
}
|
||||
|
||||
// NewProvisioner new a docker object
|
||||
func NewProvisioner(ip string, user string) *Provisioner {
|
||||
return &Provisioner{
|
||||
IP: ip,
|
||||
User: user,
|
||||
}
|
||||
}
|
||||
|
||||
// Provision provision a host, install docker and start dockerd
|
||||
func (d *Provisioner) Provision() ([]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 *Provisioner) isLocalHost() bool {
|
||||
return strings.ToLower(d.IP) == "localhost" || d.IP == "127.0.0.1"
|
||||
}
|
||||
|
||||
func (d *Provisioner) hasDocker() bool {
|
||||
cmd := exec.Command("docker", "version")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// HealthCheck check healthy status of host
|
||||
func (d *Provisioner) HealthCheck() (bool, error) {
|
||||
// TODO
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Install docker on host
|
||||
func (d *Provisioner) Install() error {
|
||||
sudo := ""
|
||||
if d.User != "root" {
|
||||
sudo = "sudo"
|
||||
}
|
||||
installCmd := fmt.Sprintf("curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-18.06.3-ce.tgz -o docker.tgz && tar zxvf docker.tgz && %s mv docker/* /usr/bin && rm -rf docker docker.tgz", sudo)
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
sshPort := infra.GetSSHPort()
|
||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||
if err := ssh.RunCommand(installCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("install docker failed \n================")
|
||||
fmt.Println(err)
|
||||
fmt.Println("================")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartDockerd start dockerd
|
||||
func (d *Provisioner) StartDockerd() error {
|
||||
sudo := ""
|
||||
if d.User != "root" {
|
||||
sudo = "sudo"
|
||||
}
|
||||
installCmd := fmt.Sprintf("%s dockerd >/dev/null 2>&1 & sleep 2", sudo)
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
sshPort := infra.GetSSHPort()
|
||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||
if err := ssh.RunCommand(installCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("start dockerd failed \n================")
|
||||
fmt.Println(err)
|
||||
fmt.Println("================")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartFxAgent start fx agent
|
||||
func (d *Provisioner) StartFxAgent() error {
|
||||
startCmd := fmt.Sprintf("sleep 3 && docker stop %s || true && docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentContainerName, constants.AgentPort)
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
sshPort := infra.GetSSHPort()
|
||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||
if err := ssh.RunCommand(startCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("start fx agent failed \n================")
|
||||
fmt.Println(err)
|
||||
fmt.Println("================")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartFxAgentLocally start fx agent
|
||||
func (d *Provisioner) StartFxAgentLocally() error {
|
||||
startCmd := fmt.Sprintf("docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentPort)
|
||||
params := strings.Split(startCmd, " ")
|
||||
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.Provisioner = &Provisioner{}
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDocker(t *testing.T) {
|
||||
func TestProvisioner(t *testing.T) {
|
||||
if os.Getenv("DOCKER_HOST") == "" ||
|
||||
os.Getenv("DOCKER_USER") == "" {
|
||||
t.Skip("skip test since DOCKER_HOST and DOCKER_USER not ready")
|
||||
}
|
||||
d := New(os.Getenv("DOCKER_HOST"), os.Getenv("DOCKER_USER"))
|
||||
d := NewProvisioner(os.Getenv("DOCKER_HOST"), os.Getenv("DOCKER_USER"))
|
||||
if err := d.Install(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1,7 +1,29 @@
|
||||
package infra
|
||||
|
||||
// Infra infrastructure provision interface
|
||||
type Infra interface {
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// Provisioner provision interface
|
||||
type Provisioner interface {
|
||||
Provision() (config []byte, err error)
|
||||
HealthCheck() (bool, error)
|
||||
}
|
||||
|
||||
// Deployer deploy interface
|
||||
type Deployer interface {
|
||||
Deploy(ctx context.Context, fn types.Func, name string, image string, bindings []types.PortBinding) error
|
||||
Destroy(ctx context.Context, name string) error
|
||||
Update(ctx context.Context, name string) error
|
||||
GetStatus(ctx context.Context, name string) (types.Service, error)
|
||||
List(ctx context.Context, name string) ([]types.Service, error)
|
||||
Ping(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Infra infrastructure provision interface
|
||||
type Infra interface {
|
||||
Provisioner
|
||||
Deployer
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@ package k8s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/deploy"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
@@ -62,14 +64,28 @@ func (k *K8S) Deploy(
|
||||
const replicas = int32(3)
|
||||
if _, err := k.GetDeployment(namespace, name); err != nil {
|
||||
// TODO enable passing replica from fx CLI
|
||||
if _, err := k.CreateDeploymentWithInitContainer(
|
||||
namespace,
|
||||
name,
|
||||
ports,
|
||||
replicas,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
if os.Getenv("K3S") != "" {
|
||||
// NOTE Doing docker build in initial container will fail when cluster is created by K3S
|
||||
if _, err := k.CreateDeployment(
|
||||
namespace,
|
||||
name,
|
||||
image,
|
||||
ports,
|
||||
replicas,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err := k.CreateDeploymentWithInitContainer(
|
||||
namespace,
|
||||
name,
|
||||
ports,
|
||||
replicas,
|
||||
selector,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := k.UpdateDeployment(
|
||||
@@ -132,15 +148,49 @@ func (k *K8S) Destroy(ctx context.Context, name string) error {
|
||||
}
|
||||
|
||||
// GetStatus get status of a service
|
||||
func (k *K8S) GetStatus(ctx context.Context, name string) error {
|
||||
return nil
|
||||
func (k *K8S) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||
svc, err := k.GetService(namespace, name)
|
||||
service := types.Service{}
|
||||
if err != nil {
|
||||
return service, err
|
||||
}
|
||||
|
||||
service.Host = svc.Spec.ClusterIP
|
||||
if len(svc.Spec.ExternalIPs) > 0 {
|
||||
service.Host = svc.Spec.ExternalIPs[0]
|
||||
}
|
||||
|
||||
for _, port := range svc.Spec.Ports {
|
||||
// TODO should clearify which port (target port, node port) should use
|
||||
service.Port = int(port.Port)
|
||||
break
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
// List services
|
||||
func (k *K8S) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||
func (k *K8S) List(ctx context.Context, name string) (svcs []types.Service, err error) {
|
||||
const task = "listing"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
return []types.Service{}, nil
|
||||
}
|
||||
|
||||
// Ping health check of infra
|
||||
func (k *K8S) Ping(ctx context.Context) error {
|
||||
// Does not find any ping method for k8s
|
||||
nodes, err := k.ListNodes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(nodes.Items) <= 0 {
|
||||
return fmt.Errorf("no available nodes")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ deploy.Deployer = &K8S{}
|
||||
_ infra.Deployer = &K8S{}
|
||||
)
|
||||
11
infra/k8s/k8s.go
Normal file
11
infra/k8s/k8s.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package k8s
|
||||
|
||||
// CreateProvisioner create a provisioner
|
||||
func CreateProvisioner(master MasterNode, agents []AgentNode) *Provisioner {
|
||||
return New(master, agents)
|
||||
}
|
||||
|
||||
// CreateDeployer create a deployer
|
||||
func CreateDeployer(kubeconfig string) (*K8S, error) {
|
||||
return Create(kubeconfig)
|
||||
}
|
||||
15
infra/k8s/node.go
Normal file
15
infra/k8s/node.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ListNodes list node
|
||||
func (k *K8S) ListNodes() (*v1.NodeList, error) {
|
||||
nodes, err := k.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package k3s
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -23,8 +23,8 @@ type AgentNode struct {
|
||||
User string
|
||||
}
|
||||
|
||||
// K3S k3s operator
|
||||
type K3S struct {
|
||||
// Provisioner k3s operator
|
||||
type Provisioner struct {
|
||||
master MasterNode
|
||||
agents []AgentNode
|
||||
}
|
||||
@@ -34,15 +34,15 @@ type K3S struct {
|
||||
const version = "v0.9.1"
|
||||
|
||||
// New new a operator
|
||||
func New(master MasterNode, agents []AgentNode) *K3S {
|
||||
return &K3S{
|
||||
func New(master MasterNode, agents []AgentNode) *Provisioner {
|
||||
return &Provisioner{
|
||||
master: master,
|
||||
agents: agents,
|
||||
}
|
||||
}
|
||||
|
||||
// Provision provision k3s cluster
|
||||
func (k *K3S) Provision() ([]byte, error) {
|
||||
func (k *Provisioner) Provision() ([]byte, error) {
|
||||
if err := k.SetupMaster(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -53,13 +53,13 @@ func (k *K3S) Provision() ([]byte, error) {
|
||||
}
|
||||
|
||||
// HealthCheck check healthy status of host
|
||||
func (k *K3S) HealthCheck() (bool, error) {
|
||||
func (k *Provisioner) HealthCheck() (bool, error) {
|
||||
// TODO
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// SetupMaster setup master node
|
||||
func (k *K3S) SetupMaster() error {
|
||||
func (k *Provisioner) SetupMaster() error {
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
||||
installCmd := fmt.Sprintf("curl -sLS https://get.k3s.io | INSTALL_K3S_EXEC='server --tls-san %s' INSTALL_K3S_VERSION='%s' sh -", k.master.IP, version)
|
||||
@@ -75,7 +75,7 @@ func (k *K3S) SetupMaster() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *K3S) getToken() (string, error) {
|
||||
func (k *Provisioner) getToken() (string, error) {
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
||||
script := "cat /var/lib/rancher/k3s/server/node-token"
|
||||
@@ -91,7 +91,7 @@ func (k *K3S) getToken() (string, error) {
|
||||
}
|
||||
|
||||
// SetupAgent set agent node
|
||||
func (k *K3S) SetupAgent() error {
|
||||
func (k *Provisioner) SetupAgent() error {
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
tok, err := k.getToken()
|
||||
if err != nil {
|
||||
@@ -117,7 +117,7 @@ func (k *K3S) SetupAgent() error {
|
||||
}
|
||||
|
||||
// GetKubeConfig get kubeconfig of k3s cluster
|
||||
func (k *K3S) GetKubeConfig() ([]byte, error) {
|
||||
func (k *Provisioner) GetKubeConfig() ([]byte, error) {
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
var config []byte
|
||||
getConfigCmd := "cat /etc/rancher/k3s/k3s.yaml\n"
|
||||
@@ -138,6 +138,7 @@ func (k *K3S) GetKubeConfig() ([]byte, error) {
|
||||
|
||||
func rewriteKubeconfig(kubeconfig string, ip string, context string) []byte {
|
||||
if context == "" {
|
||||
// nolint
|
||||
context = "default"
|
||||
}
|
||||
|
||||
@@ -150,4 +151,4 @@ func rewriteKubeconfig(kubeconfig string, ip string, context string) []byte {
|
||||
return []byte(kubeconfigReplacer.Replace(kubeconfig))
|
||||
}
|
||||
|
||||
var _ infra.Infra = &K3S{}
|
||||
var _ infra.Provisioner = &Provisioner{}
|
||||
@@ -1,4 +1,4 @@
|
||||
package k3s
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestK3S(t *testing.T) {
|
||||
func TestProvisioner(t *testing.T) {
|
||||
if os.Getenv("K3S_MASTER_IP") == "" ||
|
||||
os.Getenv("K3S_MASTER_USER") == "" ||
|
||||
os.Getenv("K3S_AGENT_IP") == "" ||
|
||||
@@ -5,10 +5,174 @@
|
||||
package mock_infra
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
types "github.com/metrue/fx/types"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockProvisioner is a mock of Provisioner interface
|
||||
type MockProvisioner struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockProvisionerMockRecorder
|
||||
}
|
||||
|
||||
// MockProvisionerMockRecorder is the mock recorder for MockProvisioner
|
||||
type MockProvisionerMockRecorder struct {
|
||||
mock *MockProvisioner
|
||||
}
|
||||
|
||||
// NewMockProvisioner creates a new mock instance
|
||||
func NewMockProvisioner(ctrl *gomock.Controller) *MockProvisioner {
|
||||
mock := &MockProvisioner{ctrl: ctrl}
|
||||
mock.recorder = &MockProvisionerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockProvisioner) EXPECT() *MockProvisionerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Provision mocks base method
|
||||
func (m *MockProvisioner) Provision() ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Provision")
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Provision indicates an expected call of Provision
|
||||
func (mr *MockProvisionerMockRecorder) Provision() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provision", reflect.TypeOf((*MockProvisioner)(nil).Provision))
|
||||
}
|
||||
|
||||
// HealthCheck mocks base method
|
||||
func (m *MockProvisioner) HealthCheck() (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HealthCheck")
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// HealthCheck indicates an expected call of HealthCheck
|
||||
func (mr *MockProvisionerMockRecorder) HealthCheck() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockProvisioner)(nil).HealthCheck))
|
||||
}
|
||||
|
||||
// MockDeployer is a mock of Deployer interface
|
||||
type MockDeployer struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockDeployerMockRecorder
|
||||
}
|
||||
|
||||
// MockDeployerMockRecorder is the mock recorder for MockDeployer
|
||||
type MockDeployerMockRecorder struct {
|
||||
mock *MockDeployer
|
||||
}
|
||||
|
||||
// NewMockDeployer creates a new mock instance
|
||||
func NewMockDeployer(ctrl *gomock.Controller) *MockDeployer {
|
||||
mock := &MockDeployer{ctrl: ctrl}
|
||||
mock.recorder = &MockDeployerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockDeployer) EXPECT() *MockDeployerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Deploy mocks base method
|
||||
func (m *MockDeployer) Deploy(ctx context.Context, fn types.Func, name, image string, bindings []types.PortBinding) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Deploy", ctx, fn, name, image, bindings)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Deploy indicates an expected call of Deploy
|
||||
func (mr *MockDeployerMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockDeployer)(nil).Deploy), ctx, fn, name, image, bindings)
|
||||
}
|
||||
|
||||
// Destroy mocks base method
|
||||
func (m *MockDeployer) Destroy(ctx context.Context, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Destroy", ctx, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Destroy indicates an expected call of Destroy
|
||||
func (mr *MockDeployerMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockDeployer)(nil).Destroy), ctx, name)
|
||||
}
|
||||
|
||||
// Update mocks base method
|
||||
func (m *MockDeployer) Update(ctx context.Context, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Update", ctx, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Update indicates an expected call of Update
|
||||
func (mr *MockDeployerMockRecorder) Update(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDeployer)(nil).Update), ctx, name)
|
||||
}
|
||||
|
||||
// GetStatus mocks base method
|
||||
func (m *MockDeployer) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetStatus", ctx, name)
|
||||
ret0, _ := ret[0].(types.Service)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetStatus indicates an expected call of GetStatus
|
||||
func (mr *MockDeployerMockRecorder) GetStatus(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockDeployer)(nil).GetStatus), ctx, name)
|
||||
}
|
||||
|
||||
// List mocks base method
|
||||
func (m *MockDeployer) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "List", ctx, name)
|
||||
ret0, _ := ret[0].([]types.Service)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// List indicates an expected call of List
|
||||
func (mr *MockDeployerMockRecorder) List(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockDeployer)(nil).List), ctx, name)
|
||||
}
|
||||
|
||||
// Ping mocks base method
|
||||
func (m *MockDeployer) Ping(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Ping", ctx)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Ping indicates an expected call of Ping
|
||||
func (mr *MockDeployerMockRecorder) Ping(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockDeployer)(nil).Ping), ctx)
|
||||
}
|
||||
|
||||
// MockInfra is a mock of Infra interface
|
||||
type MockInfra struct {
|
||||
ctrl *gomock.Controller
|
||||
@@ -61,3 +225,89 @@ func (mr *MockInfraMockRecorder) HealthCheck() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockInfra)(nil).HealthCheck))
|
||||
}
|
||||
|
||||
// Deploy mocks base method
|
||||
func (m *MockInfra) Deploy(ctx context.Context, fn 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 *MockInfraMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockInfra)(nil).Deploy), ctx, fn, name, image, bindings)
|
||||
}
|
||||
|
||||
// Destroy mocks base method
|
||||
func (m *MockInfra) Destroy(ctx context.Context, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Destroy", ctx, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Destroy indicates an expected call of Destroy
|
||||
func (mr *MockInfraMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockInfra)(nil).Destroy), ctx, name)
|
||||
}
|
||||
|
||||
// Update mocks base method
|
||||
func (m *MockInfra) Update(ctx context.Context, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Update", ctx, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Update indicates an expected call of Update
|
||||
func (mr *MockInfraMockRecorder) Update(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockInfra)(nil).Update), ctx, name)
|
||||
}
|
||||
|
||||
// GetStatus mocks base method
|
||||
func (m *MockInfra) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetStatus", ctx, name)
|
||||
ret0, _ := ret[0].(types.Service)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetStatus indicates an expected call of GetStatus
|
||||
func (mr *MockInfraMockRecorder) GetStatus(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockInfra)(nil).GetStatus), ctx, name)
|
||||
}
|
||||
|
||||
// List mocks base method
|
||||
func (m *MockInfra) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "List", ctx, name)
|
||||
ret0, _ := ret[0].([]types.Service)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// List indicates an expected call of List
|
||||
func (mr *MockInfraMockRecorder) List(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInfra)(nil).List), ctx, name)
|
||||
}
|
||||
|
||||
// Ping mocks base method
|
||||
func (m *MockInfra) Ping(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Ping", ctx)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Ping indicates an expected call of Ping
|
||||
func (mr *MockInfraMockRecorder) Ping(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockInfra)(nil).Ping), ctx)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,36 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/phayes/freeport"
|
||||
)
|
||||
|
||||
// PortRange usable port range https: //en.wikipedia.org/wiki/Ephemeral_port
|
||||
var PortRange = struct {
|
||||
min int
|
||||
max int
|
||||
}{
|
||||
min: 1023,
|
||||
max: 65535,
|
||||
}
|
||||
|
||||
// Binding create bindings
|
||||
func Binding(ctx *context.Context) error {
|
||||
cli := ctx.GetCliContext()
|
||||
port := cli.Int("port")
|
||||
func Binding(ctx context.Contexter) (err error) {
|
||||
port := ctx.Get("port").(int)
|
||||
if port == 0 {
|
||||
port, err = freeport.GetFreePort()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if port < PortRange.min || port > PortRange.max {
|
||||
return fmt.Errorf("invalid port number: %d, port number should in range of %d - %d", port, PortRange.min, PortRange.max)
|
||||
}
|
||||
|
||||
var bindings []types.PortBinding
|
||||
if os.Getenv("KUBECONFIG") != "" {
|
||||
|
||||
20
middlewares/binding_test.go
Normal file
20
middlewares/binding_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
mockCtx "github.com/metrue/fx/context/mocks"
|
||||
)
|
||||
|
||||
func TestBinding(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
ctx := mockCtx.NewMockContexter(ctrl)
|
||||
ctx.EXPECT().Get("port").Return(0)
|
||||
ctx.EXPECT().Set("bindings", gomock.Any())
|
||||
if err := Binding(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
// Build image
|
||||
func Build(ctx *context.Context) (err error) {
|
||||
func Build(ctx context.Contexter) (err error) {
|
||||
const task = "building"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
@@ -30,12 +30,12 @@ func Build(ctx *context.Context) (err error) {
|
||||
if err := packer.PackIntoDir(fn, workdir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := docker.BuildImage(ctx.Context, workdir, name); err != nil {
|
||||
if err := docker.BuildImage(ctx.GetContext(), workdir, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nameWithTag := name + ":latest"
|
||||
if err := docker.TagImage(ctx, name, nameWithTag); err != nil {
|
||||
if err := docker.TagImage(ctx.GetContext(), name, nameWithTag); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("image", nameWithTag)
|
||||
@@ -45,7 +45,7 @@ func Build(ctx *context.Context) (err error) {
|
||||
username := os.Getenv("DOCKER_USERNAME")
|
||||
password := os.Getenv("DOCKER_PASSWORD")
|
||||
if username != "" && password != "" {
|
||||
if _, err := docker.PushImage(ctx.Context, name); err != nil {
|
||||
if _, err := docker.PushImage(ctx.GetContext(), name); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("image", username+"/"+name)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// LoadConfig load default config
|
||||
func LoadConfig(ctx *context.Context) error {
|
||||
func LoadConfig(ctx context.Contexter) error {
|
||||
config, err := config.LoadDefault()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -4,32 +4,48 @@ 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.Context) (err error) {
|
||||
const task = "parsing"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
func Parse(action string) func(ctx context.Contexter) (err error) {
|
||||
return func(ctx context.Contexter) error {
|
||||
cli := ctx.GetCliContext()
|
||||
switch action {
|
||||
case "up":
|
||||
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)
|
||||
|
||||
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)
|
||||
case "down":
|
||||
services := cli.Args()
|
||||
if len(services) == 0 {
|
||||
return errors.New("service name required")
|
||||
}
|
||||
svc := []string{}
|
||||
for _, service := range services {
|
||||
svc = append(svc, service)
|
||||
}
|
||||
ctx.Set("services", svc)
|
||||
case "list":
|
||||
name := cli.Args().First()
|
||||
ctx.Set("filter", name)
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,44 +8,32 @@ import (
|
||||
"github.com/metrue/fx/constants"
|
||||
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/deploy"
|
||||
dockerDeployer "github.com/metrue/fx/deploy/docker"
|
||||
k3sDeployer "github.com/metrue/fx/deploy/k3s"
|
||||
k8sDeployer "github.com/metrue/fx/deploy/k8s"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/infra"
|
||||
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||
k8sInfra "github.com/metrue/fx/infra/k8s"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Setup create k8s or docker cli
|
||||
func Setup(ctx *context.Context) (err error) {
|
||||
const task = "setup"
|
||||
spinner.Start(task)
|
||||
defer func() {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
func Setup(ctx context.Contexter) (err error) {
|
||||
fxConfig := ctx.Get("config").(*config.Config)
|
||||
cloud := fxConfig.Clouds[fxConfig.CurrentCloud]
|
||||
|
||||
var deployer deploy.Deployer
|
||||
var deployer infra.Deployer
|
||||
if cloud["type"] == config.CloudTypeDocker {
|
||||
docker, err := dockerHTTP.Create(cloud["host"], constants.AgentPort)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrapf(err, "please make sure docker is installed and running on your host")
|
||||
}
|
||||
// TODO should clean up, but it needed in middlewares.Build
|
||||
ctx.Set("docker", docker)
|
||||
deployer, err = dockerDeployer.CreateClient(docker)
|
||||
deployer, err = dockerInfra.CreateDeployer(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 os.Getenv("KUBECONFIG") != "" {
|
||||
deployer, err = k8sInfra.CreateDeployer(cloud["kubeconfig"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
27
pkg/render/render.go
Normal file
27
pkg/render/render.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
// Table output services as table format
|
||||
func Table(services []types.Service) {
|
||||
data := [][]string{}
|
||||
for _, s := range services {
|
||||
col := []string{
|
||||
s.ID,
|
||||
s.Name,
|
||||
fmt.Sprintf("%s:%d", s.Host, +s.Port),
|
||||
}
|
||||
data = append(data, col)
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"ID", "Name", "Endpoint"})
|
||||
table.AppendBulk(data)
|
||||
table.Render()
|
||||
}
|
||||
19
pkg/render/render_test.go
Normal file
19
pkg/render/render_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestTable(t *testing.T) {
|
||||
services := []types.Service{
|
||||
types.Service{
|
||||
ID: "id-1",
|
||||
Name: "name-1",
|
||||
Host: "127.0.0.1",
|
||||
Port: 1000,
|
||||
},
|
||||
}
|
||||
Table(services)
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package provision
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/pkg/command"
|
||||
ssh "github.com/metrue/go-ssh-client"
|
||||
)
|
||||
|
||||
// Provisioner provision
|
||||
type Provisioner interface {
|
||||
Start() error
|
||||
}
|
||||
|
||||
// Provisionor provision-or
|
||||
type Provisionor struct {
|
||||
sshClient ssh.Client
|
||||
host string
|
||||
}
|
||||
|
||||
func isLocal(host string) bool {
|
||||
if host == "" {
|
||||
return false
|
||||
}
|
||||
return host == "127.0.0.1" || host == "localhost" || host == "0.0.0.0"
|
||||
}
|
||||
|
||||
// NewWithHost create a provisionor with host, user, and password
|
||||
func NewWithHost(host string, user string, password string) *Provisionor {
|
||||
p := &Provisionor{
|
||||
host: host,
|
||||
}
|
||||
if !isLocal(host) {
|
||||
p.sshClient = ssh.New(host).
|
||||
WithUser(user).
|
||||
WithPassword(password)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// IsFxAgentRunning check if fx-agent is running on host
|
||||
func (p *Provisionor) IsFxAgentRunning() bool {
|
||||
script := fmt.Sprintf("docker inspect %s", constants.AgentContainerName)
|
||||
var cmd *command.Command
|
||||
if !isLocal(p.host) {
|
||||
cmd = command.New("inspect fx-agent", script, command.NewRemoteRunner(p.sshClient))
|
||||
} else {
|
||||
cmd = command.New("inspect fx-agent", script, command.NewLocalRunner())
|
||||
}
|
||||
output, err := cmd.Exec()
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
log.Infof(string(output))
|
||||
}
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// StartFxAgent start fx agent
|
||||
func (p *Provisionor) StartFxAgent() error {
|
||||
script := fmt.Sprintf("docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentPort)
|
||||
var cmd *command.Command
|
||||
if !isLocal(p.host) {
|
||||
cmd = command.New("start fx-agent", script, command.NewRemoteRunner(p.sshClient))
|
||||
} else {
|
||||
cmd = command.New("start fx-agent", script, command.NewLocalRunner())
|
||||
}
|
||||
if output, err := cmd.Exec(); err != nil {
|
||||
log.Info(string(output))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopFxAgent stop fx agent
|
||||
func (p *Provisionor) StopFxAgent() error {
|
||||
script := fmt.Sprintf("docker stop %s", constants.AgentContainerName)
|
||||
var cmd *command.Command
|
||||
if !isLocal(p.host) {
|
||||
cmd = command.New("stop fx agent", script, command.NewRemoteRunner(p.sshClient))
|
||||
} else {
|
||||
cmd = command.New("stop fx agent", script, command.NewLocalRunner())
|
||||
}
|
||||
if output, err := cmd.Exec(); err != nil {
|
||||
log.Infof(string(output))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start start provision progress
|
||||
func (p *Provisionor) Start() error {
|
||||
scripts := map[string]string{
|
||||
"pull java Docker base image": "docker pull metrue/fx-java-base",
|
||||
"pull julia Docker base image": "docker pull metrue/fx-julia-base",
|
||||
"pull python Docker base iamge": "docker pull metrue/fx-python-base",
|
||||
"pull node Docker base image": "docker pull metrue/fx-node-base",
|
||||
"pull d Docker base image": "docker pull metrue/fx-d-base",
|
||||
"pull go Docker base image": "docker pull metrue/fx-go-base",
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for n, s := range scripts {
|
||||
wg.Add(1)
|
||||
go func(name, script string) {
|
||||
var cmd *command.Command
|
||||
if !isLocal(p.host) {
|
||||
cmd = command.New(name, script, command.NewRemoteRunner(p.sshClient))
|
||||
} else {
|
||||
cmd = command.New(name, script, command.NewLocalRunner())
|
||||
}
|
||||
if _, err := cmd.Exec(); err != nil {
|
||||
log.Fatalf("Provision:%s: %s", cmd.Name, err)
|
||||
} else {
|
||||
log.Infof("Provision:%s: \u2713", cmd.Name)
|
||||
}
|
||||
wg.Done()
|
||||
}(n, s)
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package provision
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestProvisionWorkflow(t *testing.T) {
|
||||
provisionor := NewWithHost("127.0.0.1", "", "")
|
||||
|
||||
_ = provisionor.StopFxAgent()
|
||||
// TODO wait too long here to make test pass
|
||||
time.Sleep(40 * time.Second)
|
||||
|
||||
running := provisionor.IsFxAgentRunning()
|
||||
if running {
|
||||
t.Fatalf("fx-agent should not be running")
|
||||
}
|
||||
|
||||
if err := provisionor.StartFxAgent(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
running = provisionor.IsFxAgentRunning()
|
||||
if !running {
|
||||
t.Fatalf("fx-agent should be running")
|
||||
}
|
||||
|
||||
if err := provisionor.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := provisionor.StopFxAgent(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ echo "mode: atomic\n" > coverage.txt
|
||||
for d in `go list ./... | grep -v 'mocks\|images\|examples\|assets'`; do
|
||||
echo $d
|
||||
go test -race -coverprofile=profile.out -covermode=atomic $d
|
||||
if [ $? -ne 0 ];then
|
||||
exit 1
|
||||
fi
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out | grep -v "mode: atomic" >> coverage.txt
|
||||
rm profile.out
|
||||
|
||||
Reference in New Issue
Block a user