Compare commits
9 Commits
0.8.6-alph
...
0.8.73
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2298f39cca | ||
|
|
23d68bc27b | ||
|
|
74c0423f0d | ||
|
|
06f87c4d8e | ||
|
|
35262de828 | ||
|
|
34a495984c | ||
|
|
d7130c4e28 | ||
|
|
c9630a53c3 | ||
|
|
0522690472 |
2
Makefile
2
Makefile
@@ -32,7 +32,7 @@ cli-test-ci:
|
|||||||
./scripts/test_cli.sh 'js'
|
./scripts/test_cli.sh 'js'
|
||||||
|
|
||||||
cli-test:
|
cli-test:
|
||||||
./scripts/test_cli.sh 'js rb py go php java d'
|
./scripts/test_cli.sh 'js rb py go php java d rs'
|
||||||
|
|
||||||
http-test:
|
http-test:
|
||||||
./scripts/http_test.sh
|
./scripts/http_test.sh
|
||||||
|
|||||||
89
README.md
89
README.md
@@ -1,5 +1,6 @@
|
|||||||
fx
|
fx
|
||||||
------
|
------
|
||||||
|
|
||||||
Poor man's function as a service.
|
Poor man's function as a service.
|
||||||
<br/>
|
<br/>
|
||||||

|

|
||||||
@@ -13,13 +14,12 @@ Poor man's function as a service.
|
|||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
|
- [Manage Infrastructure](#manage-infrastructure)
|
||||||
- [Contribute](#contribute)
|
- [Contribute](#contribute)
|
||||||
|
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
fx is a tool to help you do Function as a Service on your own server, fx can make your stateless function a service in seconds, both Docker host and Kubernetes cluster supported. The most exciting thing is that you can write your functions with most programming languages.
|
fx is a tool to help you do Function as a Service on your own server, fx can make your stateless function a service in seconds, both Docker host and Kubernetes cluster supported. The most exciting thing is that you can write your functions with most programming languages.
|
||||||
|
|
||||||
Feel free hacking fx to support the languages not listed. Welcome to tweet me [@_metrue](https://twitter.com/_metrue) on Twitter, [@metrue](https://www.weibo.com/u/2165714507) on Weibo.
|
Feel free hacking fx to support the languages not listed. Welcome to tweet me [@_metrue](https://twitter.com/_metrue) on Twitter, [@metrue](https://www.weibo.com/u/2165714507) on Weibo.
|
||||||
@@ -79,7 +79,7 @@ USAGE:
|
|||||||
fx [global options] command [command options] [arguments...]
|
fx [global options] command [command options] [arguments...]
|
||||||
|
|
||||||
VERSION:
|
VERSION:
|
||||||
0.8.4
|
0.8.7
|
||||||
|
|
||||||
COMMANDS:
|
COMMANDS:
|
||||||
infra manage infrastructure
|
infra manage infrastructure
|
||||||
@@ -96,44 +96,36 @@ GLOBAL OPTIONS:
|
|||||||
--version, -v print the version
|
--version, -v print the version
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Write a function
|
### Deploy your function to Docker
|
||||||
|
|
||||||
You can check out [examples](https://github.com/metrue/fx/tree/master/examples/functions) for reference. Let's write a function as an example, it calculates the sum of two numbers then returns:
|
|
||||||
|
|
||||||
```js
|
|
||||||
module.exports = (ctx) => {
|
|
||||||
ctx.body = 'hello world'
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
Then save it to a file `func.js`.
|
$ fx up --name hello-fx ./examples/functions/JavaScript/func.js
|
||||||
|
|
||||||
2. Deploy your function as a service
|
+------------------------------------------------------------------+-----------+---------------+
|
||||||
|
| ID | NAME | ENDPOINT |
|
||||||
Give your service a port with `--port`, and name with `--name`, heath checking with `--healthcheck` if you want.
|
+------------------------------------------------------------------+-----------+---------------+
|
||||||
|
| 5b24d36608ee392c937a61a530805f74551ddec304aea3aca2ffa0fabcf98cf3 | /hello-fx | 0.0.0.0:58328 |
|
||||||
```shell
|
+------------------------------------------------------------------+-----------+---------------+
|
||||||
$ fx up -name fx_service_name -p 10001 --healthcheck func.js
|
|
||||||
|
|
||||||
2019/08/10 13:26:37 info Pack Service: ✓
|
|
||||||
2019/08/10 13:26:39 info Build Service: ✓
|
|
||||||
2019/08/10 13:26:39 info Run Service: ✓
|
|
||||||
2019/08/10 13:26:39 info Service (fx_service_name) is running on: 0.0.0.0:10001
|
|
||||||
2019/08/10 13:26:39 info up function fx_service_name(func.js) to machine localhost: ✓
|
|
||||||
```
|
```
|
||||||
|
|
||||||
if you want see what the source code of your service looks like, you can export it into a dirctory,
|
### Deploy your function to Kubernetes
|
||||||
|
|
||||||
```shell
|
```
|
||||||
$ fx image export -o <path of dir> func.js
|
$ KUBECONFIG=~/.kube/config ./build/fx up examples/functions/JavaScript/func.js --name hello-fx
|
||||||
2019/09/25 19:31:19 info exported to <path of dir>: ✓
|
|
||||||
|
+-------------------------------+------+----------------+
|
||||||
|
| ID | NAME | ENDPOINT |
|
||||||
|
+----+--------------------------+-----------------------+
|
||||||
|
| 5b24d36608ee392c937a | hello-fx | 10.0.242.75:80 |
|
||||||
|
+------------------------+-------------+----------------+
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Test your service
|
### Test your service
|
||||||
|
|
||||||
then you can test your service:
|
then you can test your service:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ curl -v 0.0.0.0:10001
|
$ curl -v 0.0.0.0:58328
|
||||||
|
|
||||||
|
|
||||||
GET / HTTP/1.1
|
GET / HTTP/1.1
|
||||||
@@ -155,39 +147,32 @@ hello world
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker
|
## Manage Infrastructure
|
||||||
|
|
||||||
**fx** is originally designed to turn a function into a runnable Docker container in a easiest way, on a host with Docker running, you can just deploy your function with `fx up` command,
|
**fx** is originally designed to turn a function into a runnable Docker container in a easiest way, on a host with Docker running, you can just deploy your function with `fx up` command, and now **fx** supports deploy function to be a service onto Kubernetes cluster infrasture, and we encourage you to do that other than on bare Docker environment, there are lots of advantage to run your function on Kubernetes like self-healing, load balancing, easy horizontal scaling, etc. It's pretty simple to deploy your function onto Kubernetes with **fx**, you just set KUBECONFIG in your enviroment.
|
||||||
|
|
||||||
|
By default. **fx** use localhost as target infrastructure to run your service, and you can also setup your remote virtual machines as **fx**'s infrastructure and deploy your functions onto it.
|
||||||
|
|
||||||
|
### `fx infra create`
|
||||||
|
|
||||||
|
You can create types (docker and k8s) of infrastructures for **fx** to deploy functions
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
fx up --name hello-svc --port 7777 hello.js # onto localhost
|
$ fx infra create --name infra_us --type docker --host <user>@<ip> ## create docker type infrasture on <ip>
|
||||||
DOCKER_REMOTE_HOST_ADDR=xx.xx.xx.xx DOCKER_REMOTE_HOST_USER=xxxx DOCKER_REMOTE_HOST_PASSWORD=xxxx fx up --name hello-svc --port 7777 hello.js # onto remote host
|
$ fx infra create --name infra_bj --type k8s --master <user>@<ip> --agents '<user1>@<ip1>,<user2>@<ip2>' ## create k8s type infrasture use <ip> as master node, and <ip1> and <ip2> as agents nodes
|
||||||
```
|
```
|
||||||
|
|
||||||
## Kubernetes
|
### `fx infra use`
|
||||||
|
|
||||||
**fx** supports deploy function to be a service onto Kubernetes cluster infrasture, and we encourage you to do that other than on bare Docker environment, there are lots of advantage to run your function on Kubernetes like self-healing, load balancing, easy horizontal scaling, etc. It's pretty simple to deploy your function onto Kubernetes with **fx**, you just set KUBECONFIG in your enviroment.
|
To use a infrastructure, you can use `fx infra use` command to activate it.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
KUBECONFIG=<Your KUBECONFIG> fx deploy -n fx-service-abc_js -p 12349 examples/functions/JavaScript/func.js # function will be deploy to your Kubernetes cluster and expose a IP address of your loadbalencer
|
fx infra use <infrastructure name>
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
and you can list your infrastructure with `fx infra list`
|
||||||
|
|
||||||
```shell
|
## Use Public Cloud Kubernetes Service as infrastructure to run your functions
|
||||||
$ export KUBECONFIG=<Your KUBECONFIG>
|
|
||||||
$ fx deploy -n fx-service-abc_js -p 12349 examples/functions/JavaScript/func.js # function will be deploy to your Kubernetes cluster and expose a IP address of your loadbalencer
|
|
||||||
```
|
|
||||||
|
|
||||||
* Local Kubernetes Cluster
|
|
||||||
|
|
||||||
Docker for Mac and Docker for Windows already support Kubernetes with single node cluster, we can use it directly, and the default `KUBECONFIG` is `~/.kube/config`.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ export KUBECONFIG=~/.kube/config # then fx will take the config to deloy function
|
|
||||||
```
|
|
||||||
|
|
||||||
if you have multiple Kubernetes clusters configured, you have to set context correctly. FYI [configure-access-multiple-clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/)
|
|
||||||
|
|
||||||
* Azure Kubernetes Service (AKS)
|
* Azure Kubernetes Service (AKS)
|
||||||
|
|
||||||
@@ -224,8 +209,6 @@ But we would suggest you run `kubectl config current-context` to check if the cu
|
|||||||
|
|
||||||
* Setup your own Kubernetes cluster
|
* Setup your own Kubernetes cluster
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
fx infra create --type k3s --name fx-cluster-1 --master root@123.11.2.3 --agents 'root@1.1.1.1,root@2.2.2.2'
|
fx infra create --type k3s --name fx-cluster-1 --master root@123.11.2.3 --agents 'root@1.1.1.1,root@2.2.2.2'
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func (c *Config) AddK8SCloud(name string, kubeconfig []byte) error {
|
|||||||
|
|
||||||
cloud := map[string]string{
|
cloud := map[string]string{
|
||||||
"type": "k8s",
|
"type": "k8s",
|
||||||
"kubeConfig": kubecfg,
|
"kubeconfig": kubecfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.addCloud(name, cloud)
|
return c.addCloud(name, cloud)
|
||||||
|
|||||||
@@ -37,27 +37,29 @@ type API struct {
|
|||||||
|
|
||||||
// Create a API
|
// Create a API
|
||||||
func Create(host string, port string) (*API, error) {
|
func Create(host string, port string) (*API, error) {
|
||||||
version, err := utils.DockerVersion(host, port)
|
addr := host + ":" + port
|
||||||
|
v, err := version(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, version)
|
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, v)
|
||||||
return &API{
|
return &API{
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
version: version,
|
version: v,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustCreate a api object, panic if not
|
// MustCreate a api object, panic if not
|
||||||
func MustCreate(host string, port string) *API {
|
func MustCreate(host string, port string) *API {
|
||||||
version, err := utils.DockerVersion(host, port)
|
addr := host + ":" + port
|
||||||
|
v, err := version(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, version)
|
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, v)
|
||||||
return &API{
|
return &API{
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
version: version,
|
version: v,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +133,11 @@ func (api *API) post(path string, body []byte, expectStatus int, v interface{})
|
|||||||
|
|
||||||
// Version get version of docker engine
|
// Version get version of docker engine
|
||||||
func (api *API) Version(ctx context.Context) (string, error) {
|
func (api *API) Version(ctx context.Context) (string, error) {
|
||||||
path := api.endpoint + "/version"
|
return version(api.endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func version(endpoint string) (string, error) {
|
||||||
|
path := endpoint + "/version"
|
||||||
if !strings.HasPrefix(path, "http") {
|
if !strings.HasPrefix(path, "http") {
|
||||||
path = "http://" + path
|
path = "http://" + path
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +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) (types.Service, error)
|
|
||||||
List(ctx context.Context, name string) ([]types.Service, error)
|
|
||||||
Ping(ctx context.Context) error
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: deployer.go
|
|
||||||
|
|
||||||
// Package mock_deploy is a generated GoMock package.
|
|
||||||
package mock_deploy
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
|
||||||
types "github.com/metrue/fx/types"
|
|
||||||
reflect "reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockDeployer is a mock of Deployer interface
|
|
||||||
type MockDeployer struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockDeployerMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockDeployerMockRecorder is the mock recorder for MockDeployer
|
|
||||||
type MockDeployerMockRecorder struct {
|
|
||||||
mock *MockDeployer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockDeployer creates a new mock instance
|
|
||||||
func NewMockDeployer(ctrl *gomock.Controller) *MockDeployer {
|
|
||||||
mock := &MockDeployer{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockDeployerMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
|
||||||
func (m *MockDeployer) EXPECT() *MockDeployerMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deploy mocks base method
|
|
||||||
func (m *MockDeployer) Deploy(ctx context.Context, fn types.Func, name, image string, bindings []types.PortBinding) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Deploy", ctx, fn, name, image, bindings)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deploy indicates an expected call of Deploy
|
|
||||||
func (mr *MockDeployerMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockDeployer)(nil).Deploy), ctx, fn, name, image, bindings)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy mocks base method
|
|
||||||
func (m *MockDeployer) Destroy(ctx context.Context, name string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Destroy", ctx, name)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy indicates an expected call of Destroy
|
|
||||||
func (mr *MockDeployerMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockDeployer)(nil).Destroy), ctx, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update mocks base method
|
|
||||||
func (m *MockDeployer) Update(ctx context.Context, name string) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Update", ctx, name)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update indicates an expected call of Update
|
|
||||||
func (mr *MockDeployerMockRecorder) Update(ctx, name interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDeployer)(nil).Update), ctx, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStatus mocks base method
|
|
||||||
func (m *MockDeployer) GetStatus(ctx context.Context, name string) (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)
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 294 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB |
47
examples/functions/Rust/README.md
vendored
Normal file
47
examples/functions/Rust/README.md
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Make a Rust function a service with fx
|
||||||
|
|
||||||
|
Write a function like,
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub mod fns {
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct Response {
|
||||||
|
pub result: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Request {
|
||||||
|
pub a: i32,
|
||||||
|
pub b: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn func(req: Request) -> Response {
|
||||||
|
Response {
|
||||||
|
result: req.a + req.b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
then deploy it with `fx up` command,
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ fx up -p 8080 func.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
test it using `curl`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ curl -X 'POST' --header 'Content-Type: application/json' --data '{"a":1,"b":1}' '0.0.0.0:3000'
|
||||||
|
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Length: 12
|
||||||
|
Content-Type: application/json
|
||||||
|
Date: Fri, 06 Dec 2019 06:45:14 GMT
|
||||||
|
Server: Rocket
|
||||||
|
|
||||||
|
{
|
||||||
|
"result": 2
|
||||||
|
}
|
||||||
|
```
|
||||||
14
fx.go
14
fx.go
@@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
const version = "0.8.6"
|
const version = "0.8.73"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
go checkForUpdate()
|
go checkForUpdate()
|
||||||
@@ -156,7 +156,7 @@ func main() {
|
|||||||
},
|
},
|
||||||
Action: handle(
|
Action: handle(
|
||||||
middlewares.LoadConfig,
|
middlewares.LoadConfig,
|
||||||
middlewares.Setup,
|
middlewares.Provision,
|
||||||
middlewares.Parse("up"),
|
middlewares.Parse("up"),
|
||||||
middlewares.Binding,
|
middlewares.Binding,
|
||||||
middlewares.Build,
|
middlewares.Build,
|
||||||
@@ -170,7 +170,7 @@ func main() {
|
|||||||
Action: handle(
|
Action: handle(
|
||||||
middlewares.Parse("down"),
|
middlewares.Parse("down"),
|
||||||
middlewares.LoadConfig,
|
middlewares.LoadConfig,
|
||||||
middlewares.Setup,
|
middlewares.Provision,
|
||||||
handlers.Down,
|
handlers.Down,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -181,7 +181,7 @@ func main() {
|
|||||||
Action: handle(
|
Action: handle(
|
||||||
middlewares.Parse("list"),
|
middlewares.Parse("list"),
|
||||||
middlewares.LoadConfig,
|
middlewares.LoadConfig,
|
||||||
middlewares.Setup,
|
middlewares.Provision,
|
||||||
handlers.List,
|
handlers.List,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -211,7 +211,8 @@ func main() {
|
|||||||
},
|
},
|
||||||
Action: handle(
|
Action: handle(
|
||||||
middlewares.LoadConfig,
|
middlewares.LoadConfig,
|
||||||
middlewares.Setup,
|
middlewares.Provision,
|
||||||
|
middlewares.Parse("image_build"),
|
||||||
handlers.BuildImage,
|
handlers.BuildImage,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -226,7 +227,8 @@ func main() {
|
|||||||
},
|
},
|
||||||
Action: handle(
|
Action: handle(
|
||||||
middlewares.LoadConfig,
|
middlewares.LoadConfig,
|
||||||
middlewares.Setup,
|
middlewares.Provision,
|
||||||
|
middlewares.Parse("image_export"),
|
||||||
handlers.ExportImage,
|
handlers.ExportImage,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -27,9 +27,10 @@ require (
|
|||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/nwaples/rardecode v1.0.0 // indirect
|
github.com/nwaples/rardecode v1.0.0 // indirect
|
||||||
github.com/olekukonko/tablewriter v0.0.3
|
github.com/olekukonko/tablewriter v0.0.4
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
|
github.com/otiai10/copy v1.0.2
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
|
||||||
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf // indirect
|
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf // indirect
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -163,6 +163,8 @@ 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-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 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
|
||||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||||
|
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b h1:JGD0sJ44XzhsT1voOg00zji4ubuMNcVNK3m7d9GI88k=
|
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b h1:JGD0sJ44XzhsT1voOg00zji4ubuMNcVNK3m7d9GI88k=
|
||||||
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b/go.mod h1:ERHOEBrDy6+8vfoJjjmhdmBpOzdvvP7bLtwYTTK6LOs=
|
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b/go.mod h1:ERHOEBrDy6+8vfoJjjmhdmBpOzdvvP7bLtwYTTK6LOs=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
@@ -187,6 +189,8 @@ github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMB
|
|||||||
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
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 h1:i0LBnzgiChAWHJYTQAZJDOgf8MNxAVYZJ2m63SIDimI=
|
||||||
github.com/olekukonko/tablewriter v0.0.3/go.mod h1:YZeBtGzYYEsCHp2LST/u/0NDwGkRoBtmn1cIWCJiS6M=
|
github.com/olekukonko/tablewriter v0.0.3/go.mod h1:YZeBtGzYYEsCHp2LST/u/0NDwGkRoBtmn1cIWCJiS6M=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo 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.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
@@ -196,6 +200,10 @@ github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2i
|
|||||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
|
github.com/otiai10/copy v1.0.2 h1:DDNipYy6RkIkjMwy+AWzgKiNTyj2RUI9yEMeETEpVyc=
|
||||||
|
github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY=
|
||||||
|
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||||
|
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
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 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||||
|
|||||||
@@ -1,43 +1,11 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/apex/log"
|
|
||||||
"github.com/metrue/fx/context"
|
"github.com/metrue/fx/context"
|
||||||
"github.com/metrue/fx/packer"
|
|
||||||
"github.com/metrue/fx/types"
|
|
||||||
"github.com/metrue/fx/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Call command handle
|
// Call command handle
|
||||||
func Call(ctx context.Contexter) error {
|
func Call(ctx context.Contexter) error {
|
||||||
cli := ctx.GetCliContext()
|
|
||||||
_ = strings.Join(cli.Args()[1:], " ")
|
|
||||||
|
|
||||||
file := cli.Args().First()
|
|
||||||
src, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Read Source: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Info("Read Source: \u2713")
|
|
||||||
|
|
||||||
lang := utils.GetLangFromFileName(file)
|
|
||||||
fn := types.Func{
|
|
||||||
Language: lang,
|
|
||||||
Source: string(src),
|
|
||||||
}
|
|
||||||
if _, err := packer.Pack(file, fn); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO not supported
|
// TODO not supported
|
||||||
// if err := api.MustCreate(host.Host, constants.AgentPort).
|
|
||||||
// Call(file, params, project); err != nil {
|
|
||||||
// log.Fatalf("call functions on machine %s with %v failed: %v", name, params, err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/metrue/fx/context"
|
"github.com/metrue/fx/context"
|
||||||
"github.com/metrue/fx/deploy"
|
"github.com/metrue/fx/infra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Down command handle
|
// Down command handle
|
||||||
func Down(ctx context.Contexter) (err error) {
|
func Down(ctx context.Contexter) (err error) {
|
||||||
services := ctx.Get("services").([]string)
|
services := ctx.Get("services").([]string)
|
||||||
runner := ctx.Get("deployer").(deploy.Deployer)
|
runner := ctx.Get("deployer").(infra.Deployer)
|
||||||
for _, svc := range services {
|
for _, svc := range services {
|
||||||
if err := runner.Destroy(ctx.GetContext(), svc); err != nil {
|
if err := runner.Destroy(ctx.GetContext(), svc); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
mockCtx "github.com/metrue/fx/context/mocks"
|
mockCtx "github.com/metrue/fx/context/mocks"
|
||||||
mockDeployer "github.com/metrue/fx/deploy/mocks"
|
mockDeployer "github.com/metrue/fx/infra/mocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDown(t *testing.T) {
|
func TestDown(t *testing.T) {
|
||||||
|
|||||||
@@ -2,80 +2,74 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/metrue/fx/constants"
|
"github.com/metrue/fx/constants"
|
||||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||||
"github.com/metrue/fx/context"
|
"github.com/metrue/fx/context"
|
||||||
"github.com/metrue/fx/packer"
|
"github.com/metrue/fx/packer"
|
||||||
"github.com/metrue/fx/types"
|
"github.com/metrue/fx/pkg/spinner"
|
||||||
"github.com/metrue/fx/utils"
|
"github.com/metrue/fx/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/otiai10/copy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BuildImage build image
|
// BuildImage build image
|
||||||
func BuildImage(ctx context.Contexter) error {
|
func BuildImage(ctx context.Contexter) (err error) {
|
||||||
cli := ctx.GetCliContext()
|
spinner.Start("building")
|
||||||
funcFile := cli.Args().First()
|
defer func() {
|
||||||
tag := cli.String("tag")
|
spinner.Stop("building", err)
|
||||||
if tag == "" {
|
}()
|
||||||
tag = uuid.New().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
|
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
|
||||||
defer os.RemoveAll(workdir)
|
defer os.RemoveAll(workdir)
|
||||||
|
|
||||||
body, err := ioutil.ReadFile(funcFile)
|
sources := ctx.Get("sources").([]string)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("function code load failed: %v", err)
|
if len(sources) == 0 {
|
||||||
return err
|
return fmt.Errorf("source file/directory of function required")
|
||||||
}
|
}
|
||||||
log.Infof("function code loaded: %v", constants.CheckedSymbol)
|
if len(sources) == 1 &&
|
||||||
lang := utils.GetLangFromFileName(funcFile)
|
utils.IsDir(sources[0]) &&
|
||||||
|
utils.HasDockerfile(sources[0]) {
|
||||||
fn := types.Func{Language: lang, Source: string(body)}
|
if err := copy.Copy(sources[0], workdir); err != nil {
|
||||||
|
return err
|
||||||
if err := packer.PackIntoDir(fn, workdir); err != nil {
|
}
|
||||||
log.Fatalf("could not pack function %v: %v", fn, err)
|
} else {
|
||||||
return err
|
if err := packer.Pack(workdir, sources...); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
docker, ok := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
|
||||||
if ok {
|
|
||||||
nameWithTag := tag + ":latest"
|
|
||||||
if err := docker.BuildImage(ctx.GetContext(), workdir, nameWithTag); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Infof("image built: %v", constants.CheckedSymbol)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return fmt.Errorf("no available docker cli")
|
|
||||||
|
docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
||||||
|
nameWithTag := ctx.Get("tag").(string) + ":latest"
|
||||||
|
if err := docker.BuildImage(ctx.GetContext(), workdir, nameWithTag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infof("image built: %s %v", nameWithTag, constants.CheckedSymbol)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportImage export service's code into a directory
|
// ExportImage export service's code into a directory
|
||||||
func ExportImage(ctx context.Contexter) (err error) {
|
func ExportImage(ctx context.Contexter) (err error) {
|
||||||
cli := ctx.GetCliContext()
|
outputDir := ctx.Get("output").(string)
|
||||||
funcFile := cli.Args().First()
|
sources := ctx.Get("sources").([]string)
|
||||||
outputDir := cli.String("output")
|
|
||||||
if outputDir == "" {
|
if len(sources) == 0 {
|
||||||
log.Fatalf("output directory required")
|
return fmt.Errorf("source file/directory of function required")
|
||||||
return nil
|
}
|
||||||
|
if len(sources) == 1 &&
|
||||||
|
utils.IsDir(sources[0]) &&
|
||||||
|
utils.HasDockerfile(sources[0]) {
|
||||||
|
if err := copy.Copy(sources[0], outputDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := packer.Pack(outputDir, sources...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadFile(funcFile)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "read source failed")
|
|
||||||
}
|
|
||||||
lang := utils.GetLangFromFileName(funcFile)
|
|
||||||
|
|
||||||
if err := packer.PackIntoDir(types.Func{Language: lang, Source: string(body)}, outputDir); err != nil {
|
|
||||||
log.Fatalf("write source code to file failed: %v", constants.UncheckedSymbol)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Infof("exported to %v: %v", outputDir, constants.CheckedSymbol)
|
log.Infof("exported to %v: %v", outputDir, constants.CheckedSymbol)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ import (
|
|||||||
|
|
||||||
"github.com/metrue/fx/config"
|
"github.com/metrue/fx/config"
|
||||||
"github.com/metrue/fx/context"
|
"github.com/metrue/fx/context"
|
||||||
"github.com/metrue/fx/infra/docker"
|
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||||
"github.com/metrue/fx/infra/k3s"
|
"github.com/metrue/fx/infra/k8s"
|
||||||
"github.com/metrue/fx/pkg/spinner"
|
"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, "@")
|
info := strings.Split(masterInfo, "@")
|
||||||
if len(info) != 2 {
|
if len(info) != 2 {
|
||||||
return nil, fmt.Errorf("incorrect master info, should be <user>@<ip> format")
|
return nil, fmt.Errorf("incorrect master info, should be <user>@<ip> format")
|
||||||
}
|
}
|
||||||
master := k3s.MasterNode{
|
master := k8s.MasterNode{
|
||||||
User: info[0],
|
User: info[0],
|
||||||
IP: info[1],
|
IP: info[1],
|
||||||
}
|
}
|
||||||
agents := []k3s.AgentNode{}
|
agents := []k8s.AgentNode{}
|
||||||
if agentsInfo != "" {
|
if agentsInfo != "" {
|
||||||
agentsInfoList := strings.Split(agentsInfo, ",")
|
agentsInfoList := strings.Split(agentsInfo, ",")
|
||||||
for _, agent := range agentsInfoList {
|
for _, agent := range agentsInfoList {
|
||||||
@@ -28,16 +28,15 @@ func setupK3S(masterInfo string, agentsInfo string) ([]byte, error) {
|
|||||||
if len(info) != 2 {
|
if len(info) != 2 {
|
||||||
return nil, fmt.Errorf("incorrect agent info, should be <user>@<ip> format")
|
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],
|
User: info[0],
|
||||||
IP: info[1],
|
IP: info[1],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(master, agents, len(agents))
|
k8sOperator := k8s.New(master, agents)
|
||||||
k3sOperator := k3s.New(master, agents)
|
return k8sOperator.Provision()
|
||||||
return k3sOperator.Provision()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupDocker(hostInfo string) ([]byte, error) {
|
func setupDocker(hostInfo string) ([]byte, error) {
|
||||||
@@ -47,7 +46,7 @@ func setupDocker(hostInfo string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
user := info[1]
|
user := info[1]
|
||||||
host := info[0]
|
host := info[0]
|
||||||
dockr := docker.New(user, host)
|
dockr := dockerInfra.CreateProvisioner(user, host)
|
||||||
return dockr.Provision()
|
return dockr.Provision()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,20 +68,19 @@ func Setup(ctx context.Contexter) (err error) {
|
|||||||
if cli.String("host") == "" {
|
if cli.String("host") == "" {
|
||||||
return fmt.Errorf("host required, eg. 'root@123.1.2.12'")
|
return fmt.Errorf("host required, eg. 'root@123.1.2.12'")
|
||||||
}
|
}
|
||||||
} else if typ == "k3s" {
|
} else if typ == "k8s" {
|
||||||
if cli.String("master") == "" {
|
if cli.String("master") == "" {
|
||||||
return fmt.Errorf("master required, eg. 'root@123.1.2.12'")
|
return fmt.Errorf("master required, eg. 'root@123.1.2.12'")
|
||||||
}
|
}
|
||||||
} else if typ == "k8s" {
|
|
||||||
} else {
|
} 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)
|
fxConfig := ctx.Get("config").(*config.Config)
|
||||||
|
|
||||||
switch strings.ToLower(typ) {
|
switch strings.ToLower(typ) {
|
||||||
case "k3s":
|
case "k8s":
|
||||||
kubeconf, err := setupK3S(cli.String("master"), cli.String("agents"))
|
kubeconf, err := setupK8S(cli.String("master"), cli.String("agents"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -93,8 +91,6 @@ func Setup(ctx context.Contexter) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return fxConfig.AddDockerCloud(name, config)
|
return fxConfig.AddDockerCloud(name, config)
|
||||||
case "k8s":
|
|
||||||
fmt.Println("WIP")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,14 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/metrue/fx/context"
|
"github.com/metrue/fx/context"
|
||||||
"github.com/metrue/fx/deploy"
|
"github.com/metrue/fx/infra"
|
||||||
"github.com/metrue/fx/pkg/render"
|
"github.com/metrue/fx/pkg/render"
|
||||||
"github.com/metrue/fx/pkg/spinner"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// List command handle
|
// List command handle
|
||||||
func List(ctx context.Contexter) (err error) {
|
func List(ctx context.Contexter) (err error) {
|
||||||
const task = "deploying"
|
|
||||||
spinner.Start(task)
|
|
||||||
defer func() {
|
|
||||||
spinner.Stop(task, err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
cli := ctx.GetCliContext()
|
cli := ctx.GetCliContext()
|
||||||
deployer := ctx.Get("deployer").(deploy.Deployer)
|
deployer := ctx.Get("deployer").(infra.Deployer)
|
||||||
|
|
||||||
services, err := deployer.List(ctx.GetContext(), cli.Args().First())
|
services, err := deployer.List(ctx.GetContext(), cli.Args().First())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,17 +2,23 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/metrue/fx/context"
|
"github.com/metrue/fx/context"
|
||||||
"github.com/metrue/fx/deploy"
|
"github.com/metrue/fx/infra"
|
||||||
"github.com/metrue/fx/pkg/render"
|
"github.com/metrue/fx/pkg/render"
|
||||||
"github.com/metrue/fx/types"
|
"github.com/metrue/fx/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Up command handle
|
// Up command handle
|
||||||
func Up(ctx context.Contexter) (err error) {
|
func Up(ctx context.Contexter) (err error) {
|
||||||
fn := ctx.Get("fn").(types.Func)
|
fn, ok := ctx.Get("data").(string)
|
||||||
image := ctx.Get("image").(string)
|
if !ok {
|
||||||
|
fn = ""
|
||||||
|
}
|
||||||
|
image, ok := ctx.Get("image").(string)
|
||||||
|
if !ok {
|
||||||
|
image = ""
|
||||||
|
}
|
||||||
name := ctx.Get("name").(string)
|
name := ctx.Get("name").(string)
|
||||||
deployer := ctx.Get("deployer").(deploy.Deployer)
|
deployer := ctx.Get("deployer").(infra.Deployer)
|
||||||
bindings := ctx.Get("bindings").([]types.PortBinding)
|
bindings := ctx.Get("bindings").([]types.PortBinding)
|
||||||
|
|
||||||
if err := deployer.Deploy(
|
if err := deployer.Deploy(
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ import (
|
|||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
mockCtx "github.com/metrue/fx/context/mocks"
|
mockCtx "github.com/metrue/fx/context/mocks"
|
||||||
mockDeployer "github.com/metrue/fx/deploy/mocks"
|
mockDeployer "github.com/metrue/fx/infra/mocks"
|
||||||
"github.com/metrue/fx/types"
|
"github.com/metrue/fx/types"
|
||||||
fxTypes "github.com/metrue/fx/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUp(t *testing.T) {
|
func TestUp(t *testing.T) {
|
||||||
@@ -18,17 +17,17 @@ func TestUp(t *testing.T) {
|
|||||||
ctx := mockCtx.NewMockContexter(ctrl)
|
ctx := mockCtx.NewMockContexter(ctrl)
|
||||||
deployer := mockDeployer.NewMockDeployer(ctrl)
|
deployer := mockDeployer.NewMockDeployer(ctrl)
|
||||||
|
|
||||||
fn := fxTypes.Func{}
|
|
||||||
bindings := []types.PortBinding{}
|
bindings := []types.PortBinding{}
|
||||||
name := "sample-name"
|
name := "sample-name"
|
||||||
image := "sample-image"
|
image := "sample-image"
|
||||||
ctx.EXPECT().Get("fn").Return(fn)
|
data := "sample-data"
|
||||||
ctx.EXPECT().Get("name").Return(name)
|
ctx.EXPECT().Get("name").Return(name)
|
||||||
ctx.EXPECT().Get("image").Return(image)
|
ctx.EXPECT().Get("image").Return(image)
|
||||||
ctx.EXPECT().Get("deployer").Return(deployer)
|
ctx.EXPECT().Get("deployer").Return(deployer)
|
||||||
ctx.EXPECT().Get("bindings").Return(bindings)
|
ctx.EXPECT().Get("bindings").Return(bindings)
|
||||||
|
ctx.EXPECT().Get("data").Return(data)
|
||||||
ctx.EXPECT().GetContext().Return(context.Background()).Times(2)
|
ctx.EXPECT().GetContext().Return(context.Background()).Times(2)
|
||||||
deployer.EXPECT().Deploy(gomock.Any(), fn, name, image, bindings).Return(nil)
|
deployer.EXPECT().Deploy(gomock.Any(), data, name, image, bindings).Return(nil)
|
||||||
deployer.EXPECT().GetStatus(gomock.Any(), name).Return(types.Service{
|
deployer.EXPECT().GetStatus(gomock.Any(), name).Return(types.Service{
|
||||||
ID: "id-1",
|
ID: "id-1",
|
||||||
Name: name,
|
Name: name,
|
||||||
|
|||||||
@@ -6,23 +6,23 @@ import (
|
|||||||
|
|
||||||
dockerTypes "github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||||
"github.com/metrue/fx/deploy"
|
"github.com/metrue/fx/infra"
|
||||||
"github.com/metrue/fx/pkg/spinner"
|
"github.com/metrue/fx/pkg/spinner"
|
||||||
"github.com/metrue/fx/types"
|
"github.com/metrue/fx/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Docker manage container
|
// Deployer manage container
|
||||||
type Docker struct {
|
type Deployer struct {
|
||||||
cli containerruntimes.ContainerRuntime
|
cli containerruntimes.ContainerRuntime
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateClient create a docker instance
|
// CreateClient create a docker instance
|
||||||
func CreateClient(client containerruntimes.ContainerRuntime) (d *Docker, err error) {
|
func CreateClient(client containerruntimes.ContainerRuntime) (d *Deployer, err error) {
|
||||||
return &Docker{cli: client}, nil
|
return &Deployer{cli: client}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deploy create a Docker container from given image, and bind the constants.FxContainerExposePort to given port
|
// 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) (err error) {
|
func (d *Deployer) Deploy(ctx context.Context, fn string, name string, image string, ports []types.PortBinding) (err error) {
|
||||||
spinner.Start("deploying " + name)
|
spinner.Start("deploying " + name)
|
||||||
defer func() {
|
defer func() {
|
||||||
spinner.Stop("deploying "+name, err)
|
spinner.Stop("deploying "+name, err)
|
||||||
@@ -31,12 +31,12 @@ func (d *Docker) Deploy(ctx context.Context, fn types.Func, name string, image s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update a container
|
// Update a container
|
||||||
func (d *Docker) Update(ctx context.Context, name string) error {
|
func (d *Deployer) Update(ctx context.Context, name string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy stop and remove container
|
// Destroy stop and remove container
|
||||||
func (d *Docker) Destroy(ctx context.Context, name string) (err error) {
|
func (d *Deployer) Destroy(ctx context.Context, name string) (err error) {
|
||||||
spinner.Start("destroying " + name)
|
spinner.Start("destroying " + name)
|
||||||
defer func() {
|
defer func() {
|
||||||
spinner.Stop("destroying "+name, err)
|
spinner.Stop("destroying "+name, err)
|
||||||
@@ -45,7 +45,7 @@ func (d *Docker) Destroy(ctx context.Context, name string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetStatus get a service status
|
// GetStatus get a service status
|
||||||
func (d *Docker) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
func (d *Deployer) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||||
var container dockerTypes.ContainerJSON
|
var container dockerTypes.ContainerJSON
|
||||||
if err := d.cli.InspectContainer(ctx, name, &container); err != nil {
|
if err := d.cli.InspectContainer(ctx, name, &container); err != nil {
|
||||||
return types.Service{}, err
|
return types.Service{}, err
|
||||||
@@ -77,7 +77,7 @@ func (d *Docker) GetStatus(ctx context.Context, name string) (types.Service, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ping check healty status of infra
|
// Ping check healty status of infra
|
||||||
func (d *Docker) Ping(ctx context.Context) error {
|
func (d *Deployer) Ping(ctx context.Context) error {
|
||||||
if _, err := d.cli.Version(ctx); err != nil {
|
if _, err := d.cli.Version(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -85,11 +85,17 @@ func (d *Docker) Ping(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List services
|
// List services
|
||||||
func (d *Docker) List(ctx context.Context, name string) ([]types.Service, error) {
|
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
|
// FIXME support remote host
|
||||||
return d.cli.ListContainer(ctx, name)
|
return d.cli.ListContainer(ctx, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ deploy.Deployer = &Docker{}
|
_ infra.Deployer = &Deployer{}
|
||||||
)
|
)
|
||||||
@@ -1,171 +1,13 @@
|
|||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/metrue/fx/constants"
|
// CreateProvisioner create a provisioner
|
||||||
"github.com/metrue/fx/infra"
|
func CreateProvisioner(ip string, user string) *Provisioner {
|
||||||
sshOperator "github.com/metrue/go-ssh-client"
|
return NewProvisioner(ip, user)
|
||||||
)
|
|
||||||
|
|
||||||
// Docker docker host
|
|
||||||
type Docker struct {
|
|
||||||
IP string
|
|
||||||
User string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New new a docker object
|
// CreateDeployer create a deployer
|
||||||
func New(ip string, user string) *Docker {
|
func CreateDeployer(client containerruntimes.ContainerRuntime) (*Deployer, error) {
|
||||||
return &Docker{
|
return &Deployer{cli: client}, nil
|
||||||
IP: ip,
|
|
||||||
User: user,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provision provision a host, install docker and start dockerd
|
|
||||||
func (d *Docker) Provision() ([]byte, error) {
|
|
||||||
// TODO clean up, skip check localhost or not if in CICD env
|
|
||||||
if d.isLocalHost() {
|
|
||||||
if os.Getenv("CICD") == "" {
|
|
||||||
if !d.hasDocker() {
|
|
||||||
return nil, fmt.Errorf("please make sure docker installed and running")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := d.StartFxAgentLocally(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config, _ := json.Marshal(map[string]string{
|
|
||||||
"ip": d.IP,
|
|
||||||
"user": d.User,
|
|
||||||
})
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := d.Install(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := d.StartDockerd(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := d.StartFxAgent(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config, _ := json.Marshal(map[string]string{
|
|
||||||
"ip": d.IP,
|
|
||||||
"user": d.User,
|
|
||||||
})
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Docker) isLocalHost() bool {
|
|
||||||
return strings.ToLower(d.IP) == "localhost" || d.IP == "127.0.0.1"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Docker) hasDocker() bool {
|
|
||||||
cmd := exec.Command("docker", "version")
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// HealthCheck check healthy status of host
|
|
||||||
func (d *Docker) HealthCheck() (bool, error) {
|
|
||||||
// TODO
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install docker on host
|
|
||||||
func (d *Docker) Install() error {
|
|
||||||
sudo := ""
|
|
||||||
if d.User != "root" {
|
|
||||||
sudo = "sudo"
|
|
||||||
}
|
|
||||||
installCmd := fmt.Sprintf("curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-18.06.3-ce.tgz -o docker.tgz && tar zxvf docker.tgz && %s mv docker/* /usr/bin && rm -rf docker docker.tgz", sudo)
|
|
||||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
|
||||||
sshPort := infra.GetSSHPort()
|
|
||||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
|
||||||
if err := ssh.RunCommand(installCmd, sshOperator.CommandOptions{
|
|
||||||
Stdout: os.Stdout,
|
|
||||||
Stdin: os.Stdin,
|
|
||||||
Stderr: os.Stderr,
|
|
||||||
}); err != nil {
|
|
||||||
fmt.Println("install docker failed \n================")
|
|
||||||
fmt.Println(err)
|
|
||||||
fmt.Println("================")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartDockerd start dockerd
|
|
||||||
func (d *Docker) StartDockerd() error {
|
|
||||||
sudo := ""
|
|
||||||
if d.User != "root" {
|
|
||||||
sudo = "sudo"
|
|
||||||
}
|
|
||||||
installCmd := fmt.Sprintf("%s dockerd >/dev/null 2>&1 & sleep 2", sudo)
|
|
||||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
|
||||||
sshPort := infra.GetSSHPort()
|
|
||||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
|
||||||
if err := ssh.RunCommand(installCmd, sshOperator.CommandOptions{
|
|
||||||
Stdout: os.Stdout,
|
|
||||||
Stdin: os.Stdin,
|
|
||||||
Stderr: os.Stderr,
|
|
||||||
}); err != nil {
|
|
||||||
fmt.Println("start dockerd failed \n================")
|
|
||||||
fmt.Println(err)
|
|
||||||
fmt.Println("================")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartFxAgent start fx agent
|
|
||||||
func (d *Docker) StartFxAgent() error {
|
|
||||||
startCmd := fmt.Sprintf("sleep 3 && docker stop %s || true && docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentContainerName, constants.AgentPort)
|
|
||||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
|
||||||
sshPort := infra.GetSSHPort()
|
|
||||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
|
||||||
if err := ssh.RunCommand(startCmd, sshOperator.CommandOptions{
|
|
||||||
Stdout: os.Stdout,
|
|
||||||
Stdin: os.Stdin,
|
|
||||||
Stderr: os.Stderr,
|
|
||||||
}); err != nil {
|
|
||||||
fmt.Println("start fx agent failed \n================")
|
|
||||||
fmt.Println(err)
|
|
||||||
fmt.Println("================")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartFxAgentLocally start fx agent
|
|
||||||
func (d *Docker) StartFxAgentLocally() error {
|
|
||||||
startCmd := fmt.Sprintf("docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentPort)
|
|
||||||
params := strings.Split(startCmd, " ")
|
|
||||||
fmt.Println(params)
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
if len(params) > 1 {
|
|
||||||
// nolint: gosec
|
|
||||||
cmd = exec.Command(params[0], params[1:]...)
|
|
||||||
} else {
|
|
||||||
// nolint: gosec
|
|
||||||
cmd = exec.Command(params[0])
|
|
||||||
}
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
fmt.Println(string(out))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ infra.Infra = &Docker{}
|
|
||||||
|
|||||||
217
infra/docker/provisioner.go
Normal file
217
infra/docker/provisioner.go
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/metrue/fx/constants"
|
||||||
|
"github.com/metrue/fx/infra"
|
||||||
|
"github.com/metrue/fx/pkg/spinner"
|
||||||
|
sshOperator "github.com/metrue/go-ssh-client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provisioner docker host
|
||||||
|
type Provisioner struct {
|
||||||
|
IP string
|
||||||
|
User string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProvisioner new a docker object
|
||||||
|
func NewProvisioner(ip string, user string) *Provisioner {
|
||||||
|
return &Provisioner{
|
||||||
|
IP: ip,
|
||||||
|
User: user,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision provision a host, install docker and start dockerd
|
||||||
|
func (d *Provisioner) Provision() (config []byte, err error) {
|
||||||
|
spinner.Start("provisioning")
|
||||||
|
defer func() {
|
||||||
|
spinner.Stop("provisioning", err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// TODO clean up, skip check localhost or not if in CICD env
|
||||||
|
if os.Getenv("CICD") != "" {
|
||||||
|
if err := d.Install(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := d.StartDockerd(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := d.StartFxAgent(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config, _ := json.Marshal(map[string]string{
|
||||||
|
"ip": d.IP,
|
||||||
|
"user": d.User,
|
||||||
|
})
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.isLocalHost() {
|
||||||
|
if !d.hasDocker() {
|
||||||
|
return nil, fmt.Errorf("please make sure docker installed and running")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.StartFxAgentLocally(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config, _ := json.Marshal(map[string]string{
|
||||||
|
"ip": d.IP,
|
||||||
|
"user": d.User,
|
||||||
|
})
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := d.Install(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := d.StartDockerd(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := d.StartFxAgent(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(map[string]string{
|
||||||
|
"ip": d.IP,
|
||||||
|
"user": d.User,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Provisioner) isLocalHost() bool {
|
||||||
|
return strings.ToLower(d.IP) == "localhost" || d.IP == "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Provisioner) hasDocker() bool {
|
||||||
|
cmd := exec.Command("docker", "version")
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthCheck check healthy status of host
|
||||||
|
func (d *Provisioner) HealthCheck() (bool, error) {
|
||||||
|
if d.isLocalHost() {
|
||||||
|
return d.IfFxAgentRunningLocally(), nil
|
||||||
|
}
|
||||||
|
return d.IfFxAgentRunning(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install docker on host
|
||||||
|
func (d *Provisioner) Install() error {
|
||||||
|
sudo := ""
|
||||||
|
if d.User != "root" {
|
||||||
|
sudo = "sudo"
|
||||||
|
}
|
||||||
|
installCmd := fmt.Sprintf("curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-18.06.3-ce.tgz -o docker.tgz && tar zxvf docker.tgz && %s mv docker/* /usr/bin && rm -rf docker docker.tgz", sudo)
|
||||||
|
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||||
|
sshPort := infra.GetSSHPort()
|
||||||
|
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||||
|
if err := ssh.RunCommand(installCmd, sshOperator.CommandOptions{
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stdin: os.Stdin,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}); err != nil {
|
||||||
|
fmt.Println("install docker failed \n================")
|
||||||
|
fmt.Println(err)
|
||||||
|
fmt.Println("================")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartDockerd start dockerd
|
||||||
|
func (d *Provisioner) StartDockerd() error {
|
||||||
|
sudo := ""
|
||||||
|
if d.User != "root" {
|
||||||
|
sudo = "sudo"
|
||||||
|
}
|
||||||
|
installCmd := fmt.Sprintf("%s dockerd >/dev/null 2>&1 & sleep 2", sudo)
|
||||||
|
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||||
|
sshPort := infra.GetSSHPort()
|
||||||
|
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||||
|
if err := ssh.RunCommand(installCmd, sshOperator.CommandOptions{
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stdin: os.Stdin,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}); err != nil {
|
||||||
|
fmt.Println("start dockerd failed \n================")
|
||||||
|
fmt.Println(err)
|
||||||
|
fmt.Println("================")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartFxAgent start fx agent
|
||||||
|
func (d *Provisioner) StartFxAgent() error {
|
||||||
|
startCmd := fmt.Sprintf("sleep 3 && docker stop %s || true && docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentContainerName, constants.AgentPort)
|
||||||
|
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||||
|
sshPort := infra.GetSSHPort()
|
||||||
|
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||||
|
if err := ssh.RunCommand(startCmd, sshOperator.CommandOptions{
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stdin: os.Stdin,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}); err != nil {
|
||||||
|
fmt.Println("start fx agent failed \n================")
|
||||||
|
fmt.Println(err)
|
||||||
|
fmt.Println("================")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartFxAgentLocally start fx agent
|
||||||
|
func (d *Provisioner) StartFxAgentLocally() error {
|
||||||
|
startCmd := fmt.Sprintf("docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentPort)
|
||||||
|
params := strings.Split(startCmd, " ")
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if len(params) > 1 {
|
||||||
|
// nolint: gosec
|
||||||
|
cmd = exec.Command(params[0], params[1:]...)
|
||||||
|
} else {
|
||||||
|
// nolint: gosec
|
||||||
|
cmd = exec.Command(params[0])
|
||||||
|
}
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
fmt.Println(string(out))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfFxAgentRunningLocally check if fx agent is running
|
||||||
|
func (d *Provisioner) IfFxAgentRunningLocally() bool {
|
||||||
|
cmd := exec.Command("docker", "inspect", "fx-agent")
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfFxAgentRunning check if fx agent is running
|
||||||
|
func (d *Provisioner) IfFxAgentRunning() bool {
|
||||||
|
inspectCmd := infra.Sudo("docker inspect fx-agent", d.User)
|
||||||
|
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||||
|
sshPort := infra.GetSSHPort()
|
||||||
|
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||||
|
if err := ssh.RunCommand(inspectCmd, sshOperator.CommandOptions{
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
Stdin: os.Stdin,
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
}); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ infra.Provisioner = &Provisioner{}
|
||||||
@@ -5,12 +5,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDocker(t *testing.T) {
|
func TestProvisioner(t *testing.T) {
|
||||||
if os.Getenv("DOCKER_HOST") == "" ||
|
if os.Getenv("DOCKER_HOST") == "" ||
|
||||||
os.Getenv("DOCKER_USER") == "" {
|
os.Getenv("DOCKER_USER") == "" {
|
||||||
t.Skip("skip test since DOCKER_HOST and DOCKER_USER not ready")
|
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 {
|
if err := d.Install(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,29 @@
|
|||||||
package infra
|
package infra
|
||||||
|
|
||||||
// Infra infrastructure provision interface
|
import (
|
||||||
type Infra interface {
|
"context"
|
||||||
|
|
||||||
|
"github.com/metrue/fx/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provisioner provision interface
|
||||||
|
type Provisioner interface {
|
||||||
Provision() (config []byte, err error)
|
Provision() (config []byte, err error)
|
||||||
HealthCheck() (bool, error)
|
HealthCheck() (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deployer deploy interface
|
||||||
|
type Deployer interface {
|
||||||
|
Deploy(ctx context.Context, fn string, name string, image string, bindings []types.PortBinding) error
|
||||||
|
Destroy(ctx context.Context, name string) error
|
||||||
|
Update(ctx context.Context, name string) error
|
||||||
|
GetStatus(ctx context.Context, name string) (types.Service, error)
|
||||||
|
List(ctx context.Context, name string) ([]types.Service, error)
|
||||||
|
Ping(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infra infrastructure provision interface
|
||||||
|
type Infra interface {
|
||||||
|
Provisioner
|
||||||
|
Deployer
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metrue/fx/packer"
|
||||||
"github.com/metrue/fx/types"
|
"github.com/metrue/fx/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,16 +32,12 @@ func TestK8SDeployer(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := types.Func{
|
data, err := packer.PackIntoK8SConfigMapFile("./fixture")
|
||||||
Language: "node",
|
if err != nil {
|
||||||
Source: `
|
t.Fatal(err)
|
||||||
module.exports = (ctx) => {
|
|
||||||
ctx.body = 'hello world'
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := k8s.Deploy(ctx, fn, name, name, bindings); err != nil {
|
if err := k8s.Deploy(ctx, data, name, name, bindings); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"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"
|
"github.com/metrue/fx/types"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
@@ -40,18 +40,13 @@ func Create(kubeconfig string) (*K8S, error) {
|
|||||||
// Deploy a image to be a service
|
// Deploy a image to be a service
|
||||||
func (k *K8S) Deploy(
|
func (k *K8S) Deploy(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
fn types.Func,
|
fn string,
|
||||||
name string,
|
name string,
|
||||||
image string,
|
image string,
|
||||||
ports []types.PortBinding,
|
ports []types.PortBinding,
|
||||||
) error {
|
) error {
|
||||||
// put source code of function docker project into k8s config map
|
|
||||||
tree, err := packer.PackIntoK8SConfigMapFile(fn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := map[string]string{}
|
data := map[string]string{}
|
||||||
data[ConfigMap.AppMetaEnvName] = tree
|
data[ConfigMap.AppMetaEnvName] = fn
|
||||||
if _, err := k.CreateOrUpdateConfigMap(namespace, name, data); err != nil {
|
if _, err := k.CreateOrUpdateConfigMap(namespace, name, data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -168,7 +163,12 @@ func (k *K8S) GetStatus(ctx context.Context, name string) (types.Service, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List services
|
// 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
|
return []types.Service{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,5 +186,5 @@ func (k *K8S) Ping(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
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)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package k3s
|
package k8s
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -23,8 +23,8 @@ type AgentNode struct {
|
|||||||
User string
|
User string
|
||||||
}
|
}
|
||||||
|
|
||||||
// K3S k3s operator
|
// Provisioner k3s operator
|
||||||
type K3S struct {
|
type Provisioner struct {
|
||||||
master MasterNode
|
master MasterNode
|
||||||
agents []AgentNode
|
agents []AgentNode
|
||||||
}
|
}
|
||||||
@@ -34,15 +34,15 @@ type K3S struct {
|
|||||||
const version = "v0.9.1"
|
const version = "v0.9.1"
|
||||||
|
|
||||||
// New new a operator
|
// New new a operator
|
||||||
func New(master MasterNode, agents []AgentNode) *K3S {
|
func New(master MasterNode, agents []AgentNode) *Provisioner {
|
||||||
return &K3S{
|
return &Provisioner{
|
||||||
master: master,
|
master: master,
|
||||||
agents: agents,
|
agents: agents,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provision provision k3s cluster
|
// Provision provision k3s cluster
|
||||||
func (k *K3S) Provision() ([]byte, error) {
|
func (k *Provisioner) Provision() ([]byte, error) {
|
||||||
if err := k.SetupMaster(); err != nil {
|
if err := k.SetupMaster(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -53,17 +53,17 @@ func (k *K3S) Provision() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HealthCheck check healthy status of host
|
// HealthCheck check healthy status of host
|
||||||
func (k *K3S) HealthCheck() (bool, error) {
|
func (k *Provisioner) HealthCheck() (bool, error) {
|
||||||
// TODO
|
// TODO
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupMaster setup master node
|
// SetupMaster setup master node
|
||||||
func (k *K3S) SetupMaster() error {
|
func (k *Provisioner) SetupMaster() error {
|
||||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||||
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
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)
|
installCmd := fmt.Sprintf("curl -sLS https://get.k3s.io | INSTALL_K3S_EXEC='server --docker --tls-san %s' INSTALL_K3S_VERSION='%s' sh -", k.master.IP, version)
|
||||||
if err := ssh.RunCommand(infra.Sudo(installCmd, k.master.IP), sshOperator.CommandOptions{
|
if err := ssh.RunCommand(infra.Sudo(installCmd, k.master.User), sshOperator.CommandOptions{
|
||||||
Stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
Stdin: os.Stdin,
|
Stdin: os.Stdin,
|
||||||
Stderr: os.Stderr,
|
Stderr: os.Stderr,
|
||||||
@@ -75,12 +75,12 @@ func (k *K3S) SetupMaster() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *K3S) getToken() (string, error) {
|
func (k *Provisioner) getToken() (string, error) {
|
||||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||||
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
||||||
script := "cat /var/lib/rancher/k3s/server/node-token"
|
script := "cat /var/lib/rancher/k3s/server/node-token"
|
||||||
var outPipe bytes.Buffer
|
var outPipe bytes.Buffer
|
||||||
if err := ssh.RunCommand(infra.Sudo(script, k.master.IP), sshOperator.CommandOptions{
|
if err := ssh.RunCommand(infra.Sudo(script, k.master.User), sshOperator.CommandOptions{
|
||||||
Stdout: bufio.NewWriter(&outPipe),
|
Stdout: bufio.NewWriter(&outPipe),
|
||||||
Stdin: os.Stdin,
|
Stdin: os.Stdin,
|
||||||
Stderr: os.Stderr,
|
Stderr: os.Stderr,
|
||||||
@@ -91,13 +91,13 @@ func (k *K3S) getToken() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetupAgent set agent node
|
// SetupAgent set agent node
|
||||||
func (k *K3S) SetupAgent() error {
|
func (k *Provisioner) SetupAgent() error {
|
||||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||||
tok, err := k.getToken()
|
tok, err := k.getToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
const k3sExtraArgs = ""
|
const k3sExtraArgs = "--docker"
|
||||||
joinCmd := fmt.Sprintf("curl -fL https://get.k3s.io/ | K3S_URL='https://%s:6443' K3S_TOKEN='%s' INSTALL_K3S_VERSION='%s' sh -s - %s", k.master.IP, tok, version, k3sExtraArgs)
|
joinCmd := fmt.Sprintf("curl -fL https://get.k3s.io/ | K3S_URL='https://%s:6443' K3S_TOKEN='%s' INSTALL_K3S_VERSION='%s' sh -s - %s", k.master.IP, tok, version, k3sExtraArgs)
|
||||||
for _, agent := range k.agents {
|
for _, agent := range k.agents {
|
||||||
ssh := sshOperator.New(agent.IP).WithUser(agent.User).WithKey(sshKeyFile)
|
ssh := sshOperator.New(agent.IP).WithUser(agent.User).WithKey(sshKeyFile)
|
||||||
@@ -117,13 +117,13 @@ func (k *K3S) SetupAgent() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetKubeConfig get kubeconfig of k3s cluster
|
// GetKubeConfig get kubeconfig of k3s cluster
|
||||||
func (k *K3S) GetKubeConfig() ([]byte, error) {
|
func (k *Provisioner) GetKubeConfig() ([]byte, error) {
|
||||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||||
var config []byte
|
var config []byte
|
||||||
getConfigCmd := "cat /etc/rancher/k3s/k3s.yaml\n"
|
getConfigCmd := "cat /etc/rancher/k3s/k3s.yaml\n"
|
||||||
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
||||||
var outPipe bytes.Buffer
|
var outPipe bytes.Buffer
|
||||||
if err := ssh.RunCommand(infra.Sudo(getConfigCmd, k.master.IP), sshOperator.CommandOptions{
|
if err := ssh.RunCommand(infra.Sudo(getConfigCmd, k.master.User), sshOperator.CommandOptions{
|
||||||
Stdout: bufio.NewWriter(&outPipe),
|
Stdout: bufio.NewWriter(&outPipe),
|
||||||
Stdin: os.Stdin,
|
Stdin: os.Stdin,
|
||||||
Stderr: os.Stderr,
|
Stderr: os.Stderr,
|
||||||
@@ -138,6 +138,7 @@ func (k *K3S) GetKubeConfig() ([]byte, error) {
|
|||||||
|
|
||||||
func rewriteKubeconfig(kubeconfig string, ip string, context string) []byte {
|
func rewriteKubeconfig(kubeconfig string, ip string, context string) []byte {
|
||||||
if context == "" {
|
if context == "" {
|
||||||
|
// nolint
|
||||||
context = "default"
|
context = "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,4 +151,4 @@ func rewriteKubeconfig(kubeconfig string, ip string, context string) []byte {
|
|||||||
return []byte(kubeconfigReplacer.Replace(kubeconfig))
|
return []byte(kubeconfigReplacer.Replace(kubeconfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ infra.Infra = &K3S{}
|
var _ infra.Provisioner = &Provisioner{}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package k3s
|
package k8s
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestK3S(t *testing.T) {
|
func TestProvisioner(t *testing.T) {
|
||||||
if os.Getenv("K3S_MASTER_IP") == "" ||
|
if os.Getenv("K3S_MASTER_IP") == "" ||
|
||||||
os.Getenv("K3S_MASTER_USER") == "" ||
|
os.Getenv("K3S_MASTER_USER") == "" ||
|
||||||
os.Getenv("K3S_AGENT_IP") == "" ||
|
os.Getenv("K3S_AGENT_IP") == "" ||
|
||||||
@@ -5,10 +5,174 @@
|
|||||||
package mock_infra
|
package mock_infra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
context "context"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
types "github.com/metrue/fx/types"
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MockProvisioner is a mock of Provisioner interface
|
||||||
|
type MockProvisioner struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockProvisionerMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockProvisionerMockRecorder is the mock recorder for MockProvisioner
|
||||||
|
type MockProvisionerMockRecorder struct {
|
||||||
|
mock *MockProvisioner
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockProvisioner creates a new mock instance
|
||||||
|
func NewMockProvisioner(ctrl *gomock.Controller) *MockProvisioner {
|
||||||
|
mock := &MockProvisioner{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockProvisionerMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockProvisioner) EXPECT() *MockProvisionerMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision mocks base method
|
||||||
|
func (m *MockProvisioner) Provision() ([]byte, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Provision")
|
||||||
|
ret0, _ := ret[0].([]byte)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision indicates an expected call of Provision
|
||||||
|
func (mr *MockProvisionerMockRecorder) Provision() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provision", reflect.TypeOf((*MockProvisioner)(nil).Provision))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthCheck mocks base method
|
||||||
|
func (m *MockProvisioner) HealthCheck() (bool, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "HealthCheck")
|
||||||
|
ret0, _ := ret[0].(bool)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthCheck indicates an expected call of HealthCheck
|
||||||
|
func (mr *MockProvisionerMockRecorder) HealthCheck() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockProvisioner)(nil).HealthCheck))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockDeployer is a mock of Deployer interface
|
||||||
|
type MockDeployer struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockDeployerMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockDeployerMockRecorder is the mock recorder for MockDeployer
|
||||||
|
type MockDeployerMockRecorder struct {
|
||||||
|
mock *MockDeployer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockDeployer creates a new mock instance
|
||||||
|
func NewMockDeployer(ctrl *gomock.Controller) *MockDeployer {
|
||||||
|
mock := &MockDeployer{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockDeployerMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockDeployer) EXPECT() *MockDeployerMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deploy mocks base method
|
||||||
|
func (m *MockDeployer) Deploy(ctx context.Context, fn, name, image string, bindings []types.PortBinding) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Deploy", ctx, fn, name, image, bindings)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deploy indicates an expected call of Deploy
|
||||||
|
func (mr *MockDeployerMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockDeployer)(nil).Deploy), ctx, fn, name, image, bindings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy mocks base method
|
||||||
|
func (m *MockDeployer) Destroy(ctx context.Context, name string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Destroy", ctx, name)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy indicates an expected call of Destroy
|
||||||
|
func (mr *MockDeployerMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockDeployer)(nil).Destroy), ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method
|
||||||
|
func (m *MockDeployer) Update(ctx context.Context, name string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", ctx, name)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update
|
||||||
|
func (mr *MockDeployerMockRecorder) Update(ctx, name interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDeployer)(nil).Update), ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStatus mocks base method
|
||||||
|
func (m *MockDeployer) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetStatus", ctx, name)
|
||||||
|
ret0, _ := ret[0].(types.Service)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStatus indicates an expected call of GetStatus
|
||||||
|
func (mr *MockDeployerMockRecorder) GetStatus(ctx, name interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockDeployer)(nil).GetStatus), ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List mocks base method
|
||||||
|
func (m *MockDeployer) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "List", ctx, name)
|
||||||
|
ret0, _ := ret[0].([]types.Service)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// List indicates an expected call of List
|
||||||
|
func (mr *MockDeployerMockRecorder) List(ctx, name interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockDeployer)(nil).List), ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping mocks base method
|
||||||
|
func (m *MockDeployer) Ping(ctx context.Context) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Ping", ctx)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping indicates an expected call of Ping
|
||||||
|
func (mr *MockDeployerMockRecorder) Ping(ctx interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockDeployer)(nil).Ping), ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// MockInfra is a mock of Infra interface
|
// MockInfra is a mock of Infra interface
|
||||||
type MockInfra struct {
|
type MockInfra struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
@@ -61,3 +225,89 @@ func (mr *MockInfraMockRecorder) HealthCheck() *gomock.Call {
|
|||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockInfra)(nil).HealthCheck))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockInfra)(nil).HealthCheck))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deploy mocks base method
|
||||||
|
func (m *MockInfra) Deploy(ctx context.Context, fn, name, image string, bindings []types.PortBinding) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Deploy", ctx, fn, name, image, bindings)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deploy indicates an expected call of Deploy
|
||||||
|
func (mr *MockInfraMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockInfra)(nil).Deploy), ctx, fn, name, image, bindings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy mocks base method
|
||||||
|
func (m *MockInfra) Destroy(ctx context.Context, name string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Destroy", ctx, name)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy indicates an expected call of Destroy
|
||||||
|
func (mr *MockInfraMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockInfra)(nil).Destroy), ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update mocks base method
|
||||||
|
func (m *MockInfra) Update(ctx context.Context, name string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Update", ctx, name)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indicates an expected call of Update
|
||||||
|
func (mr *MockInfraMockRecorder) Update(ctx, name interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockInfra)(nil).Update), ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStatus mocks base method
|
||||||
|
func (m *MockInfra) GetStatus(ctx context.Context, name string) (types.Service, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetStatus", ctx, name)
|
||||||
|
ret0, _ := ret[0].(types.Service)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStatus indicates an expected call of GetStatus
|
||||||
|
func (mr *MockInfraMockRecorder) GetStatus(ctx, name interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockInfra)(nil).GetStatus), ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List mocks base method
|
||||||
|
func (m *MockInfra) List(ctx context.Context, name string) ([]types.Service, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "List", ctx, name)
|
||||||
|
ret0, _ := ret[0].([]types.Service)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// List indicates an expected call of List
|
||||||
|
func (mr *MockInfraMockRecorder) List(ctx, name interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInfra)(nil).List), ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping mocks base method
|
||||||
|
func (m *MockInfra) Ping(ctx context.Context) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Ping", ctx)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping indicates an expected call of Ping
|
||||||
|
func (mr *MockInfraMockRecorder) Ping(ctx interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockInfra)(nil).Ping), ctx)
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/metrue/fx/config"
|
||||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||||
"github.com/metrue/fx/context"
|
"github.com/metrue/fx/context"
|
||||||
"github.com/metrue/fx/packer"
|
"github.com/metrue/fx/packer"
|
||||||
"github.com/metrue/fx/pkg/spinner"
|
"github.com/metrue/fx/pkg/spinner"
|
||||||
"github.com/metrue/fx/types"
|
"github.com/metrue/fx/utils"
|
||||||
|
"github.com/otiai10/copy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Build image
|
// Build image
|
||||||
@@ -20,36 +22,68 @@ func Build(ctx context.Contexter) (err error) {
|
|||||||
spinner.Stop(task, err)
|
spinner.Stop(task, err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
cli := ctx.GetCliContext()
|
|
||||||
name := cli.String("name")
|
|
||||||
fn := ctx.Get("fn").(types.Func)
|
|
||||||
docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
|
||||||
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
|
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
|
||||||
defer os.RemoveAll(workdir)
|
defer os.RemoveAll(workdir)
|
||||||
|
|
||||||
if err := packer.PackIntoDir(fn, workdir); err != nil {
|
// Cases supports
|
||||||
return err
|
// 1. a single file function
|
||||||
}
|
// fx up func.js
|
||||||
if err := docker.BuildImage(ctx.GetContext(), workdir, name); err != nil {
|
// 2. a directory with Docker in it
|
||||||
return err
|
// fx up ./func/
|
||||||
|
// 3. a directory without Dockerfile in it, but has fx handle function file
|
||||||
|
// 4. a fx handlefunction file and its dependencies files or/and directory
|
||||||
|
// fx up func.js helper.js ./lib/
|
||||||
|
|
||||||
|
// When only one directory given and there is a Dockerfile in given directory, treat it as a containerized project and skip packing
|
||||||
|
sources := ctx.Get("sources").([]string)
|
||||||
|
|
||||||
|
if len(sources) == 0 {
|
||||||
|
return fmt.Errorf("source file/directory of function required")
|
||||||
}
|
}
|
||||||
|
|
||||||
nameWithTag := name + ":latest"
|
if len(sources) == 1 &&
|
||||||
if err := docker.TagImage(ctx.GetContext(), name, nameWithTag); err != nil {
|
utils.IsDir(sources[0]) &&
|
||||||
return err
|
utils.HasDockerfile(sources[0]) {
|
||||||
}
|
if err := copy.Copy(sources[0], workdir); err != nil {
|
||||||
ctx.Set("image", nameWithTag)
|
return err
|
||||||
|
}
|
||||||
if os.Getenv("K3S") != "" {
|
} else {
|
||||||
name := cli.String("name")
|
if err := packer.Pack(workdir, sources...); err != nil {
|
||||||
username := os.Getenv("DOCKER_USERNAME")
|
return err
|
||||||
password := os.Getenv("DOCKER_PASSWORD")
|
|
||||||
if username != "" && password != "" {
|
|
||||||
if _, err := docker.PushImage(ctx.GetContext(), name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ctx.Set("image", username+"/"+name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloudType := ctx.Get("cloud_type").(string)
|
||||||
|
name := ctx.Get("name").(string)
|
||||||
|
if cloudType == config.CloudTypeK8S && os.Getenv("K3S") == "" {
|
||||||
|
data, err := packer.PackIntoK8SConfigMapFile(workdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx.Set("data", data)
|
||||||
|
} else {
|
||||||
|
docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
||||||
|
if err := docker.BuildImage(ctx.GetContext(), workdir, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nameWithTag := name + ":latest"
|
||||||
|
if err := docker.TagImage(ctx.GetContext(), name, nameWithTag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx.Set("image", nameWithTag)
|
||||||
|
|
||||||
|
if os.Getenv("K3S") != "" {
|
||||||
|
username := os.Getenv("DOCKER_USERNAME")
|
||||||
|
password := os.Getenv("DOCKER_PASSWORD")
|
||||||
|
if username != "" && password != "" {
|
||||||
|
if _, err := docker.PushImage(ctx.GetContext(), name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx.Set("image", username+"/"+name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/metrue/fx/context"
|
"github.com/metrue/fx/context"
|
||||||
"github.com/metrue/fx/types"
|
|
||||||
"github.com/metrue/fx/utils"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parse parse input
|
// Parse parse input
|
||||||
@@ -15,18 +13,11 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
|
|||||||
cli := ctx.GetCliContext()
|
cli := ctx.GetCliContext()
|
||||||
switch action {
|
switch action {
|
||||||
case "up":
|
case "up":
|
||||||
funcFile := cli.Args().First()
|
sources := []string{}
|
||||||
lang := utils.GetLangFromFileName(funcFile)
|
for _, s := range cli.Args() {
|
||||||
body, err := ioutil.ReadFile(funcFile)
|
sources = append(sources, s)
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "read source failed")
|
|
||||||
}
|
}
|
||||||
fn := types.Func{
|
ctx.Set("sources", sources)
|
||||||
Language: lang,
|
|
||||||
Source: string(body),
|
|
||||||
}
|
|
||||||
ctx.Set("fn", fn)
|
|
||||||
|
|
||||||
name := cli.String("name")
|
name := cli.String("name")
|
||||||
ctx.Set("name", name)
|
ctx.Set("name", name)
|
||||||
port := cli.Int("port")
|
port := cli.Int("port")
|
||||||
@@ -34,7 +25,7 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
|
|||||||
case "down":
|
case "down":
|
||||||
services := cli.Args()
|
services := cli.Args()
|
||||||
if len(services) == 0 {
|
if len(services) == 0 {
|
||||||
return errors.New("service name required")
|
return fmt.Errorf("service name required")
|
||||||
}
|
}
|
||||||
svc := []string{}
|
svc := []string{}
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
@@ -44,6 +35,28 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
|
|||||||
case "list":
|
case "list":
|
||||||
name := cli.Args().First()
|
name := cli.Args().First()
|
||||||
ctx.Set("filter", name)
|
ctx.Set("filter", name)
|
||||||
|
case "image_build":
|
||||||
|
sources := []string{}
|
||||||
|
for _, s := range cli.Args() {
|
||||||
|
sources = append(sources, s)
|
||||||
|
}
|
||||||
|
ctx.Set("sources", sources)
|
||||||
|
tag := cli.String("tag")
|
||||||
|
if tag == "" {
|
||||||
|
tag = uuid.New().String()
|
||||||
|
}
|
||||||
|
ctx.Set("tag", tag)
|
||||||
|
case "image_export":
|
||||||
|
sources := []string{}
|
||||||
|
for _, s := range cli.Args() {
|
||||||
|
sources = append(sources, s)
|
||||||
|
}
|
||||||
|
ctx.Set("sources", sources)
|
||||||
|
outputDir := cli.String("output")
|
||||||
|
if outputDir == "" {
|
||||||
|
return fmt.Errorf("output directory required")
|
||||||
|
}
|
||||||
|
ctx.Set("output", outputDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
66
middlewares/provision.go
Normal file
66
middlewares/provision.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/metrue/fx/config"
|
||||||
|
"github.com/metrue/fx/constants"
|
||||||
|
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
|
||||||
|
"github.com/metrue/fx/context"
|
||||||
|
"github.com/metrue/fx/infra"
|
||||||
|
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||||
|
k8sInfra "github.com/metrue/fx/infra/k8s"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provision make sure infrastructure is healthy
|
||||||
|
func Provision(ctx context.Contexter) (err error) {
|
||||||
|
fxConfig := ctx.Get("config").(*config.Config)
|
||||||
|
cloud := fxConfig.Clouds[fxConfig.CurrentCloud]
|
||||||
|
|
||||||
|
var deployer infra.Deployer
|
||||||
|
if os.Getenv("KUBECONFIG") != "" {
|
||||||
|
deployer, err = k8sInfra.CreateDeployer(os.Getenv("KUBECONFIG"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx.Set("cloud_type", config.CloudTypeK8S)
|
||||||
|
} else if cloud["type"] == config.CloudTypeDocker {
|
||||||
|
provisioner := dockerInfra.CreateProvisioner(cloud["host"], cloud["user"])
|
||||||
|
ok, err := provisioner.HealthCheck()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
if _, err := provisioner.Provision(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
docker, err := dockerHTTP.Create(cloud["host"], constants.AgentPort)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "please make sure docker is installed and running on your host")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO should clean up, but it needed in middlewares.Build
|
||||||
|
ctx.Set("docker", docker)
|
||||||
|
deployer, err = dockerInfra.CreateDeployer(docker)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx.Set("cloud_type", config.CloudTypeDocker)
|
||||||
|
} else if cloud["type"] == config.CloudTypeK8S {
|
||||||
|
deployer, err = k8sInfra.CreateDeployer(cloud["kubeconfig"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx.Set("cloud_type", config.CloudTypeK8S)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("unsupport cloud type %s, please make sure you config is correct", cloud["type"])
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Set("deployer", deployer)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package middlewares
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/metrue/fx/config"
|
|
||||||
"github.com/metrue/fx/constants"
|
|
||||||
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
|
|
||||||
"github.com/metrue/fx/context"
|
|
||||||
"github.com/metrue/fx/deploy"
|
|
||||||
dockerDeployer "github.com/metrue/fx/deploy/docker"
|
|
||||||
k8sDeployer "github.com/metrue/fx/deploy/k8s"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Setup create k8s or docker cli
|
|
||||||
func Setup(ctx context.Contexter) (err error) {
|
|
||||||
fxConfig := ctx.Get("config").(*config.Config)
|
|
||||||
cloud := fxConfig.Clouds[fxConfig.CurrentCloud]
|
|
||||||
|
|
||||||
var deployer deploy.Deployer
|
|
||||||
if cloud["type"] == config.CloudTypeDocker {
|
|
||||||
docker, err := dockerHTTP.Create(cloud["host"], constants.AgentPort)
|
|
||||||
if err != nil {
|
|
||||||
return 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)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if cloud["type"] == config.CloudTypeK8S {
|
|
||||||
if os.Getenv("KUBECONFIG") != "" {
|
|
||||||
deployer, err = k8sDeployer.Create(cloud["kubeconfig"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("unsupport cloud type %s, please make sure you config is correct", cloud["type"])
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Set("deployer", deployer)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
14
packer/doc.go
Normal file
14
packer/doc.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Packer takes source codes of a function, and pack them into a containerized service, that means there is Dockerfile generated in the output directory
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
Pack(output, "hello.js") # a single file function
|
||||||
|
Pack(output, "hello.js", "helper.js") # multiple files function
|
||||||
|
Pack(output, "./func/") # a directory of function
|
||||||
|
Pack(output, "hello.js", "./func/") # a directory and files of function
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
package packer
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package packer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gobuffalo/packr"
|
|
||||||
"github.com/metrue/fx/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DockerPacker pack a function source code to a Docker build-able project
|
|
||||||
type DockerPacker struct {
|
|
||||||
box packr.Box
|
|
||||||
}
|
|
||||||
|
|
||||||
func isHandler(name string) bool {
|
|
||||||
basename := filepath.Base(name)
|
|
||||||
nameWithoutExt := strings.TrimSuffix(basename, filepath.Ext(basename))
|
|
||||||
return nameWithoutExt == "fx" ||
|
|
||||||
nameWithoutExt == "Fx" || // Fx is for Java
|
|
||||||
nameWithoutExt == "mod" // mod.rs is for Rust
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDockerPacker new a Docker packer
|
|
||||||
func NewDockerPacker(box packr.Box) *DockerPacker {
|
|
||||||
return &DockerPacker{box: box}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pack pack a single function source code to be project
|
|
||||||
func (p *DockerPacker) Pack(serviceName string, fn types.Func) (types.Project, error) {
|
|
||||||
var files []types.ProjectSourceFile
|
|
||||||
for _, name := range p.box.List() {
|
|
||||||
prefix := fmt.Sprintf("%s/", fn.Language)
|
|
||||||
if strings.HasPrefix(name, prefix) {
|
|
||||||
content, err := p.box.FindString(name)
|
|
||||||
if err != nil {
|
|
||||||
return types.Project{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if preset's file is handler function of project, replace it with give one
|
|
||||||
if isHandler(name) {
|
|
||||||
files = append(files, types.ProjectSourceFile{
|
|
||||||
Path: strings.Replace(name, prefix, "", 1),
|
|
||||||
Body: fn.Source,
|
|
||||||
IsHandler: true,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
files = append(files, types.ProjectSourceFile{
|
|
||||||
Path: strings.Replace(name, prefix, "", 1),
|
|
||||||
Body: content,
|
|
||||||
IsHandler: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.Project{
|
|
||||||
Name: serviceName,
|
|
||||||
Files: files,
|
|
||||||
Language: fn.Language,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
package packer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gobuffalo/packr"
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/metrue/fx/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPacker(t *testing.T) {
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
box := packr.NewBox("./images")
|
|
||||||
p := NewDockerPacker(box)
|
|
||||||
|
|
||||||
mockSource := `
|
|
||||||
module.exports = ({a, b}) => {
|
|
||||||
return a + b
|
|
||||||
}
|
|
||||||
`
|
|
||||||
fn := types.Func{
|
|
||||||
Language: "node",
|
|
||||||
Source: mockSource,
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceName := "service-mock"
|
|
||||||
project, err := p.Pack(serviceName, fn)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if project.Name != serviceName {
|
|
||||||
t.Fatalf("should get %s but got %s", serviceName, project.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if project.Language != "node" {
|
|
||||||
t.Fatal("incorrect Language")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(project.Files) != 3 {
|
|
||||||
t.Fatal("node project should have 3 files")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range project.Files {
|
|
||||||
if file.Path == "fx.js" {
|
|
||||||
if file.IsHandler == false {
|
|
||||||
t.Fatal("fx.js should be handler")
|
|
||||||
}
|
|
||||||
if file.Body != mockSource {
|
|
||||||
t.Fatalf("should get %s but got %v", mockSource, file.Body)
|
|
||||||
}
|
|
||||||
} else if file.Path == "Dockerfile" {
|
|
||||||
if file.IsHandler == true {
|
|
||||||
t.Fatalf("should get %v but got %v", false, file.IsHandler)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if file.IsHandler == true {
|
|
||||||
t.Fatalf("should get %v but %v", false, file.IsHandler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
5
packer/fixture/p1/Dockerfile
Normal file
5
packer/fixture/p1/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
FROM metrue/fx-node-base
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["node", "app.js"]
|
||||||
9
packer/fixture/p1/app.js
Normal file
9
packer/fixture/p1/app.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const Koa = require('koa');
|
||||||
|
const bodyParser = require('koa-bodyparser');
|
||||||
|
const fx = require('./fx');
|
||||||
|
|
||||||
|
const app = new Koa();
|
||||||
|
app.use(bodyParser());
|
||||||
|
app.use(fx);
|
||||||
|
|
||||||
|
app.listen(3000);
|
||||||
3
packer/fixture/p1/fx.js
Normal file
3
packer/fixture/p1/fx.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (ctx) => {
|
||||||
|
ctx.body = 'hello world'
|
||||||
|
}
|
||||||
3
packer/fixture/p2/fx.js
Normal file
3
packer/fixture/p2/fx.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = (ctx) => {
|
||||||
|
ctx.body = 'hello world'
|
||||||
|
}
|
||||||
6
packer/fixture/p3/fx.js
Normal file
6
packer/fixture/p3/fx.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const say = require('./helper')
|
||||||
|
|
||||||
|
module.exports = (ctx) => {
|
||||||
|
say("hi")
|
||||||
|
ctx.body = 'hello world'
|
||||||
|
}
|
||||||
5
packer/fixture/p3/helper.js
Normal file
5
packer/fixture/p3/helper.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const say = (msg) => {
|
||||||
|
console.log(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = say
|
||||||
242
packer/packer.go
242
packer/packer.go
@@ -1,59 +1,226 @@
|
|||||||
package packer
|
package packer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/gobuffalo/packr"
|
"github.com/gobuffalo/packr"
|
||||||
"github.com/metrue/fx/types"
|
|
||||||
"github.com/metrue/fx/utils"
|
"github.com/metrue/fx/utils"
|
||||||
|
"github.com/otiai10/copy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Packer interface
|
var presets packr.Box
|
||||||
type Packer interface {
|
|
||||||
Pack(serviceName string, fn types.Func) (types.Project, error)
|
func init() {
|
||||||
|
presets = packr.NewBox("./images")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack a function to be a docker project which is web service, handle the imcome request with given function
|
// Pack pack a file or directory into a Docker project
|
||||||
func Pack(svcName string, fn types.Func) (types.Project, error) {
|
func Pack(output string, input ...string) error {
|
||||||
box := packr.NewBox("./images")
|
if len(input) == 0 {
|
||||||
pkr := NewDockerPacker(box)
|
return fmt.Errorf("source file or directory required")
|
||||||
return pkr.Pack(svcName, fn)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// PackIntoDir pack service code into directory
|
var lang string
|
||||||
func PackIntoDir(fn types.Func, outputDir string) error {
|
for _, f := range input {
|
||||||
project, err := Pack("", fn)
|
if utils.IsRegularFile(f) {
|
||||||
if err != nil {
|
lang = langFromFileName(f)
|
||||||
|
} else if utils.IsDir(f) {
|
||||||
|
if err := filepath.Walk(f, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if utils.IsRegularFile(path) {
|
||||||
|
lang = langFromFileName(path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if lang == "" {
|
||||||
|
return fmt.Errorf("could not tell programe language of your input source codes")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := restore(output, lang); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, file := range project.Files {
|
|
||||||
tmpfn := filepath.Join(outputDir, file.Path)
|
if len(input) == 1 {
|
||||||
if err := utils.EnsureFile(tmpfn); err != nil {
|
stat, err := os.Stat(input[0])
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := ioutil.WriteFile(tmpfn, []byte(file.Body), 0666); err != nil {
|
if stat.Mode().IsRegular() {
|
||||||
return err
|
if err := filepath.Walk(output, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isHandler(path) {
|
||||||
|
if err := copy.Copy(input[0], path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasFxHandleFile(input...) {
|
||||||
|
msg := `it requires a fx handle file when input is not a single file function, e.g.
|
||||||
|
fx.go for Golang
|
||||||
|
Fx.java for Java
|
||||||
|
fx.php for PHP
|
||||||
|
fx.py for Python
|
||||||
|
fx.js for JavaScript or Node
|
||||||
|
fx.rb for Ruby
|
||||||
|
fx.jl for Julia
|
||||||
|
fx.d for D`
|
||||||
|
return fmt.Errorf(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := merge(output, input...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restore(output string, lang string) error {
|
||||||
|
for _, name := range presets.List() {
|
||||||
|
prefix := fmt.Sprintf("%s/", lang)
|
||||||
|
if strings.HasPrefix(name, prefix) {
|
||||||
|
content, err := presets.FindString(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(output, strings.Replace(name, prefix, "", 1))
|
||||||
|
if err := utils.EnsureFile(filePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(filePath, []byte(content), 0666); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PackIntoK8SConfigMapFile pack function a K8S config map file
|
func merge(dest string, input ...string) error {
|
||||||
func PackIntoK8SConfigMapFile(fn types.Func) (string, error) {
|
for _, file := range input {
|
||||||
project, err := Pack("", fn)
|
stat, err := os.Stat(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return err
|
||||||
|
}
|
||||||
|
if stat.Mode().IsRegular() {
|
||||||
|
targetFilePath := filepath.Join(dest, stat.Name())
|
||||||
|
if err := utils.EnsureFile(targetFilePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(targetFilePath, body, 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if stat.Mode().IsDir() {
|
||||||
|
if err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copy.Copy(file, dest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tree := map[string]string{}
|
return nil
|
||||||
for _, file := range project.Files {
|
}
|
||||||
tree[file.Path] = file.Body
|
|
||||||
|
func isHandler(name string) bool {
|
||||||
|
basename := filepath.Base(name)
|
||||||
|
nameWithoutExt := strings.TrimSuffix(basename, filepath.Ext(basename))
|
||||||
|
return nameWithoutExt == "fx" ||
|
||||||
|
nameWithoutExt == "Fx" || // Fx is for Java
|
||||||
|
nameWithoutExt == "mod" // mod.rs is for Rust
|
||||||
|
}
|
||||||
|
|
||||||
|
func langFromFileName(fileName string) string {
|
||||||
|
extLangMap := map[string]string{
|
||||||
|
".js": "node",
|
||||||
|
".go": "go",
|
||||||
|
".rb": "ruby",
|
||||||
|
".py": "python",
|
||||||
|
".php": "php",
|
||||||
|
".jl": "julia",
|
||||||
|
".java": "java",
|
||||||
|
".d": "d",
|
||||||
|
".rs": "rust",
|
||||||
|
}
|
||||||
|
return extLangMap[filepath.Ext(fileName)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasFxHandleFile(input ...string) bool {
|
||||||
|
var handleFile string
|
||||||
|
for _, file := range input {
|
||||||
|
if utils.IsRegularFile(file) && isHandler(file) {
|
||||||
|
handleFile = file
|
||||||
|
break
|
||||||
|
} else if utils.IsDir(file) {
|
||||||
|
if err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.IsRegularFile(path) && isHandler(info.Name()) {
|
||||||
|
handleFile = path
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return handleFile != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackIntoK8SConfigMapFile pack function a K8S config map file
|
||||||
|
func PackIntoK8SConfigMapFile(dir string) (string, error) {
|
||||||
|
tree := map[string]string{}
|
||||||
|
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
relpath := strings.Replace(path, dir, "", 1)
|
||||||
|
body, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tree[relpath] = string(body)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
data, err := json.Marshal(tree)
|
data, err := json.Marshal(tree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -74,18 +241,3 @@ func TreeToDir(tree map[string]string, outputDir string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PackIntoTar pack service code into directory
|
|
||||||
func PackIntoTar(fn types.Func, path string) error {
|
|
||||||
tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tarDir)
|
|
||||||
|
|
||||||
if err := PackIntoDir(fn, tarDir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return utils.TarDir(tarDir, path)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,80 +1,182 @@
|
|||||||
package packer
|
package packer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/metrue/fx/types"
|
"github.com/metrue/fx/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPack(t *testing.T) {
|
func TestPacker(t *testing.T) {
|
||||||
mockSource := `
|
t.Run("Pack directory with Dockerfile in it", func(t *testing.T) {
|
||||||
module.exports = ({a, b}) => {
|
input := "./fixture/p1"
|
||||||
return a + b
|
output := "output-1"
|
||||||
}
|
defer func() {
|
||||||
`
|
os.RemoveAll(output)
|
||||||
fn := types.Func{
|
}()
|
||||||
Language: "node",
|
if err := Pack(output, input); err != nil {
|
||||||
Source: mockSource,
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
serviceName := "service-mock"
|
t.Run("Pack directory only fx.js in it", func(t *testing.T) {
|
||||||
project, err := Pack(serviceName, fn)
|
input := "./fixture/p2"
|
||||||
|
output := "output-2"
|
||||||
|
defer func() {
|
||||||
|
os.RemoveAll(output)
|
||||||
|
}()
|
||||||
|
if err := Pack(output, input); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Pack directory with fx.js and helper in it", func(t *testing.T) {
|
||||||
|
input := "./fixture/p3"
|
||||||
|
output := "output-3"
|
||||||
|
defer func() {
|
||||||
|
os.RemoveAll(output)
|
||||||
|
}()
|
||||||
|
if err := Pack(output, input); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Pack files list with fx.js in it", func(t *testing.T) {
|
||||||
|
handleFile := "./fixture/p3/fx.js"
|
||||||
|
helperFile := "./fixture/p3/helper.js"
|
||||||
|
output := "output-4"
|
||||||
|
defer func() {
|
||||||
|
os.RemoveAll(output)
|
||||||
|
}()
|
||||||
|
if err := Pack(output, handleFile, helperFile); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Pack files list without fx.js in it", func(t *testing.T) {
|
||||||
|
f1 := "./fixture/p3/helper.js"
|
||||||
|
f2 := "./fixture/p3/helper.js"
|
||||||
|
output := "output-5"
|
||||||
|
defer func() {
|
||||||
|
os.RemoveAll(output)
|
||||||
|
}()
|
||||||
|
if err := Pack(output, f1, f2); err == nil {
|
||||||
|
t.Fatalf("should report error when there is not Dockerfile or fx.[ext] in it")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTreeAndUnTree(t *testing.T) {
|
||||||
|
_, err := PackIntoK8SConfigMapFile("./fixture/p1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if project.Name != serviceName {
|
func TestGenerate(t *testing.T) {
|
||||||
t.Fatalf("should get %s but got %s", serviceName, project.Name)
|
langs := []string{
|
||||||
|
"d",
|
||||||
|
"go",
|
||||||
|
"java",
|
||||||
|
"julia",
|
||||||
|
"node",
|
||||||
|
"php",
|
||||||
|
"python",
|
||||||
|
"ruby",
|
||||||
|
"rust",
|
||||||
}
|
}
|
||||||
|
for _, lang := range langs {
|
||||||
if project.Language != "node" {
|
output := fmt.Sprintf("output-%s-%d", lang, time.Now().Unix())
|
||||||
t.Fatal("incorrect Language")
|
defer func() {
|
||||||
}
|
os.RemoveAll(output)
|
||||||
|
}()
|
||||||
if len(project.Files) != 3 {
|
if err := restore(output, lang); err != nil {
|
||||||
t.Fatal("node project should have 3 files")
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
diffCmd := exec.Command("diff", "-r", output, "./images/"+lang)
|
||||||
for _, file := range project.Files {
|
if stdoutStderr, err := diffCmd.CombinedOutput(); err != nil {
|
||||||
if file.Path == "fx.js" {
|
fmt.Printf("%s\n", stdoutStderr)
|
||||||
if file.IsHandler == false {
|
t.Fatal(err)
|
||||||
t.Fatal("fx.js should be handler")
|
|
||||||
}
|
|
||||||
if file.Body != mockSource {
|
|
||||||
t.Fatalf("should get %s but got %v", mockSource, file.Body)
|
|
||||||
}
|
|
||||||
} else if file.Path == "Dockerfile" {
|
|
||||||
if file.IsHandler == true {
|
|
||||||
t.Fatalf("should get %v but got %v", false, file.IsHandler)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if file.IsHandler == true {
|
|
||||||
t.Fatalf("should get %v but %v", false, file.IsHandler)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTreeAndUnTree(t *testing.T) {
|
func TestMerge(t *testing.T) {
|
||||||
mockSource := `
|
// TODO should check the merge result
|
||||||
package fx;
|
t.Run("NoInput", func(t *testing.T) {
|
||||||
|
dest := "./dest"
|
||||||
|
_ = utils.EnsureDir("./dest")
|
||||||
|
defer func() {
|
||||||
|
os.RemoveAll(dest)
|
||||||
|
}()
|
||||||
|
|
||||||
import org.json.JSONObject;
|
if err := merge(dest); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
public class Fx {
|
t.Run("Files", func(t *testing.T) {
|
||||||
public int handle(JSONObject input) {
|
dest := "./dest"
|
||||||
String a = input.get("a").toString();
|
_ = utils.EnsureDir("./dest")
|
||||||
String b = input.get("b").toString();
|
defer func() {
|
||||||
return Integer.parseInt(a) + Integer.parseInt(b);
|
os.RemoveAll(dest)
|
||||||
}
|
}()
|
||||||
}
|
|
||||||
`
|
f1, err := ioutil.TempFile("", "fx.*.txt")
|
||||||
fn := types.Func{
|
if err != nil {
|
||||||
Language: "java",
|
log.Fatal(err)
|
||||||
Source: mockSource,
|
}
|
||||||
}
|
defer os.Remove(f1.Name())
|
||||||
_, err := PackIntoK8SConfigMapFile(fn)
|
|
||||||
if err != nil {
|
f2, err := ioutil.TempFile("", "fx.*.txt")
|
||||||
t.Fatal(err)
|
if err != nil {
|
||||||
}
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f2.Name())
|
||||||
|
|
||||||
|
if err := merge(dest, f1.Name(), f2.Name()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Directories", func(t *testing.T) {
|
||||||
|
dest := "./dest"
|
||||||
|
_ = utils.EnsureDir("./dest")
|
||||||
|
defer func() {
|
||||||
|
os.RemoveAll(dest)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := merge(dest, "./fixture/p1"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Files and Directories", func(t *testing.T) {
|
||||||
|
dest := "./dest"
|
||||||
|
_ = utils.EnsureDir("./dest")
|
||||||
|
defer func() {
|
||||||
|
os.RemoveAll(dest)
|
||||||
|
}()
|
||||||
|
|
||||||
|
f1, err := ioutil.TempFile("", "fx.*.txt")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f1.Name())
|
||||||
|
|
||||||
|
f2, err := ioutil.TempFile("", "fx.*.txt")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f2.Name())
|
||||||
|
|
||||||
|
if err := merge(dest, "./fixture/p1", f1.Name(), f2.Name()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,24 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"os"
|
||||||
"fmt"
|
"path/filepath"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
dockerTypes "github.com/docker/docker/api/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DockerVersion docker verion
|
// HasDockerfile check if there is Dockerfile in dir
|
||||||
func DockerVersion(host string, port string) (string, error) {
|
func HasDockerfile(dir string) bool {
|
||||||
path := host + ":" + port + "/version"
|
var dockerfile string
|
||||||
if !strings.HasPrefix(path, "http") {
|
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
path = "http://" + path
|
// nolint
|
||||||
|
if info.Mode().IsRegular() && info.Name() == "Dockerfile" {
|
||||||
|
dockerfile = path
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
if dockerfile == "" {
|
||||||
req, err := http.NewRequest("GET", path, nil)
|
return false
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
client := &http.Client{Timeout: 20 * time.Second}
|
return true
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
utils/docker_test.go
Normal file
12
utils/docker_test.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestHasDockerfile(t *testing.T) {
|
||||||
|
dir := "tmp"
|
||||||
|
_ = EnsureDir(dir)
|
||||||
|
|
||||||
|
if HasDockerfile(dir) {
|
||||||
|
t.Fatalf("should get false but got true")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -176,8 +176,8 @@ func CopyDir(src string, dst string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsurerDir Create Dir if not exist
|
// EnsureDir Create Dir if not exist
|
||||||
func EnsurerDir(dir string) (err error) {
|
func EnsureDir(dir string) (err error) {
|
||||||
if _, statError := os.Stat(dir); os.IsNotExist(statError) {
|
if _, statError := os.Stat(dir); os.IsNotExist(statError) {
|
||||||
mkError := os.MkdirAll(dir, os.ModePerm)
|
mkError := os.MkdirAll(dir, os.ModePerm)
|
||||||
return mkError
|
return mkError
|
||||||
@@ -188,7 +188,7 @@ func EnsurerDir(dir string) (err error) {
|
|||||||
// EnsureFile ensure a file
|
// EnsureFile ensure a file
|
||||||
func EnsureFile(fullpath string) error {
|
func EnsureFile(fullpath string) error {
|
||||||
dir := path.Dir(fullpath)
|
dir := path.Dir(fullpath)
|
||||||
err := EnsurerDir(dir)
|
err := EnsureDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -199,6 +199,24 @@ func EnsureFile(fullpath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsDir if given path is a directory
|
||||||
|
func IsDir(dir string) bool {
|
||||||
|
stat, err := os.Stat(dir)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return stat.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRegularFile if given path is a regular
|
||||||
|
func IsRegularFile(file string) bool {
|
||||||
|
stat, err := os.Stat(file)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return stat.Mode().IsRegular()
|
||||||
|
}
|
||||||
|
|
||||||
// IsPathExists checks whether a path exists or if failed to check
|
// IsPathExists checks whether a path exists or if failed to check
|
||||||
func IsPathExists(path string) (bool, error) {
|
func IsPathExists(path string) (bool, error) {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
|
|||||||
Reference in New Issue
Block a user