Refactor driver and privioner (#489)
This commit is contained in:
@@ -31,10 +31,32 @@ import (
|
||||
|
||||
// API interact with dockerd http api
|
||||
type API struct {
|
||||
host string
|
||||
port string
|
||||
endpoint string
|
||||
version string
|
||||
}
|
||||
|
||||
// New a API
|
||||
func New(host string, port string) *API {
|
||||
return &API{
|
||||
host: host,
|
||||
port: port,
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize an API
|
||||
func (api *API) Initialize() error {
|
||||
addr := api.host + ":" + api.port
|
||||
v, err := version(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
endpoint := fmt.Sprintf("http://%s:%s/v%s", api.host, api.port, v)
|
||||
api.endpoint = endpoint
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a API
|
||||
func Create(host string, port string) (*API, error) {
|
||||
addr := host + ":" + port
|
||||
@@ -133,7 +155,8 @@ func (api *API) post(path string, body []byte, expectStatus int, v interface{})
|
||||
|
||||
// Version get version of docker engine
|
||||
func (api *API) Version(ctx context.Context) (string, error) {
|
||||
return version(api.endpoint)
|
||||
addr := api.host + ":" + api.port
|
||||
return version(addr)
|
||||
}
|
||||
|
||||
func version(endpoint string) (string, error) {
|
||||
|
||||
164
container_runtimes/mocks/runtime.go
Normal file
164
container_runtimes/mocks/runtime.go
Normal file
@@ -0,0 +1,164 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: runtimes.go
|
||||
|
||||
// Package mock_containerruntimes is a generated GoMock package.
|
||||
package mock_containerruntimes
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
types "github.com/metrue/fx/types"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockContainerRuntime is a mock of ContainerRuntime interface
|
||||
type MockContainerRuntime struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockContainerRuntimeMockRecorder
|
||||
}
|
||||
|
||||
// MockContainerRuntimeMockRecorder is the mock recorder for MockContainerRuntime
|
||||
type MockContainerRuntimeMockRecorder struct {
|
||||
mock *MockContainerRuntime
|
||||
}
|
||||
|
||||
// NewMockContainerRuntime creates a new mock instance
|
||||
func NewMockContainerRuntime(ctrl *gomock.Controller) *MockContainerRuntime {
|
||||
mock := &MockContainerRuntime{ctrl: ctrl}
|
||||
mock.recorder = &MockContainerRuntimeMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockContainerRuntime) EXPECT() *MockContainerRuntimeMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// BuildImage mocks base method
|
||||
func (m *MockContainerRuntime) BuildImage(ctx context.Context, workdir, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "BuildImage", ctx, workdir, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// BuildImage indicates an expected call of BuildImage
|
||||
func (mr *MockContainerRuntimeMockRecorder) BuildImage(ctx, workdir, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildImage", reflect.TypeOf((*MockContainerRuntime)(nil).BuildImage), ctx, workdir, name)
|
||||
}
|
||||
|
||||
// PushImage mocks base method
|
||||
func (m *MockContainerRuntime) PushImage(ctx context.Context, name string) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "PushImage", ctx, name)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// PushImage indicates an expected call of PushImage
|
||||
func (mr *MockContainerRuntimeMockRecorder) PushImage(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushImage", reflect.TypeOf((*MockContainerRuntime)(nil).PushImage), ctx, name)
|
||||
}
|
||||
|
||||
// InspectImage mocks base method
|
||||
func (m *MockContainerRuntime) InspectImage(ctx context.Context, name string, img interface{}) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InspectImage", ctx, name, img)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// InspectImage indicates an expected call of InspectImage
|
||||
func (mr *MockContainerRuntimeMockRecorder) InspectImage(ctx, name, img interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InspectImage", reflect.TypeOf((*MockContainerRuntime)(nil).InspectImage), ctx, name, img)
|
||||
}
|
||||
|
||||
// TagImage mocks base method
|
||||
func (m *MockContainerRuntime) TagImage(ctx context.Context, name, tag string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "TagImage", ctx, name, tag)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// TagImage indicates an expected call of TagImage
|
||||
func (mr *MockContainerRuntimeMockRecorder) TagImage(ctx, name, tag interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TagImage", reflect.TypeOf((*MockContainerRuntime)(nil).TagImage), ctx, name, tag)
|
||||
}
|
||||
|
||||
// StartContainer mocks base method
|
||||
func (m *MockContainerRuntime) StartContainer(ctx context.Context, name, image string, bindings []types.PortBinding) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "StartContainer", ctx, name, image, bindings)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// StartContainer indicates an expected call of StartContainer
|
||||
func (mr *MockContainerRuntimeMockRecorder) StartContainer(ctx, name, image, bindings interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartContainer", reflect.TypeOf((*MockContainerRuntime)(nil).StartContainer), ctx, name, image, bindings)
|
||||
}
|
||||
|
||||
// StopContainer mocks base method
|
||||
func (m *MockContainerRuntime) StopContainer(ctx context.Context, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "StopContainer", ctx, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// StopContainer indicates an expected call of StopContainer
|
||||
func (mr *MockContainerRuntimeMockRecorder) StopContainer(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopContainer", reflect.TypeOf((*MockContainerRuntime)(nil).StopContainer), ctx, name)
|
||||
}
|
||||
|
||||
// InspectContainer mocks base method
|
||||
func (m *MockContainerRuntime) InspectContainer(ctx context.Context, name string, container interface{}) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InspectContainer", ctx, name, container)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// InspectContainer indicates an expected call of InspectContainer
|
||||
func (mr *MockContainerRuntimeMockRecorder) InspectContainer(ctx, name, container interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InspectContainer", reflect.TypeOf((*MockContainerRuntime)(nil).InspectContainer), ctx, name, container)
|
||||
}
|
||||
|
||||
// ListContainer mocks base method
|
||||
func (m *MockContainerRuntime) ListContainer(ctx context.Context, filter string) ([]types.Service, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ListContainer", ctx, filter)
|
||||
ret0, _ := ret[0].([]types.Service)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ListContainer indicates an expected call of ListContainer
|
||||
func (mr *MockContainerRuntimeMockRecorder) ListContainer(ctx, filter interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListContainer", reflect.TypeOf((*MockContainerRuntime)(nil).ListContainer), ctx, filter)
|
||||
}
|
||||
|
||||
// Version mocks base method
|
||||
func (m *MockContainerRuntime) Version(ctx context.Context) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Version", ctx)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Version indicates an expected call of Version
|
||||
func (mr *MockContainerRuntimeMockRecorder) Version(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Version", reflect.TypeOf((*MockContainerRuntime)(nil).Version), ctx)
|
||||
}
|
||||
@@ -6,48 +6,63 @@ import (
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/driver"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// Deployer manage container
|
||||
type Deployer struct {
|
||||
cli containerruntimes.ContainerRuntime
|
||||
// Driver manage container
|
||||
type Driver struct {
|
||||
dockerClient containerruntimes.ContainerRuntime
|
||||
}
|
||||
|
||||
// CreateClient create a docker instance
|
||||
func CreateClient(client containerruntimes.ContainerRuntime) (d *Deployer, err error) {
|
||||
return &Deployer{cli: client}, nil
|
||||
// Options to initialize a fx docker driver
|
||||
type Options struct {
|
||||
DockerClient containerruntimes.ContainerRuntime
|
||||
}
|
||||
|
||||
// New a fx docker driver
|
||||
func New(options Options) *Driver {
|
||||
return &Driver{
|
||||
dockerClient: options.DockerClient,
|
||||
}
|
||||
}
|
||||
|
||||
// Ping check healty status of driver
|
||||
func (d *Driver) Ping(ctx context.Context) error {
|
||||
if _, err := d.dockerClient.Version(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deploy create a Docker container from given image, and bind the constants.FxContainerExposePort to given port
|
||||
func (d *Deployer) Deploy(ctx context.Context, fn string, name string, image string, ports []types.PortBinding) (err error) {
|
||||
func (d *Driver) Deploy(ctx context.Context, fn string, name string, image string, ports []types.PortBinding) (err error) {
|
||||
spinner.Start("deploying " + name)
|
||||
defer func() {
|
||||
spinner.Stop("deploying "+name, err)
|
||||
}()
|
||||
return d.cli.StartContainer(ctx, name, image, ports)
|
||||
return d.dockerClient.StartContainer(ctx, name, image, ports)
|
||||
}
|
||||
|
||||
// Update a container
|
||||
func (d *Deployer) Update(ctx context.Context, name string) error {
|
||||
func (d *Driver) 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) {
|
||||
func (d *Driver) 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)
|
||||
return d.dockerClient.StopContainer(ctx, name)
|
||||
}
|
||||
|
||||
// GetStatus get a service status
|
||||
func (d *Deployer) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||
func (d *Driver) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||
var container dockerTypes.ContainerJSON
|
||||
if err := d.cli.InspectContainer(ctx, name, &container); err != nil {
|
||||
if err := d.dockerClient.InspectContainer(ctx, name, &container); err != nil {
|
||||
return types.Service{}, err
|
||||
}
|
||||
|
||||
@@ -55,6 +70,7 @@ func (d *Deployer) GetStatus(ctx context.Context, name string) (types.Service, e
|
||||
ID: container.ID,
|
||||
Name: container.Name,
|
||||
}
|
||||
|
||||
for _, bindings := range container.NetworkSettings.Ports {
|
||||
if len(bindings) > 0 {
|
||||
binding := bindings[0]
|
||||
@@ -76,20 +92,12 @@ func (d *Deployer) GetStatus(ctx context.Context, name string) (types.Service, e
|
||||
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) {
|
||||
func (d *Driver) List(ctx context.Context, name string) (svcs []types.Service, err error) {
|
||||
// FIXME support remote host
|
||||
return d.cli.ListContainer(ctx, name)
|
||||
return d.dockerClient.ListContainer(ctx, name)
|
||||
}
|
||||
|
||||
var (
|
||||
_ infra.Deployer = &Deployer{}
|
||||
_ driver.Driver = &Driver{}
|
||||
)
|
||||
94
driver/docker/docker_test.go
Normal file
94
driver/docker/docker_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
dockerMock "github.com/metrue/fx/container_runtimes/mocks"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestDriverPing(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
dockerClient := dockerMock.NewMockContainerRuntime(ctrl)
|
||||
n := New(Options{
|
||||
DockerClient: dockerClient,
|
||||
})
|
||||
ctx := context.Background()
|
||||
dockerClient.EXPECT().Version(ctx).Return("", nil)
|
||||
if err := n.Ping(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriverDeploy(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
dockerClient := dockerMock.NewMockContainerRuntime(ctrl)
|
||||
n := New(Options{
|
||||
DockerClient: dockerClient,
|
||||
})
|
||||
ctx := context.Background()
|
||||
fn := "fn"
|
||||
name := "name"
|
||||
image := "image"
|
||||
ports := []types.PortBinding{}
|
||||
dockerClient.EXPECT().StartContainer(ctx, name, image, ports).Return(nil)
|
||||
if err := n.Deploy(ctx, fn, name, image, ports); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriverDestroy(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
dockerClient := dockerMock.NewMockContainerRuntime(ctrl)
|
||||
n := New(Options{
|
||||
DockerClient: dockerClient,
|
||||
})
|
||||
ctx := context.Background()
|
||||
name := "name"
|
||||
dockerClient.EXPECT().StopContainer(ctx, name).Return(nil)
|
||||
if err := n.Destroy(ctx, name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDriverGetStatus(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
dockerClient := dockerMock.NewMockContainerRuntime(ctrl)
|
||||
n := New(Options{
|
||||
DockerClient: dockerClient,
|
||||
})
|
||||
ctx := context.Background()
|
||||
name := "name"
|
||||
err := errors.New("no such container")
|
||||
dockerClient.EXPECT().InspectContainer(ctx, name, gomock.Any()).Return(err)
|
||||
if _, err := n.GetStatus(ctx, name); err == nil {
|
||||
t.Fatalf("should get error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
dockerClient := dockerMock.NewMockContainerRuntime(ctrl)
|
||||
n := New(Options{
|
||||
DockerClient: dockerClient,
|
||||
})
|
||||
ctx := context.Background()
|
||||
name := "name"
|
||||
dockerClient.EXPECT().ListContainer(ctx, name).Return(nil, nil)
|
||||
if _, err := n.List(ctx, name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package infra
|
||||
package driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -6,14 +6,8 @@ import (
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// Clouder cloud interface
|
||||
type Clouder interface {
|
||||
Provision() error
|
||||
IsHealth() (bool, error)
|
||||
}
|
||||
|
||||
// Deployer deploy interface
|
||||
type Deployer interface {
|
||||
// Driver fx function running driver
|
||||
type Driver interface {
|
||||
Deploy(ctx context.Context, fn string, name string, image string, bindings []types.PortBinding) error
|
||||
Destroy(ctx context.Context, name string) error
|
||||
Update(ctx context.Context, name string) error
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/driver"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/types"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@@ -37,6 +37,11 @@ func Create(kubeconfig string) (*K8S, error) {
|
||||
return &K8S{clientset}, nil
|
||||
}
|
||||
|
||||
// Provision TODO may need support manually k8s cluster creation
|
||||
func (k *K8S) Provision(ctx context.Context, isRemote bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deploy a image to be a service
|
||||
func (k *K8S) Deploy(
|
||||
ctx context.Context,
|
||||
@@ -186,5 +191,5 @@ func (k *K8S) Ping(ctx context.Context) error {
|
||||
}
|
||||
|
||||
var (
|
||||
_ infra.Deployer = &K8S{}
|
||||
_ driver.Driver = &K8S{}
|
||||
)
|
||||
135
driver/mocks/driver.go
Normal file
135
driver/mocks/driver.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: driver.go
|
||||
|
||||
// Package mock_driver is a generated GoMock package.
|
||||
package mock_driver
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
types "github.com/metrue/fx/types"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockDriver is a mock of Driver interface
|
||||
type MockDriver struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockDriverMockRecorder
|
||||
}
|
||||
|
||||
// MockDriverMockRecorder is the mock recorder for MockDriver
|
||||
type MockDriverMockRecorder struct {
|
||||
mock *MockDriver
|
||||
}
|
||||
|
||||
// NewMockDriver creates a new mock instance
|
||||
func NewMockDriver(ctrl *gomock.Controller) *MockDriver {
|
||||
mock := &MockDriver{ctrl: ctrl}
|
||||
mock.recorder = &MockDriverMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockDriver) EXPECT() *MockDriverMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Deploy mocks base method
|
||||
func (m *MockDriver) Deploy(ctx context.Context, fn, name, image string, bindings []types.PortBinding) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Deploy", ctx, fn, name, image, bindings)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Deploy indicates an expected call of Deploy
|
||||
func (mr *MockDriverMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockDriver)(nil).Deploy), ctx, fn, name, image, bindings)
|
||||
}
|
||||
|
||||
// Provision mocks base method
|
||||
func (m *MockDriver) Provision(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Provision", ctx)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Provision indicates an expected call of Provision
|
||||
func (mr *MockDriverMockRecorder) Provision(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provision", reflect.TypeOf((*MockDriver)(nil).Provision), ctx)
|
||||
}
|
||||
|
||||
// Destroy mocks base method
|
||||
func (m *MockDriver) 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 *MockDriverMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockDriver)(nil).Destroy), ctx, name)
|
||||
}
|
||||
|
||||
// Update mocks base method
|
||||
func (m *MockDriver) 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 *MockDriverMockRecorder) Update(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDriver)(nil).Update), ctx, name)
|
||||
}
|
||||
|
||||
// GetStatus mocks base method
|
||||
func (m *MockDriver) 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 *MockDriverMockRecorder) GetStatus(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockDriver)(nil).GetStatus), ctx, name)
|
||||
}
|
||||
|
||||
// List mocks base method
|
||||
func (m *MockDriver) 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 *MockDriverMockRecorder) List(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockDriver)(nil).List), ctx, name)
|
||||
}
|
||||
|
||||
// Ping mocks base method
|
||||
func (m *MockDriver) 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 *MockDriverMockRecorder) Ping(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockDriver)(nil).Ping), ctx)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package infra
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package infra
|
||||
package driver
|
||||
|
||||
// Sudo append sudo when user is not root
|
||||
func Sudo(cmd string, user string) string {
|
||||
7
fx.go
7
fx.go
@@ -135,9 +135,9 @@ func main() {
|
||||
},
|
||||
Action: handle(
|
||||
middlewares.Parse("up"),
|
||||
middlewares.Provision,
|
||||
middlewares.Language(),
|
||||
middlewares.Binding,
|
||||
middlewares.SSH,
|
||||
middlewares.Driver,
|
||||
middlewares.Build,
|
||||
handlers.Up,
|
||||
@@ -170,7 +170,7 @@ func main() {
|
||||
},
|
||||
Action: handle(
|
||||
middlewares.Parse("down"),
|
||||
middlewares.Provision,
|
||||
middlewares.SSH,
|
||||
middlewares.Driver,
|
||||
handlers.Down,
|
||||
),
|
||||
@@ -207,7 +207,7 @@ func main() {
|
||||
},
|
||||
Action: handle(
|
||||
middlewares.Parse("list"),
|
||||
middlewares.Provision,
|
||||
middlewares.SSH,
|
||||
middlewares.Driver,
|
||||
handlers.List,
|
||||
),
|
||||
@@ -260,7 +260,6 @@ func main() {
|
||||
},
|
||||
Action: handle(
|
||||
middlewares.Parse("image_build"),
|
||||
middlewares.Provision,
|
||||
middlewares.Language(),
|
||||
handlers.BuildImage,
|
||||
),
|
||||
|
||||
@@ -2,17 +2,23 @@ package handlers
|
||||
|
||||
import (
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/driver"
|
||||
)
|
||||
|
||||
// Down command handle
|
||||
func Down(ctx context.Contexter) (err error) {
|
||||
services := ctx.Get("services").([]string)
|
||||
runner := ctx.Get("deployer").(infra.Deployer)
|
||||
for _, svc := range services {
|
||||
if err := runner.Destroy(ctx.GetContext(), svc); err != nil {
|
||||
return err
|
||||
for _, targetdriver := range []string{"docker_driver", "k8s_driver"} {
|
||||
driver, ok := ctx.Get(targetdriver).(driver.Driver)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, svc := range services {
|
||||
if err := driver.Destroy(ctx.GetContext(), svc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
mockCtx "github.com/metrue/fx/context/mocks"
|
||||
mockDeployer "github.com/metrue/fx/infra/mocks"
|
||||
mockDeployer "github.com/metrue/fx/driver/mocks"
|
||||
)
|
||||
|
||||
func TestDown(t *testing.T) {
|
||||
@@ -14,13 +14,14 @@ func TestDown(t *testing.T) {
|
||||
defer ctrl.Finish()
|
||||
|
||||
ctx := mockCtx.NewMockContexter(ctrl)
|
||||
deployer := mockDeployer.NewMockDeployer(ctrl)
|
||||
driver := mockDeployer.NewMockDriver(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)
|
||||
ctx.EXPECT().Get("docker_driver").Return(driver)
|
||||
ctx.EXPECT().Get("k8s_driver").Return(driver)
|
||||
ctx.EXPECT().GetContext().Return(context.Background()).Times(2)
|
||||
driver.EXPECT().Destroy(gomock.Any(), services[0]).Return(nil).Times(2)
|
||||
if err := Down(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/driver"
|
||||
"github.com/metrue/fx/pkg/renderrer"
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ func List(ctx context.Contexter) (err error) {
|
||||
format := ctx.Get("format").(string)
|
||||
|
||||
for _, targetdriver := range []string{"docker_driver", "k8s_driver"} {
|
||||
driver, ok := ctx.Get(targetdriver).(infra.Deployer)
|
||||
driver, ok := ctx.Get(targetdriver).(driver.Driver)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package handlers
|
||||
import (
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/driver"
|
||||
"github.com/metrue/fx/pkg/renderrer"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
@@ -23,7 +23,7 @@ func Up(ctx context.Contexter) (err error) {
|
||||
force := ctx.Get("force").(bool)
|
||||
|
||||
for _, targetdriver := range []string{"docker_driver", "k8s_driver"} {
|
||||
driver, ok := ctx.Get(targetdriver).(infra.Deployer)
|
||||
driver, ok := ctx.Get(targetdriver).(driver.Driver)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
mockCtx "github.com/metrue/fx/context/mocks"
|
||||
mockDeployer "github.com/metrue/fx/infra/mocks"
|
||||
mockDeployer "github.com/metrue/fx/driver/mocks"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestUp(t *testing.T) {
|
||||
defer ctrl.Finish()
|
||||
|
||||
ctx := mockCtx.NewMockContexter(ctrl)
|
||||
driver := mockDeployer.NewMockDeployer(ctrl)
|
||||
driver := mockDeployer.NewMockDriver(ctrl)
|
||||
|
||||
bindings := []types.PortBinding{}
|
||||
name := "sample-name"
|
||||
@@ -47,7 +47,7 @@ func TestUp(t *testing.T) {
|
||||
defer ctrl.Finish()
|
||||
|
||||
ctx := mockCtx.NewMockContexter(ctrl)
|
||||
driver := mockDeployer.NewMockDeployer(ctrl)
|
||||
driver := mockDeployer.NewMockDriver(ctrl)
|
||||
|
||||
bindings := []types.PortBinding{}
|
||||
name := "sample-name"
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/go-ssh-client"
|
||||
)
|
||||
|
||||
// Cloud define a docker host
|
||||
type Cloud struct {
|
||||
IP string `json:"ip"`
|
||||
User string `json:"user"`
|
||||
Port string `json:"port"`
|
||||
Type string `json:"type"`
|
||||
KeyFile string `json:"key_file"`
|
||||
|
||||
sshClient ssh.Clienter
|
||||
}
|
||||
|
||||
const sshConnectionTimeout = 10 * time.Second
|
||||
|
||||
// New new a docker cloud
|
||||
func New(ip string, user string, port string, keyfile string) *Cloud {
|
||||
return &Cloud{
|
||||
IP: ip,
|
||||
User: user,
|
||||
Port: port,
|
||||
KeyFile: keyfile,
|
||||
Type: types.CloudTypeDocker,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a docker node
|
||||
func Create(ip string, user string, port string, keyfile string) (*Cloud, error) {
|
||||
sshClient := ssh.New(ip).WithUser(user).WithKey(keyfile).WithPort(port)
|
||||
return &Cloud{
|
||||
IP: ip,
|
||||
User: user,
|
||||
Port: port,
|
||||
Type: types.CloudTypeDocker,
|
||||
KeyFile: keyfile,
|
||||
|
||||
sshClient: sshClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Load a docker node from meta
|
||||
func Load(meta []byte) (*Cloud, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Provision a host
|
||||
func (c *Cloud) Provision() error {
|
||||
if err := c.runCmd(infra.Scripts["docker_version"].(string)); err != nil {
|
||||
if err := c.runCmd(infra.Scripts["install_docker"].(string)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.runCmd(infra.Scripts["start_dockerd"].(string)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.runCmd(infra.Scripts["check_fx_agent"].(string)); err != nil {
|
||||
if err := c.runCmd(infra.Scripts["start_fx_agent"].(string)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsHealth check if cloud is in health
|
||||
func (c *Cloud) IsHealth() (bool, error) {
|
||||
local := c.IP == "127.0.0.1" || c.IP == "localhost"
|
||||
if !local || os.Getenv("CI") != "" {
|
||||
ok, err := c.sshClient.Connectable(sshConnectionTimeout)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not connect to %s@%s:%s via SSH: '%s'", c.User, c.IP, c.Port, err)
|
||||
}
|
||||
if !ok {
|
||||
return false, fmt.Errorf("could not connect to %s@%s:%s via SSH ", c.User, c.IP, c.Port)
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.runCmd(infra.Scripts["check_fx_agent"].(string)); err != nil {
|
||||
if err := c.runCmd(infra.Scripts["start_fx_agent"].(string)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// NOTE only using for unit testing
|
||||
func (c *Cloud) setsshClient(client ssh.Clienter) {
|
||||
c.sshClient = client
|
||||
}
|
||||
|
||||
// nolint:unparam
|
||||
func (c *Cloud) runCmd(script string, options ...ssh.CommandOptions) error {
|
||||
option := ssh.CommandOptions{
|
||||
Timeout: sshConnectionTimeout,
|
||||
}
|
||||
if len(options) >= 1 {
|
||||
option = options[0]
|
||||
}
|
||||
|
||||
local := c.IP == "127.0.0.1" || c.IP == "localhost"
|
||||
if local && os.Getenv("CI") == "" {
|
||||
params := strings.Split(script, " ")
|
||||
if len(params) == 0 {
|
||||
return fmt.Errorf("invalid script: %s", script)
|
||||
}
|
||||
// nolint
|
||||
cmd := exec.Command(params[0], params[1:]...)
|
||||
cmd.Stdout = option.Stdout
|
||||
cmd.Stderr = option.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.sshClient.RunCommand(script, option)
|
||||
}
|
||||
|
||||
var (
|
||||
_ infra.Clouder = &Cloud{}
|
||||
)
|
||||
@@ -1,135 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/go-ssh-client"
|
||||
sshMocks "github.com/metrue/go-ssh-client/mocks"
|
||||
)
|
||||
|
||||
func TestCloudProvision(t *testing.T) {
|
||||
t.Run("FxAgentStarted", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
n := New("127.0.0.1", "fx", "22", "~/.ssh/id_rsa")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n.setsshClient(sshClient)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
if err := n.Provision(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FxAgentNotStarted", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
n := New("127.0.0.1", "fx", "22", "~/.ssh/id_rsa")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n.setsshClient(sshClient)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(fmt.Errorf("no such container"))
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
if err := n.Provision(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DockerNotInstalledAndFxAgentNotStarted", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
n := New("127.0.0.1", "fx", "22", "~/.ssh/id_rsa")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n.setsshClient(sshClient)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(fmt.Errorf("no such command"))
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["install_docker"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["start_dockerd"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(fmt.Errorf("no such container"))
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
if err := n.Provision(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCloudIsHealth(t *testing.T) {
|
||||
t.Run("Connectable", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
n := New("127.0.0.1", "fx", "22", "~/.ssh/id_rsa")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n.setsshClient(sshClient)
|
||||
|
||||
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(false, nil)
|
||||
ok, err := n.IsHealth()
|
||||
if ok {
|
||||
t.Fatalf("should not be healthy")
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("error should not be nil")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FxAgentStarted", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cloud := New("127.0.0.1", "fx", "22", "~/.ssh/id_rsa")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
cloud.setsshClient(sshClient)
|
||||
|
||||
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
ok, err := cloud.IsHealth()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("cloud should be healthy")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FxAgentNotStartedAndStartItOK", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cloud := New("127.0.0.1", "fx", "22", "~/.ssh/id_rsa")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
cloud.setsshClient(sshClient)
|
||||
|
||||
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(fmt.Errorf("fx agent not started"))
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
ok, err := cloud.IsHealth()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("cloud should be healthy")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FxAgentNotStartedAndStartItNotOK", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cloud := New("127.0.0.1", "fx", "22", "~/.ssh/id_rsa")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
cloud.setsshClient(sshClient)
|
||||
|
||||
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(fmt.Errorf("fx agent not started"))
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(fmt.Errorf("fx agent started failed"))
|
||||
ok, err := cloud.IsHealth()
|
||||
if err == nil {
|
||||
t.Fatal("should got failed starting")
|
||||
}
|
||||
if ok {
|
||||
t.Fatalf("cloud should not be healthy")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDocker(t *testing.T) {
|
||||
// ctx := context.Background()
|
||||
// cli, err := CreateClient(ctx)
|
||||
// if err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// name := "helloworld"
|
||||
// bindings := []types.PortBinding{
|
||||
// types.PortBinding{
|
||||
// ServiceBindingPort: 80,
|
||||
// ContainerExposePort: 3000,
|
||||
// },
|
||||
// types.PortBinding{
|
||||
// ServiceBindingPort: 443,
|
||||
// ContainerExposePort: 3000,
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// fn := types.Func{
|
||||
// Language: "node",
|
||||
// Source: `
|
||||
// module.exports = (ctx) => {
|
||||
// ctx.body = 'hello world'
|
||||
// }
|
||||
// `,
|
||||
// }
|
||||
// if err := cli.Deploy(ctx, fn, name, name, bindings); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
//
|
||||
// time.Sleep(1 * time.Second)
|
||||
//
|
||||
// if err := cli.Destroy(ctx, name); err != nil {
|
||||
// t.Fatal(err)
|
||||
// }
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package docker
|
||||
|
||||
import containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
|
||||
// CreateDeployer create a deployer
|
||||
func CreateDeployer(client containerruntimes.ContainerRuntime) (*Deployer, error) {
|
||||
return &Deployer{cli: client}, nil
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: infra.go
|
||||
|
||||
// Package mock_infra is a generated GoMock package.
|
||||
package mock_infra
|
||||
|
||||
import (
|
||||
context "context"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
types "github.com/metrue/fx/types"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockClouder is a mock of Clouder interface
|
||||
type MockClouder struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockClouderMockRecorder
|
||||
}
|
||||
|
||||
// MockClouderMockRecorder is the mock recorder for MockClouder
|
||||
type MockClouderMockRecorder struct {
|
||||
mock *MockClouder
|
||||
}
|
||||
|
||||
// NewMockClouder creates a new mock instance
|
||||
func NewMockClouder(ctrl *gomock.Controller) *MockClouder {
|
||||
mock := &MockClouder{ctrl: ctrl}
|
||||
mock.recorder = &MockClouderMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockClouder) EXPECT() *MockClouderMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Provision mocks base method
|
||||
func (m *MockClouder) Provision() error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Provision")
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Provision indicates an expected call of Provision
|
||||
func (mr *MockClouderMockRecorder) Provision() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provision", reflect.TypeOf((*MockClouder)(nil).Provision))
|
||||
}
|
||||
|
||||
// IsHealth mocks base method
|
||||
func (m *MockClouder) IsHealth() (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "IsHealth")
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// IsHealth indicates an expected call of IsHealth
|
||||
func (mr *MockClouderMockRecorder) IsHealth() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsHealth", reflect.TypeOf((*MockClouder)(nil).IsHealth))
|
||||
}
|
||||
|
||||
// MockDeployer is a mock of Deployer interface
|
||||
type MockDeployer struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockDeployerMockRecorder
|
||||
}
|
||||
|
||||
// MockDeployerMockRecorder is the mock recorder for MockDeployer
|
||||
type MockDeployerMockRecorder struct {
|
||||
mock *MockDeployer
|
||||
}
|
||||
|
||||
// NewMockDeployer creates a new mock instance
|
||||
func NewMockDeployer(ctrl *gomock.Controller) *MockDeployer {
|
||||
mock := &MockDeployer{ctrl: ctrl}
|
||||
mock.recorder = &MockDeployerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockDeployer) EXPECT() *MockDeployerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Deploy mocks base method
|
||||
func (m *MockDeployer) Deploy(ctx context.Context, fn, name, image string, bindings []types.PortBinding) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Deploy", ctx, fn, name, image, bindings)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Deploy indicates an expected call of Deploy
|
||||
func (mr *MockDeployerMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockDeployer)(nil).Deploy), ctx, fn, name, image, bindings)
|
||||
}
|
||||
|
||||
// Destroy mocks base method
|
||||
func (m *MockDeployer) Destroy(ctx context.Context, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Destroy", ctx, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Destroy indicates an expected call of Destroy
|
||||
func (mr *MockDeployerMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockDeployer)(nil).Destroy), ctx, name)
|
||||
}
|
||||
|
||||
// Update mocks base method
|
||||
func (m *MockDeployer) Update(ctx context.Context, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Update", ctx, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Update indicates an expected call of Update
|
||||
func (mr *MockDeployerMockRecorder) Update(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDeployer)(nil).Update), ctx, name)
|
||||
}
|
||||
|
||||
// GetStatus mocks base method
|
||||
func (m *MockDeployer) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetStatus", ctx, name)
|
||||
ret0, _ := ret[0].(types.Service)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetStatus indicates an expected call of GetStatus
|
||||
func (mr *MockDeployerMockRecorder) GetStatus(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockDeployer)(nil).GetStatus), ctx, name)
|
||||
}
|
||||
|
||||
// List mocks base method
|
||||
func (m *MockDeployer) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "List", ctx, name)
|
||||
ret0, _ := ret[0].([]types.Service)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// List indicates an expected call of List
|
||||
func (mr *MockDeployerMockRecorder) List(ctx, name interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockDeployer)(nil).List), ctx, name)
|
||||
}
|
||||
|
||||
// Ping mocks base method
|
||||
func (m *MockDeployer) Ping(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Ping", ctx)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Ping indicates an expected call of Ping
|
||||
func (mr *MockDeployerMockRecorder) Ping(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockDeployer)(nil).Ping), ctx)
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package middlewares
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metrue/fx/bundle"
|
||||
@@ -56,13 +55,8 @@ func Build(ctx context.Contexter) (err error) {
|
||||
}
|
||||
|
||||
if host != "" {
|
||||
addr := strings.Split(host, "@")
|
||||
if len(addr) != 2 {
|
||||
return fmt.Errorf("invalid host information, should be format of <user>@<ip>")
|
||||
}
|
||||
ip := addr[1]
|
||||
// TODO port should be configurable
|
||||
docker, err := dockerHTTP.Create(ip, constants.AgentPort)
|
||||
docker, err := dockerHTTP.Create(host, constants.AgentPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,34 +2,42 @@ package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/metrue/fx/constants"
|
||||
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
|
||||
"github.com/metrue/fx/context"
|
||||
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||
k8sInfra "github.com/metrue/fx/infra/k8s"
|
||||
dockerDriver "github.com/metrue/fx/driver/docker"
|
||||
k8sInfra "github.com/metrue/fx/driver/k8s"
|
||||
"github.com/metrue/fx/provisioners"
|
||||
"github.com/metrue/go-ssh-client"
|
||||
)
|
||||
|
||||
// Driver initialize infrastructure driver
|
||||
func Driver(ctx context.Contexter) (err error) {
|
||||
host := ctx.Get("host").(string)
|
||||
sshClient := ctx.Get("ssh").(ssh.Clienter)
|
||||
kubeconf := ctx.Get("kubeconf").(string)
|
||||
if host != "" {
|
||||
addr := strings.Split(host, "@")
|
||||
if len(addr) != 2 {
|
||||
return fmt.Errorf("invalid host information, should be format of <user>@<ip>")
|
||||
}
|
||||
ip := addr[1]
|
||||
// TODO port should be configurable
|
||||
docker, err := dockerHTTP.Create(ip, constants.AgentPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
docker := dockerHTTP.New(host, constants.AgentPort)
|
||||
driver := dockerDriver.New(dockerDriver.Options{
|
||||
DockerClient: docker,
|
||||
})
|
||||
|
||||
driver, err := dockerInfra.CreateDeployer(docker)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := driver.Ping(ctx.GetContext()); err != nil {
|
||||
log.Infof("provisioning %s ...", host)
|
||||
|
||||
provisioner := provisioners.New(sshClient)
|
||||
isRemote := (host != "127.0.0.1" && host != "localhost")
|
||||
if err := provisioner.Provision(ctx.GetContext(), isRemote); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
if err := docker.Initialize(); err != nil {
|
||||
return fmt.Errorf("initialize docker client failed: %s", err)
|
||||
}
|
||||
ctx.Set("docker_driver", driver)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
mockCtx "github.com/metrue/fx/context/mocks"
|
||||
"github.com/metrue/go-ssh-client"
|
||||
sshMocks "github.com/metrue/go-ssh-client/mocks"
|
||||
)
|
||||
|
||||
func TestDriver(t *testing.T) {
|
||||
// TODO enable
|
||||
t.Skip()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := mockCtx.NewMockContexter(ctrl)
|
||||
@@ -49,11 +51,22 @@ users:
|
||||
|
||||
defer os.Remove(kubeconf.Name())
|
||||
|
||||
ctx.EXPECT().Get("host").Return("root@127.0.0.1")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
sshClient.EXPECT().Connectable(10*time.Second).Return(true, nil).Times(2)
|
||||
sshClient.EXPECT().RunCommand("docker version", ssh.CommandOptions{
|
||||
Timeout: 10 * time.Second,
|
||||
}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand("docker inspect fx-agent", ssh.CommandOptions{
|
||||
Timeout: 10 * time.Second,
|
||||
}).Return(nil)
|
||||
|
||||
cntx := context.Background()
|
||||
ctx.EXPECT().GetContext().Return(cntx).Times(2)
|
||||
ctx.EXPECT().Get("ssh").Return(sshClient)
|
||||
ctx.EXPECT().Get("host").Return("1.2.3.4")
|
||||
ctx.EXPECT().Get("kubeconf").Return(kubeconf.Name())
|
||||
ctx.EXPECT().Set("docker_driver", gomock.Any())
|
||||
ctx.EXPECT().Set("k8s_driver", gomock.Any())
|
||||
if err := Driver(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
// TODO mock http call
|
||||
if err := Driver(ctx); err == nil {
|
||||
t.Fatal("should failed on initial docker client dude to /version api not ready")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package middlewares
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/utils"
|
||||
@@ -15,10 +16,22 @@ type argsField struct {
|
||||
Env string
|
||||
}
|
||||
|
||||
func set(ctx context.Contexter, cli *cli.Context, fields []argsField) {
|
||||
func set(ctx context.Contexter, cli *cli.Context, fields []argsField) error {
|
||||
for _, f := range fields {
|
||||
if f.Type == "string" {
|
||||
ctx.Set(f.Name, cli.String(f.Name))
|
||||
if f.Name == "host" {
|
||||
addr := strings.Split(cli.String(f.Name), "@")
|
||||
if len(addr) != 2 {
|
||||
return fmt.Errorf("invalid host information, should be format of <user>@<ip>")
|
||||
}
|
||||
user := addr[0]
|
||||
ip := addr[1]
|
||||
ctx.Set("host", ip)
|
||||
ctx.Set("user", user)
|
||||
} else {
|
||||
|
||||
ctx.Set(f.Name, cli.String(f.Name))
|
||||
}
|
||||
} else if f.Type == "int" {
|
||||
ctx.Set(f.Name, cli.Int(f.Name))
|
||||
} else if f.Type == "bool" {
|
||||
@@ -29,6 +42,7 @@ func set(ctx context.Contexter, cli *cli.Context, fields []argsField) {
|
||||
ctx.Set(f.Name, os.Getenv(f.Env))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse parse input
|
||||
@@ -54,7 +68,7 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
|
||||
}
|
||||
ctx.Set("deps", deps)
|
||||
|
||||
set(ctx, cli, []argsField{
|
||||
if err := set(ctx, cli, []argsField{
|
||||
argsField{Name: "name", Type: "string"},
|
||||
argsField{Name: "port", Type: "int"},
|
||||
argsField{Name: "force", Type: "bool"},
|
||||
@@ -62,7 +76,9 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
|
||||
argsField{Name: "ssh_key", Type: "string", Env: "SSH_KEY_FILE"},
|
||||
argsField{Name: "host", Type: "string", Env: "FX_HOST"},
|
||||
argsField{Name: "kubeconf", Type: "string", Env: "FX_KUBECONF"},
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "down":
|
||||
services := cli.Args()
|
||||
@@ -75,24 +91,28 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
|
||||
}
|
||||
ctx.Set("services", svc)
|
||||
|
||||
set(ctx, cli, []argsField{
|
||||
if err := set(ctx, cli, []argsField{
|
||||
argsField{Name: "ssh_port", Type: "string", Env: "SSH_PORT"},
|
||||
argsField{Name: "ssh_key", Type: "string", Env: "SSH_KEY_FILE"},
|
||||
argsField{Name: "host", Type: "string", Env: "FX_HOST"},
|
||||
argsField{Name: "kubeconf", Type: "string", Env: "FX_KUBECONF"},
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "list":
|
||||
name := cli.Args().First()
|
||||
ctx.Set("filter", name)
|
||||
format := cli.String("format")
|
||||
ctx.Set("format", format)
|
||||
set(ctx, cli, []argsField{
|
||||
if err := set(ctx, cli, []argsField{
|
||||
argsField{Name: "ssh_port", Type: "string", Env: "SSH_PORT"},
|
||||
argsField{Name: "ssh_key", Type: "string", Env: "SSH_KEY_FILE"},
|
||||
argsField{Name: "host", Type: "string", Env: "FX_HOST"},
|
||||
argsField{Name: "kubeconf", Type: "string", Env: "FX_KUBECONF"},
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "image_build":
|
||||
if !cli.Args().Present() {
|
||||
@@ -111,13 +131,15 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
|
||||
}
|
||||
}
|
||||
ctx.Set("deps", deps)
|
||||
set(ctx, cli, []argsField{
|
||||
if err := set(ctx, cli, []argsField{
|
||||
argsField{Name: "tag", Type: "string"},
|
||||
argsField{Name: "ssh_port", Type: "string", Env: "SSH_PORT"},
|
||||
argsField{Name: "ssh_key", Type: "string", Env: "SSH_KEY_FILE"},
|
||||
argsField{Name: "host", Type: "string", Env: "FX_HOST"},
|
||||
argsField{Name: "kubeconf", Type: "string", Env: "FX_KUBECONF"},
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "image_export":
|
||||
if !cli.Args().Present() {
|
||||
|
||||
@@ -12,6 +12,18 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
type stringValue string
|
||||
|
||||
func (s stringValue) Set(v string) error {
|
||||
// nolint
|
||||
s = stringValue(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s stringValue) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func TestParseUp(t *testing.T) {
|
||||
t.Run("SourceCodeNotReady", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
@@ -32,6 +44,9 @@ func TestParseUp(t *testing.T) {
|
||||
|
||||
ctx := mockCtx.NewMockContexter(ctrl)
|
||||
argset := flag.NewFlagSet("test", 0)
|
||||
host := "127.0.0.1"
|
||||
user := "root"
|
||||
argset.Var(stringValue(user+"@"+host), "host", "host info")
|
||||
cli := cli.NewContext(nil, argset, nil)
|
||||
fd, err := ioutil.TempFile("", "fx_func_*.js")
|
||||
if err != nil {
|
||||
@@ -43,7 +58,8 @@ func TestParseUp(t *testing.T) {
|
||||
ctx.EXPECT().GetCliContext().Return(cli)
|
||||
ctx.EXPECT().Set("fn", fd.Name())
|
||||
ctx.EXPECT().Set("deps", []string{})
|
||||
ctx.EXPECT().Set("host", "")
|
||||
ctx.EXPECT().Set("host", host)
|
||||
ctx.EXPECT().Set("user", user)
|
||||
ctx.EXPECT().Set("ssh_port", "")
|
||||
ctx.EXPECT().Set("ssh_key", "")
|
||||
ctx.EXPECT().Set("kubeconf", "")
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/context"
|
||||
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||
)
|
||||
|
||||
// Provision make sure infrastructure is healthy
|
||||
func Provision(ctx context.Contexter) (err error) {
|
||||
host := ctx.Get("host").(string)
|
||||
port := ctx.Get("ssh_port").(string)
|
||||
keyfile := ctx.Get("ssh_key").(string)
|
||||
kubeconf := ctx.Get("kubeconf").(string)
|
||||
if host == "" && kubeconf == "" {
|
||||
return fmt.Errorf("at least host or kubeconf provided")
|
||||
}
|
||||
|
||||
if host != "" {
|
||||
addr := strings.Split(host, "@")
|
||||
if len(addr) != 2 {
|
||||
return fmt.Errorf("invalid host information, should be format of <user>@<ip>")
|
||||
}
|
||||
user := addr[0]
|
||||
ip := addr[1]
|
||||
|
||||
cloud, err := dockerInfra.Create(ip, user, port, keyfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cloud.Provision(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok, err := cloud.IsHealth()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("target docker host is not healthy")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
mockCtx "github.com/metrue/fx/context/mocks"
|
||||
)
|
||||
|
||||
func TestProvision(t *testing.T) {
|
||||
t.Skip()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
ctx := mockCtx.NewMockContexter(ctrl)
|
||||
|
||||
ctx.EXPECT().Get("host").Return("root@127.0.0.1")
|
||||
ctx.EXPECT().Get("kubeconf").Return("~/.kube/config")
|
||||
ctx.EXPECT().Get("ssh_port").Return("22")
|
||||
ctx.EXPECT().Get("ssh_key").Return("~/.ssh/id_rsa")
|
||||
|
||||
if err := Provision(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
17
middlewares/ssh.go
Normal file
17
middlewares/ssh.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/go-ssh-client"
|
||||
)
|
||||
|
||||
// SSH create a ssh client
|
||||
func SSH(ctx context.Contexter) error {
|
||||
host := ctx.Get("host").(string)
|
||||
user := ctx.Get("user").(string)
|
||||
port := ctx.Get("ssh_port").(string)
|
||||
keyfile := ctx.Get("ssh_key").(string)
|
||||
sshClient := ssh.New(host).WithUser(user).WithPort(port).WithKey(keyfile)
|
||||
ctx.Set("ssh", sshClient)
|
||||
return nil
|
||||
}
|
||||
90
provisioners/docker.go
Normal file
90
provisioners/docker.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package provisioners
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metrue/go-ssh-client"
|
||||
)
|
||||
|
||||
const sshConnectionTimeout = 10 * time.Second
|
||||
|
||||
var scripts = map[string]interface{}{
|
||||
"docker_version": "docker version",
|
||||
"install_docker": "curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-18.06.3-ce.tgz -o docker.tgz && tar zxvf docker.tgz && mv docker/* /usr/bin && rm -rf docker docker.tgz",
|
||||
"start_dockerd": "dockerd >/dev/null 2>&1 & sleep 2",
|
||||
"check_fx_agent": "docker inspect fx-agent",
|
||||
"start_fx_agent": "docker run -d --name=fx-agent --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:8866:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock",
|
||||
}
|
||||
|
||||
// Docker define a fx docker host
|
||||
type Docker struct {
|
||||
sshClient ssh.Clienter
|
||||
}
|
||||
|
||||
// New a docker provioner
|
||||
func New(sshClient ssh.Clienter) *Docker {
|
||||
return &Docker{
|
||||
sshClient: sshClient,
|
||||
}
|
||||
}
|
||||
|
||||
// Provision a host
|
||||
func (d *Docker) Provision(ctx context.Context, isRemote bool) error {
|
||||
if err := d.runCmd(scripts["docker_version"].(string), isRemote); err != nil {
|
||||
if err := d.runCmd(scripts["install_docker"].(string), isRemote); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := d.runCmd(scripts["start_dockerd"].(string), isRemote); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.runCmd(scripts["check_fx_agent"].(string), isRemote); err != nil {
|
||||
if err := d.runCmd(scripts["start_fx_agent"].(string), isRemote); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Docker) runCmd(script string, isRemote bool, options ...ssh.CommandOptions) error {
|
||||
option := ssh.CommandOptions{
|
||||
Timeout: sshConnectionTimeout,
|
||||
}
|
||||
if len(options) >= 1 {
|
||||
option = options[0]
|
||||
}
|
||||
if !isRemote {
|
||||
params := strings.Split(script, " ")
|
||||
if len(params) == 0 {
|
||||
return fmt.Errorf("invalid script: %s", script)
|
||||
}
|
||||
// nolint
|
||||
cmd := exec.Command(params[0], params[1:]...)
|
||||
cmd.Stdout = option.Stdout
|
||||
cmd.Stderr = option.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
ok, err := d.sshClient.Connectable(sshConnectionTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not connect via SSH: '%s'", err)
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("could not connect via SSH")
|
||||
}
|
||||
|
||||
return d.sshClient.RunCommand(script, option)
|
||||
}
|
||||
|
||||
var (
|
||||
_ Provisioner = &Docker{}
|
||||
)
|
||||
124
provisioners/docker_test.go
Normal file
124
provisioners/docker_test.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package provisioners
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/metrue/go-ssh-client"
|
||||
sshMocks "github.com/metrue/go-ssh-client/mocks"
|
||||
)
|
||||
|
||||
func TestDriverProvision(t *testing.T) {
|
||||
t.Run("SSHConnectError", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n := &Docker{sshClient: sshClient}
|
||||
err := errors.New("could not connect to host")
|
||||
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(false, err).AnyTimes()
|
||||
if err := n.Provision(context.Background(), true); err == nil {
|
||||
t.Fatalf("should get error when SSH connection not ok")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SSHConnectionNotOK", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n := New(sshClient)
|
||||
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(false, nil).AnyTimes()
|
||||
if err := n.Provision(context.Background(), true); err == nil {
|
||||
t.Fatalf("should get error when SSH connection not ok")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DockerAndFxAgentOK", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n := New(sshClient)
|
||||
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil).AnyTimes()
|
||||
sshClient.EXPECT().RunCommand(scripts["docker_version"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(scripts["check_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
if err := n.Provision(context.Background(), true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DockerNotReady", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n := New(sshClient)
|
||||
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil).AnyTimes()
|
||||
err := errors.New("docker command not found")
|
||||
sshClient.EXPECT().RunCommand(scripts["docker_version"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(err)
|
||||
sshClient.EXPECT().RunCommand(scripts["install_docker"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(scripts["start_dockerd"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(scripts["check_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
if err := n.Provision(context.Background(), true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("FxAgentNotReady", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n := New(sshClient)
|
||||
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil).AnyTimes()
|
||||
err := errors.New("fx agent not found")
|
||||
sshClient.EXPECT().RunCommand(scripts["docker_version"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(scripts["check_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(err)
|
||||
sshClient.EXPECT().RunCommand(scripts["start_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
if err := n.Provision(context.Background(), true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("DockerAndFxAgentNotReady", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n := New(sshClient)
|
||||
|
||||
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil).AnyTimes()
|
||||
err2 := errors.New("fx agent not found")
|
||||
err1 := errors.New("docker command not found")
|
||||
sshClient.EXPECT().RunCommand(scripts["docker_version"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(err1)
|
||||
sshClient.EXPECT().RunCommand(scripts["install_docker"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(scripts["start_dockerd"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(scripts["check_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(err2)
|
||||
sshClient.EXPECT().RunCommand(scripts["start_fx_agent"].(string), ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
|
||||
if err := n.Provision(context.Background(), true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunCommand(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n := &Docker{
|
||||
sshClient: sshClient,
|
||||
}
|
||||
script := "script"
|
||||
option := ssh.CommandOptions{
|
||||
Timeout: sshConnectionTimeout,
|
||||
}
|
||||
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil)
|
||||
sshClient.EXPECT().RunCommand(script, option).Return(nil)
|
||||
if err := n.runCmd(script, true, option); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
8
provisioners/provisioner.go
Normal file
8
provisioners/provisioner.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package provisioners
|
||||
|
||||
import "context"
|
||||
|
||||
// Provisioner define provisioner interface
|
||||
type Provisioner interface {
|
||||
Provision(ctx context.Context, isRemote bool) error
|
||||
}
|
||||
Reference in New Issue
Block a user