Compare commits

...

13 Commits

Author SHA1 Message Date
dependabot-preview[bot]
2a17ea6131 Bump github.com/gin-gonic/gin from 1.4.0 to 1.6.2 (#503)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.4.0 to 1.6.2.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.4.0...v1.6.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-28 12:03:06 +08:00
dependabot-preview[bot]
5adca0dd2f Bump github.com/briandowns/spinner from 1.9.0 to 1.10.0 (#501)
Bumps [github.com/briandowns/spinner](https://github.com/briandowns/spinner) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/briandowns/spinner/releases)
- [Commits](https://github.com/briandowns/spinner/compare/v1.9.0...v1.10.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-26 10:16:58 +08:00
Minghe
521a9e64a2 update docs (#498) 2020-03-20 18:54:30 +08:00
Minghe Huang
bfe8dc4249 bump version 2020-03-19 22:15:06 +08:00
Minghe
b569820d3e Provisioners (#495)
* seperate privisioners by platforms
* refactor provisioner
* fix image build
2020-03-19 21:53:31 +08:00
dependabot-preview[bot]
99b3696b29 Bump github.com/golang/mock from 1.4.2 to 1.4.3 (#486)
Bumps [github.com/golang/mock](https://github.com/golang/mock) from 1.4.2 to 1.4.3.
- [Release notes](https://github.com/golang/mock/releases)
- [Changelog](https://github.com/golang/mock/blob/master/.goreleaser.yml)
- [Commits](https://github.com/golang/mock/compare/v1.4.2...v1.4.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-19 17:51:22 +08:00
Minghe
779679a809 Remove 'fx call' and 'fx doctor' 2020-03-19 16:54:45 +08:00
Minghe
ec49f75717 update readme (#491) 2020-03-19 13:37:39 +08:00
Minghe
23598ce1c6 update docs (#490) 2020-03-19 13:30:28 +08:00
Minghe
2831949814 Refactor driver and privioner (#489) 2020-03-19 11:28:52 +08:00
Minghe
e712e3d0e2 Simplify fx infra (#485)
* add --host and --kubeconf to `fx up`
* remove `fx infra list` and `fx infra use`
* put assets into skip dir for golangci

* fix lint issue

* upgrade go-ssh-client to consume the 'Connectable' API, enable more
friendly SSH connection error message

* parse 'host' and 'kubeconf' for `fx list` and `fx down` also

* refactor parse

* clean infra stuff
2020-03-18 09:00:14 +08:00
Minghe
7df1c64740 name the box (#481) 2020-03-15 12:55:52 +08:00
Minghe
91325f9ca3 commit packr-ed files (#480) 2020-03-15 08:36:46 +08:00
105 changed files with 1902 additions and 2853 deletions

View File

@@ -19,8 +19,7 @@ jobs:
- name: lint
run: |
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint \
golangci-lint run -v
make lint
- name: unit test
env:
@@ -43,11 +42,6 @@ jobs:
make docker-build
make test
# make docker-publish #TODO in release workflow
- name: test fx docker cloud
run: |
make start_docker_infra
make test_docker_infra
make stop_docker_infra
- name: test fx cli
env:

View File

@@ -7,6 +7,8 @@ run:
- examples
- api/images
- test/functions
- assets/
- bundler/go/assets
linters:
enable:
- goimports

View File

@@ -4,7 +4,8 @@ DOCKER_REMOTE_HOST_ADDR ?= "127.0.0.1"
DOCKER_REMOTE_HOST_USER ?= $(whoami)
lint:
golangci-lint run
docker pull golangci/golangci-lint
docker run --rm -v $(CURDIR):/app -w /app golangci/golangci-lint golangci-lint run -v
generate:
packr
@@ -26,7 +27,7 @@ clean:
rm -rf ${DIST_DIR}
unit-test:
CI=true ./scripts/coverage.sh
./scripts/coverage.sh
cli-test-ci:
./scripts/test_cli.sh 'js'

View File

@@ -15,9 +15,6 @@ Poor man's function as a service.
- [Introduction](#introduction)
- [Installation](#installation)
- [Usage](#usage)
- [Manage Infrastructure](#manage-infrastructure)
- [Contribute](#contribute)
## Introduction
@@ -71,8 +68,6 @@ You can go the release page to [download](https://github.com/metrue/fx/releases)
## Usage
Make sure [Docker](https://docs.docker.com/engine/installation/) installed and running on your server first. then type `fx -h` on your terminal to check out basic help.
```
NAME:
fx - makes function as a service
@@ -80,17 +75,11 @@ NAME:
USAGE:
fx [global options] command [command options] [arguments...]
VERSION:
0.8.7
COMMANDS:
infra manage infrastructure
up deploy a function
down destroy a service
list, ls list deployed services
call run a function instantly
image manage image of service
doctor health check for fx
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
@@ -98,22 +87,40 @@ GLOBAL OPTIONS:
--version, -v print the version
```
### Deploy your function to Docker
### Deploy function
#### Local Docker environment
By default, function will be deployed on localhost make sure [Docker](https://docs.docker.com/engine/installation/) installed and running on your server first. then type `fx -h` on your terminal to check out basic help.
```
$ fx up --name hello-fx ./examples/functions/JavaScript/func.js
$ fx up --name hello ./examples/functions/JavaScript/func.js
+------------------------------------------------------------------+-----------+---------------+
| ID | NAME | ENDPOINT |
+------------------------------------------------------------------+-----------+---------------+
| 5b24d36608ee392c937a61a530805f74551ddec304aea3aca2ffa0fabcf98cf3 | /hello-fx | 0.0.0.0:58328 |
| 5b24d36608ee392c937a61a530805f74551ddec304aea3aca2ffa0fabcf98cf3 | /hello | 0.0.0.0:58328 |
+------------------------------------------------------------------+-----------+---------------+
```
### Deploy your function to Kubernetes
#### Remote host
Use `--host` to specify the target host for your function, or you can just set it to `FX_HOST` environment variable.
```shell
$ fx up --host roo@<your host> --name hello ./examples/functions/JavaScript/func.js
+------------------------------------------------------------------+-----------+---------------+
| ID | NAME | ENDPOINT |
+------------------------------------------------------------------+-----------+---------------+
| 5b24d36608ee392c937a61a530805f74551ddec304aea3aca2ffa0fabcf98cf3 | /hello | 0.0.0.0:58345 |
+------------------------------------------------------------------+-----------+---------------+
```
#### Kubernetes
```
$ KUBECONFIG=~/.kube/config ./build/fx up examples/functions/JavaScript/func.js --name hello-fx
$ FX_KUBECONF=~/.kube/config fx up examples/functions/JavaScript/func.js --name hello
+-------------------------------+------+----------------+
| ID | NAME | ENDPOINT |
@@ -122,7 +129,7 @@ $ KUBECONFIG=~/.kube/config ./build/fx up examples/functions/JavaScript/func.js
+------------------------+-------------+----------------+
```
### Test your service
### Test service
then you can test your service:
@@ -149,31 +156,6 @@ hello world
```
## 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, 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
$ fx infra create --name infra_us --type docker --host <user>@<ip> ## create docker type infrasture on <ip>
$ fx infra create --name infra_bj --type k8s --master <user>@<ip> --agents '<user1>@<ip1>,<user2>@<ip2>' ## create k8s type infrasture use <ip> as master node, and <ip1> and <ip2> as agents nodes
```
### `fx infra use`
To use a infrastructure, you can use `fx infra use` command to activate it.
```shell
fx infra use <infrastructure name>
```
and you can list your infrastructure with `fx infra list`
## Use Public Cloud Kubernetes Service as infrastructure to run your functions
* Azure Kubernetes Service (AKS)
@@ -198,7 +180,7 @@ aks-nodepool1-31718369-0 Ready agent 6m44s v1.12.8
Since AKS's config will be merged into `~/.kube/config` and set to be current context after you run `az aks get-credentials` command, so you can just set KUBECONFIG to default config also,
```shell
$ export KUBECONFIG=~/.kube/config # then fx will take the config to deloy function
$ export FX_KUBECONF=~/.kube/config # then fx will take the config to deloy function
```
But we would suggest you run `kubectl config current-context` to check if the current context is what you want.
@@ -224,7 +206,7 @@ $ kubectl config current-context
Then you can deploy your function onto GKE cluster with,
```shell
$ KUBECONFIG=~/.kube/config fx up examples/functions/JavaScript/func.js --name hellojs
$ FX_KUBECONF=~/.kube/config fx up examples/functions/JavaScript/func.js --name hellojs
```
* Setup your own Kubernetes cluster
@@ -233,31 +215,6 @@ $ KUBECONFIG=~/.kube/config fx up examples/functions/JavaScript/func.js --name h
fx infra create --type k3s --name fx-cluster-1 --master root@123.11.2.3 --agents 'root@1.1.1.1,root@2.2.2.2'
```
## Contribute
fx uses [Project](https://github.com/metrue/fx/projects/4) to manage the development.
#### Prerequisites
Docker: make sure [Docker](https://docs.docker.com/engine/installation/) installed and running on your server.
<a name="buildtest"></a>
#### Build & Test
```
$ git clone https://github.com/metrue/fx
$ cd fx
$ make build
```
Then you can build and test:
```
$ make build
$ ./build/fx -h
```
## Contributors

63
bundler/README.md Normal file
View File

@@ -0,0 +1,63 @@
The only thing `fx` does is to make a single function to be an HTTP service, `fx` takes two steps to finish the process,
* Wraps the function into a web server project and make it be the handler function of the HTTP request.
* Builds the bundled web server to be Docker image, then run it as a Docker container and expose the service port.
`bundler` is the component responsible for step 1: wrap a single function (and its dependencies) into a web server. Take a Node web service as an example, the project looks like this,
```
helloworld
├── Dockerfile
├── app.js
└── fx.js
```
And the codes is pretty simple,
**app.js**
```javascript
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const cors = require('@koa/cors');
const fx = require('./fx');
const app = new Koa();
app.use(cors({
origin: '*',
}));
app.use(bodyParser());
app.use(fx);
app.listen(3000);
```
**fx.js**
```javascript
module.exports = (ctx) => {
ctx.body = 'hello world'
}
```
**Dockerfile**
```dockerfile
FROM metrue/fx-node-base
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]
```
You can see that it's a web service built with `Koa`, the request handler function defined in `fx.js` to response plain text `hello world`, so to make a general JavaScript/Node function to be a web service, we only have to put it into `fx.js` above, then build and run it with Docker and that's it.
To support different programming languages, `fx` needs to implement different `bundlers`, the reasons are,
* The way to set up a web service is different in different languages
* The way to manage dependency is different in different languages.
So there will be (are) different `bundlers` in `fx`.
```
go-bundler: based on Gin
ruby-bundler: based on Sinatra
node-bundler: based on Koa
python-bundler: based on flask
...
```

8
bundler/d/d-packr.go Normal file
View File

@@ -0,0 +1,8 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr clean" command to clean up this,
// and any other packr generated files.
package d
import _ "github.com/metrue/fx/bundler/d/packrd"

View File

@@ -13,7 +13,7 @@ type D struct {
// New a koa bundler
func New() *D {
return &D{
assets: packr.New("", "./assets"),
assets: packr.New("d", "./assets"),
}
}

File diff suppressed because one or more lines are too long

View File

@@ -13,7 +13,7 @@ type Gin struct {
// New a koa bundler
func New() *Gin {
return &Gin{
assets: packr.New("", "./assets"),
assets: packr.New("go", "./assets"),
}
}

View File

@@ -0,0 +1,8 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr clean" command to clean up this,
// and any other packr generated files.
package golang
import _ "github.com/metrue/fx/bundler/go/packrd"

View File

@@ -0,0 +1,33 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr2 clean" command to clean up this,
// and any other packr generated files.
package packrd
import (
"github.com/gobuffalo/packr/v2"
"github.com/gobuffalo/packr/v2/file/resolver"
)
var _ = func() error {
const gk = "449af63614707e60b8b2ffe119b470cf"
g := packr.New(gk, "")
hgr, err := resolver.NewHexGzip(map[string]string{
"76eca10e3a22b8409f5af0b284f82ee4": "1f8b08000000000000ff7ccabdaac2301407f0fd3cc59fec490af711da5e10a929115111877ee5586839a569318fefe6e8fefbf7aec23c6ceb3ed890348b6e9b3810e5aebec3c0b2d8b87696c7edb5b7a693d97e315d9d3f1607ff1b91bf9cc082761fa71e7aeac3d47084d26fe8a8a005212124c38266590c0b5179abddb9c45f9665447955e0a18c0d493de9130000ffff44ef0fc1ae000000",
"cf8b44c6431c48afd768cf293ee38cab": "1f8b08000000000000ff4c8cc1aac2301045d799af98975502efa585b7ab74a7b854d41f88218983edb40c0d08d27f9766e5eede03e7cc3e3c7d8e387a62001ae7491634a074a6e551ee2e4c639389fff2c414b6a5c102a4c2a12ac6e21b9460d7632676fb987c19166341893b1e6e4637fa17d3abfef3e9fa0d2861946a8abb1436bafb6fdb56db5dc53f3d320d5b5ccd9e2998286241adb0c2270000ffff1428eddcb3000000",
"da1f5928cfe751551db26cc0459d944e": "1f8b08000000000000ff14c9b10e82301000d0b9f715974e601089a3ab8b71d0c12fa8b5948bed1d81233621fcbb617bc91b9dffba18303b6200caa34c8a36920ecbbbf5924f91f81885c9efb200fdc21efb52792d7888c4ed555843d11a57305e4b7b7f3d1fd5b9eb1adcf7b682313687797631d80bda21a424f893297d6c0366ab61837f000000ffffe12418cf85000000",
})
if err != nil {
panic(err)
}
g.DefaultResolver = hgr
func() {
b := packr.New("go", "./assets")
b.SetResolver("Dockerfile", packr.Pointer{ForwardBox: gk, ForwardPath: "76eca10e3a22b8409f5af0b284f82ee4"})
b.SetResolver("app.go", packr.Pointer{ForwardBox: gk, ForwardPath: "cf8b44c6431c48afd768cf293ee38cab"})
b.SetResolver("fx.go", packr.Pointer{ForwardBox: gk, ForwardPath: "da1f5928cfe751551db26cc0459d944e"})
}()
return nil
}()

View File

@@ -0,0 +1,8 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr clean" command to clean up this,
// and any other packr generated files.
package java
import _ "github.com/metrue/fx/bundler/java/packrd"

View File

@@ -13,7 +13,7 @@ type Java struct {
// New a koa bundler
func New() *Java {
return &Java{
assets: packr.New("", "./assets"),
assets: packr.New("java", "./assets"),
}
}

View File

@@ -0,0 +1,35 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr2 clean" command to clean up this,
// and any other packr generated files.
package packrd
import (
"github.com/gobuffalo/packr/v2"
"github.com/gobuffalo/packr/v2/file/resolver"
)
var _ = func() error {
const gk = "472df26e20796ab2f7966f2dbcce8ee2"
g := packr.New(gk, "")
hgr, err := resolver.NewHexGzip(map[string]string{
"3ce2296391d96d76aaff2d829f1f612d": "1f8b08000000000000ff4c4fb16ec32014dcf98a274f586da9a58e563a66c8d00c19ab0e0f4c5d2801042f2955e57faf9c10c7b71cdc9dee20a2fac651c367e91933c718128109c2e2199df16277e5fe6685340a9b8317bbc3fe6d2fad56d433164fd21905ca61ce8031c21f0300a8722624a3e01ccc0047349e1f28193fbe7f00a631b7353ca3ae5d2a36b79bc88489f84bd7756dbf44b705bed00f4e27d880d73fb02d7c65638c22864cbc796e1e415181a7d7d5d0656cf90104696bcb5de48a8a9061f8e5edaa77c66c249d4f8e78d33cd457882bf320ed3a3fd5f3c426f61f0000ffff88067e0e6b010000",
"74b2f70d9674f4e1a69013f26355fca9": "1f8b08000000000000ff8c55516fdb20107ecfafb0fc1eb0bb4aab2a4a1f264daad46ad3d64d7bab6ef8ec90da808024debf9f0c4e63c775dc3c85fb3eeebeef8033bb6f9b3ad9a37552abbb3427599aa012ba90aaba4b7f3d7f5ddfa4f77cc58cd55b143e699b5ab9bb74e3bdb9a5b4813d2a0206c40689b615fdfeed895e93accb1298b7ad936fecc3e1400e9f02ef2acb72fae7e9f1a7d860036ba99c0725305d254992b44edeba003c6a013e085b2c98cc314260bdbf7ec95e32d2ba22e5a1086b7481f5ef689c87148c8e62915659bd330f05d7b622ce58a9aad24283076d5f193d82910ad6cb12847f2878d9aec198f516f6c0e8201e8906c42b5452557c0b96d1d332c2fd69f08ce49da8e37215d1bf3b59f78962b27a5749e54e9141741c9cf8e9bb145bd6e7393335da3b3012bbea3650e03aee9cfa1ced3dbab822f9c0d284862d8a5d77e46e0a8e09efe3d1fc061cf2d85664342ee7e995867aa6dc88c3835d46c3fff97474211f135a95b2da59b86c2370bd05e54a6d1bb40b12cfe98be4ee271b536383caf7cf6cee5690609d58747a6705922750b244e77ff481e753dd74596690da80545f6a708e972d0163183d45968dd281d38516d28ff590d10f9c0ba317eedf003c7f8ef4fc3d1e233d91d1c1b3ee86ad41eb250ed2b0781e423746d66849ec3bbf61f47d6076a3075ba17f67630fac82b8910256a04155a01293c0bfb3312935e9865eddcd83d9e9f8c6984cc6e360c8497e3627181dd79c97d0dde1add317050478b6fa55967fceb3fce6b2003aee4a685af78de4abff010000ffff2998100355070000",
"9d097ccc5e31491bd2ee70619fc36101": "1f8b08000000000000ff74cbc1aac2301005d07dbe62e82ae1417ea0bced83be855df40b26718ca97512922914a4ff2ed282a87897f79e9bd15f30109c9656a978cda908a412ec5813dbffa13ff46e242fad52797653f4e027ac15fe16b8290080bd8d2c70463e4ea49f27889c6731bb7c6490123900c2efb6d940a21b6c8c95b46ddab4efdabd6af7451792b930742c14a8d88ca552c7a2d1c0cf67ebf6e7aa56750f0000ffffeb12798604010000",
"bde0ba82beaa4757c20608eb0bcc46bd": "1f8b08000000000000ff2ccecf4a033110c7f17b9e6288d7661351c46b71ebad562a82507a984da64bd2e60f93ece2e3cbae3dcde1cbfcf8bc1f0f7b88d478227df955016754035612e201b6cef93442cd135bda80cdb1f81b01260705ed1547029f5a06840b3608c862dbf750d982b6d991ae6cc5f1fb034e32ce496e40debfe45988ddcfe7e16b074fc618f1b6efe124f55459dffca0c31cf5ea7855b9500aeeaa30ba97673df8b486654a05e4e536e491da42c752fef9a67bec4cb7f4b3f80b0000ffff3eecd9bedf000000",
})
if err != nil {
panic(err)
}
g.DefaultResolver = hgr
func() {
b := packr.New("java", "./assets")
b.SetResolver("Dockerfile", packr.Pointer{ForwardBox: gk, ForwardPath: "bde0ba82beaa4757c20608eb0bcc46bd"})
b.SetResolver("pom.xml", packr.Pointer{ForwardBox: gk, ForwardPath: "74b2f70d9674f4e1a69013f26355fca9"})
b.SetResolver("src/main/java/fx/Fx.java", packr.Pointer{ForwardBox: gk, ForwardPath: "9d097ccc5e31491bd2ee70619fc36101"})
b.SetResolver("src/main/java/fx/app.java", packr.Pointer{ForwardBox: gk, ForwardPath: "3ce2296391d96d76aaff2d829f1f612d"})
}()
return nil
}()

View File

@@ -0,0 +1,8 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr clean" command to clean up this,
// and any other packr generated files.
package julia
import _ "github.com/metrue/fx/bundler/julia/packrd"

View File

@@ -13,7 +13,7 @@ type Julia struct {
// New a koa bundler
func New() *Julia {
return &Julia{
assets: packr.New("", "./assets"),
assets: packr.New("julia", "./assets"),
}
}

View File

@@ -0,0 +1,37 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr2 clean" command to clean up this,
// and any other packr generated files.
package packrd
import (
"github.com/gobuffalo/packr/v2"
"github.com/gobuffalo/packr/v2/file/resolver"
)
var _ = func() error {
const gk = "9e78c349d756cf23066df37690663267"
g := packr.New(gk, "")
hgr, err := resolver.NewHexGzip(map[string]string{
"17bc9961cffa014e8d7ac818d8a3ec14": "1f8b08000000000000ff4c8fc16ac3300c86ef7e8a9f9e6c2826b09d0a39edb2edb08d959dc61866515a975449243bf4f18793a69bc020ebe3d36f678d7cc0634ac39e642231f13cf492f0bc7f7d59fb0f3e07d163e88c89fcd3e586eca6bdf853b771c6a84ca8b1c8d6a1e92134ee76ef3466d2b48590969b0e3d2b1900882d3ae2433a5aa1d137210587ba4635c35242ea3585941535eeab055077d54b0d41949aefe2a29edfeadfca48fc4ceca98f6c3f1f8e41ece4d0f6820991b1e67d39775b75dd71fba3cf6b679f78c869fb3fec4fd324910fb6bdd879be00e2c6946324b35599b6b8abaaca99df000000ffffa9ee463365010000",
"516dab4c6ff105b6210ce90d2afa826f": "1f8b08000000000000ff2a2e292a4d2e51f0cc2b282de15250505048b4b2f22bcd4d4a2d02f392e0bcd4bc142eaeb40a055b85b4d2bce492ccfc3c8d4c90262b2bb05e4db0f2a2d492d2a23c05b0845ea28236949504d60d080000ffff662990f46b000000",
"b4a52bf8c0e242a9573eebd6bd5881fd": "1f8b08000000000000ff4ccd31cbc2301080e1fd7ec54dddd206bee103d75ab7da5211743c9b5053cf723417417fbd98415c9fe17d7743d7e29c38d0c696ff0075d79fb1c48a440086e31e49d44c5e318923f558145f094b546246f3c4691c33270d1c312daf2078a79b470e97abaa18a135fad538ffc8d13ccc93ca7989e5cc0075bbfd7512f97073eabb43837fd65a78070000ffff383c868aac000000",
"c85770f1f90561b2df722b26d344976a": "1f8b08000000000000ff14c9310ac3300c46e1593a85f0642fcd093a06da2d0df4002ebf5c4c832de4f4fec1c35bded74d5b0c4b365bf6f5f57eee6b48822e85096a43eee29a71d4a62396c454ba0ba43699ca44dbef7bcb404462d2069ecdf7f9d703313cced3b6ec433d24be020000ffff9afc26056c000000",
"ccfa88310f9e4f3419a50e352a228292": "1f8b08000000000000fff228292908482c2a4e2de2023183538bca528bb8bc82fdfdb842f372138b8a33127300010000ffffd7a8f0a324000000",
})
if err != nil {
panic(err)
}
g.DefaultResolver = hgr
func() {
b := packr.New("julia", "./assets")
b.SetResolver("Dockerfile", packr.Pointer{ForwardBox: gk, ForwardPath: "b4a52bf8c0e242a9573eebd6bd5881fd"})
b.SetResolver("REQUIRE", packr.Pointer{ForwardBox: gk, ForwardPath: "ccfa88310f9e4f3419a50e352a228292"})
b.SetResolver("app.jl", packr.Pointer{ForwardBox: gk, ForwardPath: "17bc9961cffa014e8d7ac818d8a3ec14"})
b.SetResolver("deps.jl", packr.Pointer{ForwardBox: gk, ForwardPath: "c85770f1f90561b2df722b26d344976a"})
b.SetResolver("fx.jl", packr.Pointer{ForwardBox: gk, ForwardPath: "516dab4c6ff105b6210ce90d2afa826f"})
}()
return nil
}()

View File

@@ -0,0 +1,8 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr clean" command to clean up this,
// and any other packr generated files.
package node
import _ "github.com/metrue/fx/bundler/node/packrd"

View File

@@ -15,7 +15,7 @@ type Node struct {
// New a koa bundler
func New() *Node {
return &Node{
assets: packr.New("", "./assets"),
assets: packr.New("node", "./assets"),
}
}

View File

@@ -0,0 +1,33 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr2 clean" command to clean up this,
// and any other packr generated files.
package packrd
import (
"github.com/gobuffalo/packr/v2"
"github.com/gobuffalo/packr/v2/file/resolver"
)
var _ = func() error {
const gk = "099a799bb033d71a85f319790ea9425b"
g := packr.New(gk, "")
hgr, err := resolver.NewHexGzip(map[string]string{
"06d0f22f31cc0848f733e8dcaa11962c": "1f8b08000000000000ffcacd4f29cd49d54bad28c82f2a2956b055d0482ea9d054b0b553a8e65250482ea9d04bca4fa954b05550cf48cdc9c95728cf2fca4951e7aae502040000ffff4a686e4639000000",
"2c75a41a4116ea4ba707c129527fd11c": "1f8b08000000000000ff5c8ec10ac2301044eff98abd25116d0bde2c05ef5efc8558b7122ad9b869b122febbac0a091e67de63989e429ae0400e3a60bccd9ed1e8919cb6adfab2139d1f47c709f94fd908891f92ed9e3895de7e24574b9995612985aa1e16613fe862840e02dee593b1ad7231567342231be6a90088fdc5871de8955eab972d94fcd494f5b0c8bca4ab4f1306b36d9ac6b6ea1d0000ffffc9e8e169fb000000",
"98bb98a34bd158dd2fdd41ca90f4e64d": "1f8b08000000000000ff720bf2f755c84d2d292a4dd54fabd0cdcb4f49d54d4a2c4ee5e272f60f8854d053d0e3728d08f00f765530363030e072f67551885602a952d251504a2c28d0cb2a568ae502040000ffff6c95391846000000",
})
if err != nil {
panic(err)
}
g.DefaultResolver = hgr
func() {
b := packr.New("node", "./assets")
b.SetResolver("Dockerfile", packr.Pointer{ForwardBox: gk, ForwardPath: "98bb98a34bd158dd2fdd41ca90f4e64d"})
b.SetResolver("app.js", packr.Pointer{ForwardBox: gk, ForwardPath: "2c75a41a4116ea4ba707c129527fd11c"})
b.SetResolver("fx.js", packr.Pointer{ForwardBox: gk, ForwardPath: "06d0f22f31cc0848f733e8dcaa11962c"})
}()
return nil
}()

View File

@@ -0,0 +1,33 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr2 clean" command to clean up this,
// and any other packr generated files.
package packrd
import (
"github.com/gobuffalo/packr/v2"
"github.com/gobuffalo/packr/v2/file/resolver"
)
var _ = func() error {
const gk = "77d2d2753a1e41552ea722fb94d9bca6"
g := packr.New(gk, "")
hgr, err := resolver.NewHexGzip(map[string]string{
"2e6c11f0e86d189a5f33d2eda0bc7406": "1f8b08000000000000ff720bf2f755c84d2d292a4dd54fabd02d482dcad14d4a2c4ee5e272747151d053d0e3e2728d08f00f765530363030e072f67551885602a952d251504a2c28d02b00b352125373f3f39462b900010000ffff0583b6ed50000000",
"b3a2e75d2833aa5c271d28326dee512c": "1f8b08000000000000ff2a2e4d5248ab50a8e65250c8ad5450492ea950b05528cec84c2bb1e65250284a2d292dca5350cf48cdc9c95748ab50e7aae5e232b4e602040000ffffd1aa6a1336000000",
"cd2f2d8e12b35147ad2b9cb35d65d3ab": "1f8b08000000000000ffb48c41aa83401044f77d8a423ea80b75efa027f83984316d32629c49770f1842ee1e067285ec8a578f9794710a6bd8fcec43d2befff7c68e48f891bc308ab65b8e366e8523bab2a1ec4a0c23349df122e0fec4df6c0706e8cd2fe6be4858316039aa7cd699e6d18cc2fb85a55a35ecb992bddad1db11c5a0bf8b4f3136a3da24e6e8130000ffff6c5481b5f1000000",
})
if err != nil {
panic(err)
}
g.DefaultResolver = hgr
func() {
b := packr.New("perl", "./assets")
b.SetResolver("Dockerfile", packr.Pointer{ForwardBox: gk, ForwardPath: "2e6c11f0e86d189a5f33d2eda0bc7406"})
b.SetResolver("app.pl", packr.Pointer{ForwardBox: gk, ForwardPath: "cd2f2d8e12b35147ad2b9cb35d65d3ab"})
b.SetResolver("fx.pl", packr.Pointer{ForwardBox: gk, ForwardPath: "b3a2e75d2833aa5c271d28326dee512c"})
}()
return nil
}()

View File

@@ -0,0 +1,8 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr clean" command to clean up this,
// and any other packr generated files.
package perl
import _ "github.com/metrue/fx/bundler/perl/packrd"

View File

@@ -13,7 +13,7 @@ type Julia struct {
// New a koa bundler
func New() *Julia {
return &Julia{
assets: packr.New("", "./assets"),
assets: packr.New("perl", "./assets"),
}
}

View File

@@ -0,0 +1,33 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr2 clean" command to clean up this,
// and any other packr generated files.
package packrd
import (
"github.com/gobuffalo/packr/v2"
"github.com/gobuffalo/packr/v2/file/resolver"
)
var _ = func() error {
const gk = "37124a38bea92e4ec995016569c93ec9"
g := packr.New(gk, "")
hgr, err := resolver.NewHexGzip(map[string]string{
"0541aa67f1217ee20e491552a545a7ca": "1f8b08000000000000ff4a494d5348abd0284a2d2c4d2d2ed1b4e252505050284a2d292dca5350ca48cdc9c95728cf2fca4951e202040000ffffc6a7b4282a000000",
"46416dc28552c117e810b36196e043ca": "1f8b08000000000000ff720bf2f755c84d2d292a4dd54fabd02da82cc9c8cfd34d4a2c4ee5e272f60f8854d053d0e3728d08f00f765530363030e072f6755148cb492cce56282acd53d0cd5030d0034305dd02880240000000ffff21aa3b6b52000000",
"65d09a53a70631fa1bbd7c5e1906d910": "1f8b08000000000000ff34cdbd0ac23014c5f1fd3ec5d99240d05d28b8a8a382dd4442a037b4da7c98a410df5ea43a9edf19fe2e470fd730f91473856bb4ca6ccbf38fc7efd0c8fc5ab8548d478961726fb229a15b5f694cb09e8d51447b9bd226c7a5b2145ba1e1b98e7128dd4d5cced75e6888d3a1177745033b8c360c334bb52300c85c971ce09afcc5147d020000ffff9da83eeaa1000000",
})
if err != nil {
panic(err)
}
g.DefaultResolver = hgr
func() {
b := packr.New("python", "./assets")
b.SetResolver("Dockerfile", packr.Pointer{ForwardBox: gk, ForwardPath: "46416dc28552c117e810b36196e043ca"})
b.SetResolver("app.py", packr.Pointer{ForwardBox: gk, ForwardPath: "65d09a53a70631fa1bbd7c5e1906d910"})
b.SetResolver("fx.py", packr.Pointer{ForwardBox: gk, ForwardPath: "0541aa67f1217ee20e491552a545a7ca"})
}()
return nil
}()

View File

@@ -0,0 +1,8 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr clean" command to clean up this,
// and any other packr generated files.
package python
import _ "github.com/metrue/fx/bundler/python/packrd"

View File

@@ -13,7 +13,7 @@ type Julia struct {
// New a koa bundler
func New() *Julia {
return &Julia{
assets: packr.New("", "./assets"),
assets: packr.New("python", "./assets"),
}
}

View File

@@ -0,0 +1,33 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr2 clean" command to clean up this,
// and any other packr generated files.
package packrd
import (
"github.com/gobuffalo/packr/v2"
"github.com/gobuffalo/packr/v2/file/resolver"
)
var _ = func() error {
const gk = "5957c0f80ccc65e6ad8f2c329312596e"
g := packr.New(gk, "")
hgr, err := resolver.NewHexGzip(map[string]string{
"5d0faa4e62eb53572bfc7410d0f064f9": "1f8b08000000000000ff720bf2f755c84d2d292a4dd54fabd02d2a4daad44d4a2c4ee5e272f60f8854d053d0e3728d08f00f765530363030e072f6755100a951482c28d02b4a52d02d008b2be8e62b18e8812117200000ffff79eb4a3952000000",
"93130b893788876c223c2a0f3de05793": "1f8b08000000000000ffccce41aac23010c6f1fd9ce2dbcda6bc577057a8579168a75a91a4ce4ca520de5ddad4780537e19ffc207c2af7695001db10836b602a2f574b91e9733fa8dc820f0f01f7f39f1e99c8c4d18c49bdc2aeae6ba2319983ff195d220038f98c16cfb58166f949ccd1eeb165f5251b5334c996bba079f0c916ca55e022a1135d65cb4caff5ece7650049ec88cef29bc3de010000ffff22ee4db77f010000",
"973570cec900912c2cdd5c5531be70ac": "1f8b08000000000000ff3ccd410ac2301085e1fd9ce2d16c14c40308f122e2a276a674111acd4c3022de5d6262773fef8319470e931578bcc9013825796451833fa3e7e10f7a8fab4a93d69dd446cb5aa1559f171959d26fef59e1432c33e6b29bacec09f5fb653b7e3dde22bfe0312c1242c433a6c003c9caf40d0000ffff99b9e753aa000000",
})
if err != nil {
panic(err)
}
g.DefaultResolver = hgr
func() {
b := packr.New("ruby", "./assets")
b.SetResolver("Dockerfile", packr.Pointer{ForwardBox: gk, ForwardPath: "5d0faa4e62eb53572bfc7410d0f064f9"})
b.SetResolver("app.rb", packr.Pointer{ForwardBox: gk, ForwardPath: "93130b893788876c223c2a0f3de05793"})
b.SetResolver("fx.rb", packr.Pointer{ForwardBox: gk, ForwardPath: "973570cec900912c2cdd5c5531be70ac"})
}()
return nil
}()

View File

@@ -0,0 +1,8 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr clean" command to clean up this,
// and any other packr generated files.
package ruby
import _ "github.com/metrue/fx/bundler/ruby/packrd"

View File

@@ -13,7 +13,7 @@ type Julia struct {
// New a koa bundler
func New() *Julia {
return &Julia{
assets: packr.New("", "./assets"),
assets: packr.New("ruby", "./assets"),
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
// +build !skippackr
// Code generated by github.com/gobuffalo/packr/v2. DO NOT EDIT.
// You can use the "packr clean" command to clean up this,
// and any other packr generated files.
package rust
import _ "github.com/metrue/fx/bundler/rust/packrd"

View File

@@ -13,7 +13,7 @@ type Julia struct {
// New a koa bundler
func New() *Julia {
return &Julia{
assets: packr.New("", "./assets"),
assets: packr.New("rust", "./assets"),
}
}

View File

@@ -1,207 +0,0 @@
package config
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/user"
"path"
"path/filepath"
dockerInfra "github.com/metrue/fx/infra/docker"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
"github.com/mitchellh/go-homedir"
)
// Configer manage fx config
type Configer interface {
GetCurrentCloud() ([]byte, error)
GetCurrentCloudType() (string, error)
GetKubeConfig() (string, error)
UseCloud(name string) error
View() ([]byte, error)
AddCloud(name string, meta []byte) error
Dir() (string, error)
}
// Config config of fx
type Config struct {
configFile string
container *Container
}
const defaultFxConfig = "~/.fx/config.yml"
// LoadDefault load default config
func LoadDefault() (*Config, error) {
configFile, err := homedir.Expand(defaultFxConfig)
if err != nil {
return nil, err
}
if os.Getenv("FX_CONFIG") != "" {
configFile = os.Getenv("FX_CONFIG")
}
if _, err := os.Stat(configFile); os.IsNotExist(err) {
if err := utils.EnsureFile(configFile); err != nil {
return nil, err
}
}
return load(configFile)
}
func load(configFile string) (*Config, error) {
container, err := CreateContainer(configFile)
if err != nil {
return nil, err
}
config := &Config{
configFile: configFile,
container: container,
}
if container.get("clouds") == nil {
if err := config.writeDefaultConfig(); err != nil {
return nil, err
}
}
return config, nil
}
// Load config
func Load(configFile string) (*Config, error) {
if configFile == "" {
return nil, fmt.Errorf("invalid config file")
}
if _, err := os.Stat(configFile); os.IsNotExist(err) {
if err := utils.EnsureFile(configFile); err != nil {
return nil, err
}
}
return load(configFile)
}
// AddCloud add k8s cloud
func (c *Config) AddCloud(name string, meta []byte) error {
var cloudMeta map[string]interface{}
if err := json.Unmarshal(meta, &cloudMeta); err != nil {
return err
}
cloudType, ok := cloudMeta["type"].(string)
if !ok || cloudType == "" {
return fmt.Errorf("unknown cloud type")
}
if cloudType == types.CloudTypeK8S {
dir := path.Dir(c.configFile)
kubecfg := path.Join(dir, name+".kubeconfig")
if err := utils.EnsureFile(kubecfg); err != nil {
return err
}
config, ok := cloudMeta["config"].(string)
if !ok {
return fmt.Errorf("invalid k8s config")
}
if err := ioutil.WriteFile(kubecfg, []byte(config), 0666); err != nil {
return err
}
}
if err := c.container.set("clouds."+name, cloudMeta); err != nil {
return err
}
return nil
}
// UseCloud set cloud instance with name as current context
func (c *Config) UseCloud(name string) error {
if name == "" {
return fmt.Errorf("could not use empty name")
}
if c.container.get("clouds."+name) == nil {
return fmt.Errorf("no such cloud with name: %s", name)
}
return c.container.set("current_cloud", name)
}
// View view current config
func (c *Config) View() ([]byte, error) {
return ioutil.ReadFile(c.configFile)
}
// GetCurrentCloud get current using cloud's meta
func (c *Config) GetCurrentCloud() ([]byte, error) {
name, ok := c.container.get("current_cloud").(string)
if !ok {
return nil, fmt.Errorf("no active cloud")
}
meta := c.container.get("clouds." + name)
if meta == nil {
return nil, fmt.Errorf("invalid config")
}
return json.Marshal(meta)
}
// GetCurrentCloudType get current cloud type
func (c *Config) GetCurrentCloudType() (string, error) {
name, ok := c.container.get("current_cloud").(string)
if !ok {
return "", fmt.Errorf("no active cloud")
}
return c.container.get("clouds." + name + ".type").(string), nil
}
// GetKubeConfig get kubeconfig
func (c *Config) GetKubeConfig() (string, error) {
name, ok := c.container.get("current_cloud").(string)
if !ok {
return "", fmt.Errorf("no active cloud")
}
dir := path.Dir(c.configFile)
kubecfg := path.Join(dir, name+".kubeconfig")
return kubecfg, nil
}
func (c *Config) writeDefaultConfig() error {
me, err := user.Current()
if err != nil {
return err
}
defaultCloud := &dockerInfra.Cloud{
IP: "127.0.0.1",
User: me.Username,
Name: "default",
Type: types.CloudTypeDocker,
}
meta, err := defaultCloud.Dump()
if err != nil {
return err
}
if err := c.container.set("clouds", map[string]interface{}{}); err != nil {
return err
}
if err := c.AddCloud("default", meta); err != nil {
return err
}
return c.UseCloud("default")
}
// Dir get directory of config
func (c *Config) Dir() (string, error) {
p, err := filepath.Abs(c.configFile)
if err != nil {
return "", err
}
return path.Dir(p), nil
}
var (
_ Configer = &Config{}
)

View File

@@ -1,131 +0,0 @@
package config
import (
"encoding/json"
"fmt"
"os"
"os/user"
"path/filepath"
"reflect"
"testing"
k8sInfra "github.com/metrue/fx/infra/k8s"
"github.com/metrue/fx/types"
)
func TestConfig(t *testing.T) {
configPath := "./tmp/config.yml"
defer func() {
if err := os.RemoveAll("./tmp/config.yml"); err != nil {
t.Fatal(err)
}
}()
// default cloud
c, err := Load(configPath)
if err != nil {
t.Fatal(err)
}
defaultMeta, err := c.GetCurrentCloud()
if err != nil {
t.Fatal(err)
}
var cloudMeta map[string]string
if err := json.Unmarshal(defaultMeta, &cloudMeta); err != nil {
t.Fatal(err)
}
if cloudMeta["ip"] != "127.0.0.1" {
t.Fatalf("should get %s but got %s", "127.0.0.1", cloudMeta["ip"])
}
me, _ := user.Current()
if cloudMeta["user"] != me.Username {
t.Fatalf("should get %s but got %s", me.Username, cloudMeta["user"])
}
if cloudMeta["type"] != types.CloudTypeDocker {
t.Fatalf("should get %s but got %s", types.CloudTypeDocker, cloudMeta["type"])
}
if cloudMeta["name"] != "default" {
t.Fatalf("should get %s but got %s", "default", cloudMeta["name"])
}
n1, err := k8sInfra.CreateNode(
"1.1.1.1",
"user-1",
"k3s-master",
"master-node",
)
if err != nil {
t.Fatal(err)
}
n2, err := k8sInfra.CreateNode(
"1.1.1.1",
"user-1",
"k3s-agent",
"agent-node-1",
)
if err != nil {
t.Fatal(err)
}
kName := "k8s-1"
kubeconf := "./tmp/" + kName + "config.yml"
defer func() {
if err := os.RemoveAll(kubeconf); err != nil {
t.Fatal(err)
}
}()
// add k8s cloud
kCloud := k8sInfra.NewCloud(kubeconf, n1, n2)
kMeta, err := kCloud.Dump()
if err != nil {
t.Fatal(err)
}
if err := c.AddCloud(kName, kMeta); err != nil {
t.Fatal(err)
}
curMeta, err := c.GetCurrentCloud()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(curMeta, defaultMeta) {
t.Fatalf("should get %v but got %v", defaultMeta, curMeta)
}
if err := c.UseCloud("cloud-not-existed"); err == nil {
t.Fatalf("should get error when there is not given cloud name")
}
if err := c.UseCloud(kName); err != nil {
t.Fatal(err)
}
curMeta, err = c.GetCurrentCloud()
if err != nil {
t.Fatal(err)
}
if reflect.DeepEqual(curMeta, kMeta) {
t.Fatalf("should get %v but got %v", kMeta, curMeta)
}
body, err := c.View()
if err != nil {
t.Fatal(err)
}
fmt.Println(string(body))
dir, err := c.Dir()
if err != nil {
t.Fatal(err)
}
here, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
if dir != filepath.Join(here, "./tmp") {
t.Fatalf("should get %s but got %s", "./tmp", dir)
}
}

View File

@@ -1,73 +0,0 @@
package config
import (
"fmt"
"path/filepath"
"strings"
"sync"
"github.com/metrue/fx/utils"
"github.com/spf13/viper"
)
// Container config container, wrap viper as a key-value store with lock
type Container struct {
mux sync.Mutex
store string
}
// CreateContainer new a container
func CreateContainer(storeFile string) (*Container, error) {
if err := utils.EnsureFile(storeFile); err != nil {
return nil, err
}
dir := filepath.Dir(storeFile)
ext := filepath.Ext(storeFile)
name := filepath.Base(storeFile)
viper.AddConfigPath(dir)
viper.SetConfigName(strings.Replace(name, ext, "", 1))
viper.SetConfigType(strings.Replace(ext, ".", "", 1))
if err := viper.ReadInConfig(); err != nil {
return nil, err
}
return &Container{
store: storeFile,
}, nil
}
func (c *Container) set(key string, value interface{}) error {
c.mux.Lock()
defer c.mux.Unlock()
if key == "" {
return fmt.Errorf("empty key not allowed")
}
keys := strings.Split(key, ".")
if len(keys) == 1 {
viper.Set(key, value)
} else {
prePath := keys[0]
for i := 1; i < len(keys)-2; i++ {
prePath += "." + keys[i]
}
if viper.Get(prePath) == nil {
return fmt.Errorf("%s not existed", prePath)
}
viper.Set(key, value)
}
// viper.Set(key, value)
if err := viper.WriteConfig(); err != nil {
return err
}
return nil
}
func (c *Container) get(key string) interface{} {
c.mux.Lock()
defer c.mux.Unlock()
return viper.Get(key)
}

View File

@@ -1,84 +0,0 @@
package config
import (
"os"
"testing"
)
func TestContainer(t *testing.T) {
configPath := "./tmp/container.yml"
defer func() {
if err := os.RemoveAll("./tmp/container.yml"); err != nil {
t.Fatal(err)
}
}()
c, err := CreateContainer(configPath)
if err != nil {
t.Fatal(err)
}
if err := c.set("", ""); err == nil {
t.Fatalf("should get error when key is empty")
}
if c.get("1") != nil {
t.Fatalf("should get %v but got %v", nil, c.get("key"))
}
// create
if err := c.set("1", "1"); err != nil {
t.Fatal(err)
}
// read
if c.get("1").(string) != "1" {
t.Fatalf("should get %s but got %s", "val-1", c.get("key"))
}
// invaliad set
if err := c.set("1.1", "1.1"); err != nil {
t.Fatal(err)
}
if c.get("1.1").(string) != "1.1" {
t.Fatalf("should get 1.1 but got %s", c.get("1.1"))
}
// update
if err := c.set("1", "11"); err != nil {
t.Fatal(err)
}
if c.get("1").(string) != "11" {
t.Fatalf("should get 11 but got %s", c.get("1").(string))
}
// nested set
if err := c.set("2.2.2.2", "2222"); err == nil {
t.Fatalf("should throw error since 2.2.2 not ready yet")
}
if err := c.set("2", map[string]interface{}{
"2": map[string]interface{}{
"2": "2",
},
}); err != nil {
t.Fatal(err)
}
if c.get("2.2.2").(string) != "2" {
t.Fatalf("should get 2 but got %s", c.get("2.2.2"))
}
if err := c.set("2.2.2.2", "2222"); err != nil {
t.Fatal(err)
}
if c.get("2.2.2.2").(string) != "2222" {
t.Fatalf("should get 2222 but got %s", c.get("2.2.2.2"))
}
if err := c.set("2.2.2.1", "1111"); err != nil {
t.Fatal(err)
}
if c.get("2.2.2.1").(string) != "1111" {
t.Fatalf("should get 1111 but got %s", c.get("2.2.2.1"))
}
}

View File

@@ -18,7 +18,6 @@ import (
"github.com/apex/log"
dockerTypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
dockerTypesContainer "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/go-connections/nat"
"github.com/google/go-querystring/query"
@@ -32,10 +31,32 @@ import (
// API interact with dockerd http api
type API struct {
host string
port string
endpoint string
version string
}
// New a API
func New(host string, port string) *API {
return &API{
host: host,
port: port,
}
}
// Initialize an API
func (api *API) Initialize() error {
addr := api.host + ":" + api.port
v, err := version(addr)
if err != nil {
return err
}
endpoint := fmt.Sprintf("http://%s:%s/v%s", api.host, api.port, v)
api.endpoint = endpoint
return nil
}
// Create a API
func Create(host string, port string) (*API, error) {
addr := host + ":" + port
@@ -134,7 +155,8 @@ func (api *API) post(path string, body []byte, expectStatus int, v interface{})
// Version get version of docker engine
func (api *API) Version(ctx context.Context) (string, error) {
return version(api.endpoint)
addr := api.host + ":" + api.port
return version(addr)
}
func version(endpoint string) (string, error) {
@@ -397,12 +419,12 @@ func (api *API) StartContainer(ctx context.Context, name string, image string, b
portSet[port] = struct{}{}
portMap[port] = bindings
}
config := &dockerTypesContainer.Config{
config := &container.Config{
Image: image,
ExposedPorts: portSet,
}
hostConfig := &dockerTypesContainer.HostConfig{
hostConfig := &container.HostConfig{
AutoRemove: !fxConfig.DisableContainerAutoremove,
PortBindings: portMap,
}

View File

@@ -0,0 +1,164 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: runtimes.go
// Package mock_containerruntimes is a generated GoMock package.
package mock_containerruntimes
import (
context "context"
gomock "github.com/golang/mock/gomock"
types "github.com/metrue/fx/types"
reflect "reflect"
)
// MockContainerRuntime is a mock of ContainerRuntime interface
type MockContainerRuntime struct {
ctrl *gomock.Controller
recorder *MockContainerRuntimeMockRecorder
}
// MockContainerRuntimeMockRecorder is the mock recorder for MockContainerRuntime
type MockContainerRuntimeMockRecorder struct {
mock *MockContainerRuntime
}
// NewMockContainerRuntime creates a new mock instance
func NewMockContainerRuntime(ctrl *gomock.Controller) *MockContainerRuntime {
mock := &MockContainerRuntime{ctrl: ctrl}
mock.recorder = &MockContainerRuntimeMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockContainerRuntime) EXPECT() *MockContainerRuntimeMockRecorder {
return m.recorder
}
// BuildImage mocks base method
func (m *MockContainerRuntime) BuildImage(ctx context.Context, workdir, name string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BuildImage", ctx, workdir, name)
ret0, _ := ret[0].(error)
return ret0
}
// BuildImage indicates an expected call of BuildImage
func (mr *MockContainerRuntimeMockRecorder) BuildImage(ctx, workdir, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildImage", reflect.TypeOf((*MockContainerRuntime)(nil).BuildImage), ctx, workdir, name)
}
// PushImage mocks base method
func (m *MockContainerRuntime) PushImage(ctx context.Context, name string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PushImage", ctx, name)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// PushImage indicates an expected call of PushImage
func (mr *MockContainerRuntimeMockRecorder) PushImage(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushImage", reflect.TypeOf((*MockContainerRuntime)(nil).PushImage), ctx, name)
}
// InspectImage mocks base method
func (m *MockContainerRuntime) InspectImage(ctx context.Context, name string, img interface{}) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InspectImage", ctx, name, img)
ret0, _ := ret[0].(error)
return ret0
}
// InspectImage indicates an expected call of InspectImage
func (mr *MockContainerRuntimeMockRecorder) InspectImage(ctx, name, img interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InspectImage", reflect.TypeOf((*MockContainerRuntime)(nil).InspectImage), ctx, name, img)
}
// TagImage mocks base method
func (m *MockContainerRuntime) TagImage(ctx context.Context, name, tag string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "TagImage", ctx, name, tag)
ret0, _ := ret[0].(error)
return ret0
}
// TagImage indicates an expected call of TagImage
func (mr *MockContainerRuntimeMockRecorder) TagImage(ctx, name, tag interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TagImage", reflect.TypeOf((*MockContainerRuntime)(nil).TagImage), ctx, name, tag)
}
// StartContainer mocks base method
func (m *MockContainerRuntime) StartContainer(ctx context.Context, name, image string, bindings []types.PortBinding) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StartContainer", ctx, name, image, bindings)
ret0, _ := ret[0].(error)
return ret0
}
// StartContainer indicates an expected call of StartContainer
func (mr *MockContainerRuntimeMockRecorder) StartContainer(ctx, name, image, bindings interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartContainer", reflect.TypeOf((*MockContainerRuntime)(nil).StartContainer), ctx, name, image, bindings)
}
// StopContainer mocks base method
func (m *MockContainerRuntime) StopContainer(ctx context.Context, name string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StopContainer", ctx, name)
ret0, _ := ret[0].(error)
return ret0
}
// StopContainer indicates an expected call of StopContainer
func (mr *MockContainerRuntimeMockRecorder) StopContainer(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopContainer", reflect.TypeOf((*MockContainerRuntime)(nil).StopContainer), ctx, name)
}
// InspectContainer mocks base method
func (m *MockContainerRuntime) InspectContainer(ctx context.Context, name string, container interface{}) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InspectContainer", ctx, name, container)
ret0, _ := ret[0].(error)
return ret0
}
// InspectContainer indicates an expected call of InspectContainer
func (mr *MockContainerRuntimeMockRecorder) InspectContainer(ctx, name, container interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InspectContainer", reflect.TypeOf((*MockContainerRuntime)(nil).InspectContainer), ctx, name, container)
}
// ListContainer mocks base method
func (m *MockContainerRuntime) ListContainer(ctx context.Context, filter string) ([]types.Service, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListContainer", ctx, filter)
ret0, _ := ret[0].([]types.Service)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListContainer indicates an expected call of ListContainer
func (mr *MockContainerRuntimeMockRecorder) ListContainer(ctx, filter interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListContainer", reflect.TypeOf((*MockContainerRuntime)(nil).ListContainer), ctx, filter)
}
// Version mocks base method
func (m *MockContainerRuntime) Version(ctx context.Context) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Version", ctx)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Version indicates an expected call of Version
func (mr *MockContainerRuntimeMockRecorder) Version(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Version", reflect.TypeOf((*MockContainerRuntime)(nil).Version), ctx)
}

View File

@@ -52,6 +52,7 @@ func (ctx *Context) GetCliContext() *cli.Context {
// Set a value with name
func (ctx *Context) Set(name string, value interface{}) {
// nolint
newCtx := context.WithValue(ctx.Context, name, value)
ctx.Context = newCtx
}

View File

@@ -36,11 +36,26 @@ $ curl 127.0.0.1:2000
## Deploy a function onto remote host
* make sure your instance can be ssh login
* make sure you can ssh login to target host with root
Update `/etc/ssh/sshd_config` to allow login with root.
```
PermitRootLogin yes
```
Then restart sshd with,
```shell
$ sudo service sshd restart
```
* make sure your instance accept port 8866
[FYI](https://lightsail.aws.amazon.com/ls/docs/en_us/articles/understanding-firewall-and-port-mappings-in-amazon-lightsail)
then you can deploy function to remote host
```shell
DOCKER_REMOTE_HOST_ADDR=<your host> DOCKER_REMOTE_HOST_USER=<your user> DOCKER_REMOTE_HOST_PASSWORD=<your password> fx up -p 2000 test/functions/func.js
fx up --host root@<your host> test/functions/func.js
```

View File

@@ -6,48 +6,63 @@ import (
dockerTypes "github.com/docker/docker/api/types"
containerruntimes "github.com/metrue/fx/container_runtimes"
"github.com/metrue/fx/infra"
"github.com/metrue/fx/driver"
"github.com/metrue/fx/pkg/spinner"
"github.com/metrue/fx/types"
)
// Deployer manage container
type Deployer struct {
cli containerruntimes.ContainerRuntime
// Driver manage container
type Driver struct {
dockerClient containerruntimes.ContainerRuntime
}
// CreateClient create a docker instance
func CreateClient(client containerruntimes.ContainerRuntime) (d *Deployer, err error) {
return &Deployer{cli: client}, nil
// Options to initialize a fx docker driver
type Options struct {
DockerClient containerruntimes.ContainerRuntime
}
// New a fx docker driver
func New(options Options) *Driver {
return &Driver{
dockerClient: options.DockerClient,
}
}
// Ping check healty status of driver
func (d *Driver) Ping(ctx context.Context) error {
if _, err := d.dockerClient.Version(ctx); err != nil {
return err
}
return nil
}
// Deploy create a Docker container from given image, and bind the constants.FxContainerExposePort to given port
func (d *Deployer) Deploy(ctx context.Context, fn string, name string, image string, ports []types.PortBinding) (err error) {
func (d *Driver) Deploy(ctx context.Context, fn string, name string, image string, ports []types.PortBinding) (err error) {
spinner.Start("deploying " + name)
defer func() {
spinner.Stop("deploying "+name, err)
}()
return d.cli.StartContainer(ctx, name, image, ports)
return d.dockerClient.StartContainer(ctx, name, image, ports)
}
// Update a container
func (d *Deployer) Update(ctx context.Context, name string) error {
func (d *Driver) Update(ctx context.Context, name string) error {
return nil
}
// Destroy stop and remove container
func (d *Deployer) Destroy(ctx context.Context, name string) (err error) {
func (d *Driver) Destroy(ctx context.Context, name string) (err error) {
spinner.Start("destroying " + name)
defer func() {
spinner.Stop("destroying "+name, err)
}()
return d.cli.StopContainer(ctx, name)
return d.dockerClient.StopContainer(ctx, name)
}
// GetStatus get a service status
func (d *Deployer) GetStatus(ctx context.Context, name string) (types.Service, error) {
func (d *Driver) GetStatus(ctx context.Context, name string) (types.Service, error) {
var container dockerTypes.ContainerJSON
if err := d.cli.InspectContainer(ctx, name, &container); err != nil {
if err := d.dockerClient.InspectContainer(ctx, name, &container); err != nil {
return types.Service{}, err
}
@@ -55,6 +70,7 @@ func (d *Deployer) GetStatus(ctx context.Context, name string) (types.Service, e
ID: container.ID,
Name: container.Name,
}
for _, bindings := range container.NetworkSettings.Ports {
if len(bindings) > 0 {
binding := bindings[0]
@@ -76,20 +92,12 @@ func (d *Deployer) GetStatus(ctx context.Context, name string) (types.Service, e
return service, nil
}
// Ping check healty status of infra
func (d *Deployer) Ping(ctx context.Context) error {
if _, err := d.cli.Version(ctx); err != nil {
return err
}
return nil
}
// List services
func (d *Deployer) List(ctx context.Context, name string) (svcs []types.Service, err error) {
func (d *Driver) List(ctx context.Context, name string) (svcs []types.Service, err error) {
// FIXME support remote host
return d.cli.ListContainer(ctx, name)
return d.dockerClient.ListContainer(ctx, name)
}
var (
_ infra.Deployer = &Deployer{}
_ driver.Driver = &Driver{}
)

View File

@@ -0,0 +1,94 @@
package docker
import (
"context"
"errors"
"testing"
"github.com/golang/mock/gomock"
dockerMock "github.com/metrue/fx/container_runtimes/mocks"
"github.com/metrue/fx/types"
)
func TestDriverPing(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
dockerClient := dockerMock.NewMockContainerRuntime(ctrl)
n := New(Options{
DockerClient: dockerClient,
})
ctx := context.Background()
dockerClient.EXPECT().Version(ctx).Return("", nil)
if err := n.Ping(ctx); err != nil {
t.Fatal(err)
}
}
func TestDriverDeploy(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
dockerClient := dockerMock.NewMockContainerRuntime(ctrl)
n := New(Options{
DockerClient: dockerClient,
})
ctx := context.Background()
fn := "fn"
name := "name"
image := "image"
ports := []types.PortBinding{}
dockerClient.EXPECT().StartContainer(ctx, name, image, ports).Return(nil)
if err := n.Deploy(ctx, fn, name, image, ports); err != nil {
t.Fatal(err)
}
}
func TestDriverDestroy(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
dockerClient := dockerMock.NewMockContainerRuntime(ctrl)
n := New(Options{
DockerClient: dockerClient,
})
ctx := context.Background()
name := "name"
dockerClient.EXPECT().StopContainer(ctx, name).Return(nil)
if err := n.Destroy(ctx, name); err != nil {
t.Fatal(err)
}
}
func TestDriverGetStatus(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
dockerClient := dockerMock.NewMockContainerRuntime(ctrl)
n := New(Options{
DockerClient: dockerClient,
})
ctx := context.Background()
name := "name"
err := errors.New("no such container")
dockerClient.EXPECT().InspectContainer(ctx, name, gomock.Any()).Return(err)
if _, err := n.GetStatus(ctx, name); err == nil {
t.Fatalf("should get error")
}
}
func TestList(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
dockerClient := dockerMock.NewMockContainerRuntime(ctrl)
n := New(Options{
DockerClient: dockerClient,
})
ctx := context.Background()
name := "name"
dockerClient.EXPECT().ListContainer(ctx, name).Return(nil, nil)
if _, err := n.List(ctx, name); err != nil {
t.Fatal(err)
}
}

View File

@@ -1,4 +1,4 @@
package infra
package driver
import (
"context"
@@ -6,17 +6,8 @@ import (
"github.com/metrue/fx/types"
)
// Clouder cloud interface
type Clouder interface {
Provision() error
GetConfig() (string, error)
GetType() string
Dump() ([]byte, error)
IsHealth() (bool, error)
}
// Deployer deploy interface
type Deployer interface {
// Driver fx function running driver
type Driver interface {
Deploy(ctx context.Context, fn string, name string, image string, bindings []types.PortBinding) error
Destroy(ctx context.Context, name string) error
Update(ctx context.Context, name string) error
@@ -24,8 +15,3 @@ type Deployer interface {
List(ctx context.Context, name string) ([]types.Service, error)
Ping(ctx context.Context) error
}
// Infra infrastructure provision interface
type Infra interface {
Deployer
}

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"os"
"github.com/metrue/fx/infra"
"github.com/metrue/fx/driver"
"github.com/metrue/fx/pkg/spinner"
"github.com/metrue/fx/types"
"k8s.io/client-go/kubernetes"
@@ -37,6 +37,11 @@ func Create(kubeconfig string) (*K8S, error) {
return &K8S{clientset}, nil
}
// Provision TODO may need support manually k8s cluster creation
func (k *K8S) Provision(ctx context.Context, isRemote bool) error {
return nil
}
// Deploy a image to be a service
func (k *K8S) Deploy(
ctx context.Context,
@@ -186,5 +191,5 @@ func (k *K8S) Ping(ctx context.Context) error {
}
var (
_ infra.Deployer = &K8S{}
_ driver.Driver = &K8S{}
)

View File

@@ -6,7 +6,6 @@ import (
"github.com/metrue/fx/types"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -29,7 +28,7 @@ func generateDeploymentSpec(
Name: "fx-placeholder-container-name",
Image: image,
Ports: ports,
ImagePullPolicy: v1.PullIfNotPresent,
ImagePullPolicy: apiv1.PullIfNotPresent,
}
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{

View File

@@ -2,7 +2,6 @@ package k8s
import (
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
)
@@ -64,7 +63,7 @@ func injectInitContainer(name string, deployment *appsv1.Deployment) *appsv1.Dep
},
},
}
deployment.Spec.Template.Spec.InitContainers = []apiv1.Container{initContainer}
deployment.Spec.Template.Spec.InitContainers = []v1.Container{initContainer}
deployment.Spec.Template.Spec.Volumes = volumes
return deployment
}

135
driver/mocks/driver.go Normal file
View File

@@ -0,0 +1,135 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: driver.go
// Package mock_driver is a generated GoMock package.
package mock_driver
import (
context "context"
gomock "github.com/golang/mock/gomock"
types "github.com/metrue/fx/types"
reflect "reflect"
)
// MockDriver is a mock of Driver interface
type MockDriver struct {
ctrl *gomock.Controller
recorder *MockDriverMockRecorder
}
// MockDriverMockRecorder is the mock recorder for MockDriver
type MockDriverMockRecorder struct {
mock *MockDriver
}
// NewMockDriver creates a new mock instance
func NewMockDriver(ctrl *gomock.Controller) *MockDriver {
mock := &MockDriver{ctrl: ctrl}
mock.recorder = &MockDriverMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockDriver) EXPECT() *MockDriverMockRecorder {
return m.recorder
}
// Deploy mocks base method
func (m *MockDriver) Deploy(ctx context.Context, fn, name, image string, bindings []types.PortBinding) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Deploy", ctx, fn, name, image, bindings)
ret0, _ := ret[0].(error)
return ret0
}
// Deploy indicates an expected call of Deploy
func (mr *MockDriverMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockDriver)(nil).Deploy), ctx, fn, name, image, bindings)
}
// Provision mocks base method
func (m *MockDriver) Provision(ctx context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Provision", ctx)
ret0, _ := ret[0].(error)
return ret0
}
// Provision indicates an expected call of Provision
func (mr *MockDriverMockRecorder) Provision(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provision", reflect.TypeOf((*MockDriver)(nil).Provision), ctx)
}
// Destroy mocks base method
func (m *MockDriver) Destroy(ctx context.Context, name string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Destroy", ctx, name)
ret0, _ := ret[0].(error)
return ret0
}
// Destroy indicates an expected call of Destroy
func (mr *MockDriverMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockDriver)(nil).Destroy), ctx, name)
}
// Update mocks base method
func (m *MockDriver) Update(ctx context.Context, name string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, name)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update
func (mr *MockDriverMockRecorder) Update(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDriver)(nil).Update), ctx, name)
}
// GetStatus mocks base method
func (m *MockDriver) GetStatus(ctx context.Context, name string) (types.Service, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetStatus", ctx, name)
ret0, _ := ret[0].(types.Service)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetStatus indicates an expected call of GetStatus
func (mr *MockDriverMockRecorder) GetStatus(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockDriver)(nil).GetStatus), ctx, name)
}
// List mocks base method
func (m *MockDriver) List(ctx context.Context, name string) ([]types.Service, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, name)
ret0, _ := ret[0].([]types.Service)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// List indicates an expected call of List
func (mr *MockDriverMockRecorder) List(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockDriver)(nil).List), ctx, name)
}
// Ping mocks base method
func (m *MockDriver) Ping(ctx context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Ping", ctx)
ret0, _ := ret[0].(error)
return ret0
}
// Ping indicates an expected call of Ping
func (mr *MockDriverMockRecorder) Ping(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockDriver)(nil).Ping), ctx)
}

View File

@@ -1,4 +1,4 @@
package infra
package driver
import (
"fmt"

View File

@@ -1,4 +1,4 @@
package infra
package driver
// Sudo append sudo when user is not root
func Sudo(cmd string, user string) string {

185
fx.go
View File

@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"os"
"os/user"
"regexp"
"github.com/apex/log"
@@ -13,11 +14,12 @@ import (
"github.com/metrue/fx/context"
"github.com/metrue/fx/handlers"
"github.com/metrue/fx/middlewares"
"github.com/mitchellh/go-homedir"
"github.com/urfave/cli"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
)
const version = "0.9.3"
const version = "0.9.34"
func init() {
go checkForUpdate()
@@ -77,61 +79,18 @@ func main() {
fmt.Println(aurora.Red("*****************"))
}
}()
user, err := user.Current()
if err != nil {
panic(err)
}
defaultHost := user.Username + "@localhost"
defaultSSHKeyFile, err := homedir.Expand("~/.ssh/id_rsa")
if err != nil {
panic(err)
}
app.Commands = []cli.Command{
{
Name: "infra",
Usage: "manage infrastructure",
Subcommands: []cli.Command{
{
Name: "create",
Usage: "create a infra for fx service",
Flags: []cli.Flag{
cli.StringFlag{
Name: "type, t",
Usage: "infracture type, 'docker', 'k8s' and 'k3s' support",
},
cli.StringFlag{
Name: "name, n",
Usage: "name to identify the infrastructure",
},
cli.StringFlag{
Name: "host",
Usage: "user and ip of your host, eg. 'root@182.12.1.12'",
},
cli.StringFlag{
Name: "master",
Usage: "serve as master node in K3S cluster, eg. 'root@182.12.1.12'",
},
cli.StringFlag{
Name: "agents",
Usage: "serve as agent node in K3S cluster, eg. 'root@187.1. 2. 3,root@123.3.2.1'",
},
},
Action: handle(
middlewares.LoadConfig,
handlers.Setup,
),
},
{
Name: "list",
Usage: "list all infrastructures",
Action: handle(
middlewares.LoadConfig,
handlers.ListInfra,
),
},
{
Name: "use",
Usage: "set current context to target cloud with given name",
Action: handle(
middlewares.LoadConfig,
handlers.UseInfra,
),
},
},
},
{
Name: "up",
Usage: "deploy a function",
@@ -146,6 +105,25 @@ func main() {
Name: "port, p",
Usage: "port number",
},
cli.StringFlag{
Name: "host, H",
Usage: "target host, <user>@<host>",
Value: defaultHost,
},
cli.StringFlag{
Name: "ssh_port, P",
Usage: "SSH port for target host",
Value: "22",
},
cli.StringFlag{
Name: "ssh_key, K",
Usage: "SSH key file for login target host",
Value: defaultSSHKeyFile,
},
cli.StringFlag{
Name: "kubeconf, C",
Usage: "kubeconf of kubernetes cluster",
},
cli.BoolFlag{
Name: "healthcheck, hc",
Usage: "do a health check after service up",
@@ -156,11 +134,11 @@ func main() {
},
},
Action: handle(
middlewares.LoadConfig,
middlewares.Provision,
middlewares.Parse("up"),
middlewares.Language(),
middlewares.Binding,
middlewares.SSH,
middlewares.Driver,
middlewares.Build,
handlers.Up,
),
@@ -169,10 +147,31 @@ func main() {
Name: "down",
Usage: "destroy a service",
ArgsUsage: "[service 1, service 2, ....]",
Flags: []cli.Flag{
cli.StringFlag{
Name: "ssh_port, P",
Usage: "SSH port for target host",
Value: "22",
},
cli.StringFlag{
Name: "ssh_key, K",
Usage: "SSH key file for login target host",
Value: defaultSSHKeyFile,
},
cli.StringFlag{
Name: "host, H",
Usage: "target host, <user>@<host>",
Value: defaultHost,
},
cli.StringFlag{
Name: "kubeconf, C",
Usage: "kubeconf of kubernetes cluster",
},
},
Action: handle(
middlewares.Parse("down"),
middlewares.LoadConfig,
middlewares.Provision,
middlewares.SSH,
middlewares.Driver,
handlers.Down,
),
},
@@ -186,25 +185,33 @@ func main() {
Value: "table",
Usage: "output format, 'table' and 'JSON' supported",
},
cli.StringFlag{
Name: "ssh_port, P",
Usage: "SSH port for target host",
Value: "22",
},
cli.StringFlag{
Name: "ssh_key, K",
Usage: "SSH key file for login target host",
Value: defaultSSHKeyFile,
},
cli.StringFlag{
Name: "host, H",
Usage: "target host, <user>@<host>",
Value: defaultHost,
},
cli.StringFlag{
Name: "kubeconf, C",
Usage: "kubeconf of kubernetes cluster",
},
},
Action: handle(
middlewares.Parse("list"),
middlewares.LoadConfig,
middlewares.Provision,
middlewares.SSH,
middlewares.Driver,
handlers.List,
),
},
{
Name: "call",
Usage: "run a function instantly",
Flags: []cli.Flag{
cli.StringFlag{
Name: "host, H",
Usage: "fx server host, default is localhost",
},
},
Action: handle(handlers.Call),
},
{
Name: "image",
Usage: "manage image of service",
@@ -214,15 +221,36 @@ func main() {
Usage: "build a image",
Flags: []cli.Flag{
cli.StringFlag{
Name: "tag, t",
Usage: "image tag",
Name: "ssh_port, P",
Usage: "SSH port for target host",
Value: "22",
},
cli.StringFlag{
Name: "ssh_key, K",
Usage: "SSH key file for login target host",
Value: defaultSSHKeyFile,
},
cli.StringFlag{
Name: "host, H",
Usage: "target host, <user>@<host>",
Value: defaultHost,
},
cli.StringFlag{
Name: "kubeconf, C",
Usage: "kubeconf of kubernetes cluster",
},
cli.StringFlag{
Name: "name, n",
Usage: "image name",
Value: uuid.New().String(),
},
},
Action: handle(
middlewares.LoadConfig,
middlewares.Provision,
middlewares.Parse("image_build"),
middlewares.Language(),
middlewares.SSH,
middlewares.Driver,
middlewares.Build,
handlers.BuildImage,
),
},
@@ -236,8 +264,6 @@ func main() {
},
},
Action: handle(
middlewares.LoadConfig,
middlewares.Provision,
middlewares.Parse("image_export"),
middlewares.Language(),
handlers.ExportImage,
@@ -245,11 +271,6 @@ func main() {
},
},
},
{
Name: "doctor",
Usage: "health check for fx",
Action: handle(handlers.Doctor),
},
}
if err := app.Run(os.Args); err != nil {

11
go.mod
View File

@@ -6,17 +6,17 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/apex/log v1.1.2
github.com/briandowns/spinner v1.9.0
github.com/briandowns/spinner v1.10.0
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v0.0.0-20190313072916-46036c230805
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.3.3 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/gin-gonic/gin v1.4.0
github.com/gin-gonic/gin v1.6.2
github.com/gobuffalo/envy v1.8.1 // indirect
github.com/gobuffalo/packd v0.3.0
github.com/gobuffalo/packd v1.0.0
github.com/gobuffalo/packr/v2 v2.8.0
github.com/golang/mock v1.4.2
github.com/golang/mock v1.4.3
github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-querystring v1.0.0
github.com/google/uuid v1.1.1
@@ -24,7 +24,7 @@ require (
github.com/gorilla/mux v1.7.3 // indirect
github.com/imdario/mergo v0.3.7 // indirect
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434
github.com/metrue/go-ssh-client v0.0.0-20191219103445-1f07b67e2b29
github.com/metrue/go-ssh-client v0.0.0-20200317072149-19d54050aefd
github.com/mholt/archiver v3.1.1+incompatible
github.com/mitchellh/go-homedir v1.1.0
github.com/morikuni/aec v1.0.0 // indirect
@@ -36,7 +36,6 @@ require (
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf // indirect
github.com/pkg/errors v0.9.1
github.com/rogpeppe/go-internal v1.5.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.6.2
github.com/stretchr/testify v1.5.1

35
go.sum
View File

@@ -33,6 +33,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/briandowns/spinner v1.9.0 h1:+OMAisemaHar1hjuJ3Z2hIvNhQl9Y7GLPWUwwz2Pxo8=
github.com/briandowns/spinner v1.9.0/go.mod h1://Zf9tMcxfRUA36V23M6YGEAv+kECGfvpnLTnb8n4XQ=
github.com/briandowns/spinner v1.10.0 h1:753NIJC2NHmPyVoPVWS+wh9eDx5umqe2U+JgX+KoTag=
github.com/briandowns/spinner v1.10.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
@@ -45,6 +47,7 @@ github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -77,8 +80,12 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/gin-gonic/gin v1.6.2 h1:88crIK23zO6TqlQBt+f9FrPJNKm9ZEr7qjp9vl/d5TM=
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@@ -87,6 +94,13 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
@@ -120,11 +134,15 @@ github.com/golang/mock v1.4.1 h1:ocYkMQY5RrXTYgXl7ICpV0IXwlEQGwKIsery4gyXa1U=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.2 h1:fXIkPzOBCwDUPvYmOPzETABgbqpYlYNigQ2o64eMr5c=
github.com/golang/mock v1.4.2/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -174,6 +192,8 @@ github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBv
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
@@ -196,6 +216,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434 h1:im9kkmH0WWwxzegiv18gSUJbuXR9y028rXrWuPp6Jug=
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
@@ -217,11 +239,19 @@ github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
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/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/metrue/go-ssh-client v0.0.0-20191219103445-1f07b67e2b29 h1:ENoMPMVc24XbBuVZ7guZmTB/7MSd+vqOkImSu9UUiJw=
github.com/metrue/go-ssh-client v0.0.0-20191219103445-1f07b67e2b29/go.mod h1:aPG/JtXTyLliKDDlkv+nzHbSbz2p2CBMAjNJRK4uhzY=
github.com/metrue/go-ssh-client v0.0.0-20200317034720-e3f45ef1916d h1:vLJfcEJfjiL2LBGBoZbS1mcNjVBXxM4v2vZuUX5XiBE=
github.com/metrue/go-ssh-client v0.0.0-20200317034720-e3f45ef1916d/go.mod h1:aPG/JtXTyLliKDDlkv+nzHbSbz2p2CBMAjNJRK4uhzY=
github.com/metrue/go-ssh-client v0.0.0-20200317035802-7c3bc0c87929 h1:Z/+UhJcqXfrTgosjU8s91ASl8JOqh29/YJh8Tu26hw8=
github.com/metrue/go-ssh-client v0.0.0-20200317035802-7c3bc0c87929/go.mod h1:aPG/JtXTyLliKDDlkv+nzHbSbz2p2CBMAjNJRK4uhzY=
github.com/metrue/go-ssh-client v0.0.0-20200317072149-19d54050aefd h1:HoDa3tI6njhpyhu7aIcIfib8QugB66ILgYRLc5IuP6s=
github.com/metrue/go-ssh-client v0.0.0-20200317072149-19d54050aefd/go.mod h1:mgU+XR/ItF6PaQGpx0MthaaMP2dgGuck0IiXcrF3zMw=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
@@ -413,6 +443,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -443,6 +474,8 @@ golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191223224216-5a3cf8467b4e h1:z2Flw7sLy7DxaQi3zDOvI9X+Kb06+G9iZJlkEyHvujE=
golang.org/x/sys v0.0.0-20191223224216-5a3cf8467b4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -507,6 +540,8 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -1,11 +0,0 @@
package handlers
import (
"github.com/metrue/fx/context"
)
// Call command handle
func Call(ctx context.Contexter) error {
// TODO not supported
return nil
}

View File

@@ -1,26 +0,0 @@
package handlers
import (
"os"
"github.com/apex/log"
"github.com/metrue/fx/constants"
"github.com/metrue/fx/context"
"github.com/metrue/fx/doctor"
)
// Doctor command handle
func Doctor(ctx context.Contexter) error {
host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
user := os.Getenv("DOCKER_REMOTE_HOST_USER")
password := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD")
if host == "" {
host = "localhost"
}
if err := doctor.New(host, user, password).Start(); err != nil {
log.Warnf("machine %s is in dirty state: %v", host, err)
} else {
log.Infof("machine %s is in healthy state: %s", host, constants.CheckedSymbol)
}
return nil
}

View File

@@ -2,17 +2,23 @@ package handlers
import (
"github.com/metrue/fx/context"
"github.com/metrue/fx/infra"
"github.com/metrue/fx/driver"
)
// Down command handle
func Down(ctx context.Contexter) (err error) {
services := ctx.Get("services").([]string)
runner := ctx.Get("deployer").(infra.Deployer)
for _, svc := range services {
if err := runner.Destroy(ctx.GetContext(), svc); err != nil {
return err
for _, targetdriver := range []string{"docker_driver", "k8s_driver"} {
driver, ok := ctx.Get(targetdriver).(driver.Driver)
if !ok {
continue
}
for _, svc := range services {
if err := driver.Destroy(ctx.GetContext(), svc); err != nil {
return err
}
}
}
return nil
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/golang/mock/gomock"
mockCtx "github.com/metrue/fx/context/mocks"
mockDeployer "github.com/metrue/fx/infra/mocks"
mockDeployer "github.com/metrue/fx/driver/mocks"
)
func TestDown(t *testing.T) {
@@ -14,13 +14,14 @@ func TestDown(t *testing.T) {
defer ctrl.Finish()
ctx := mockCtx.NewMockContexter(ctrl)
deployer := mockDeployer.NewMockDeployer(ctrl)
driver := mockDeployer.NewMockDriver(ctrl)
services := []string{"sample-name"}
ctx.EXPECT().Get("services").Return(services)
ctx.EXPECT().Get("deployer").Return(deployer)
ctx.EXPECT().GetContext().Return(context.Background())
deployer.EXPECT().Destroy(gomock.Any(), services[0]).Return(nil)
ctx.EXPECT().Get("docker_driver").Return(driver)
ctx.EXPECT().Get("k8s_driver").Return(driver)
ctx.EXPECT().GetContext().Return(context.Background()).Times(2)
driver.EXPECT().Destroy(gomock.Any(), services[0]).Return(nil).Times(2)
if err := Down(ctx); err != nil {
t.Fatal(err)
}

View File

@@ -1,46 +1,17 @@
package handlers
import (
"fmt"
"os"
"time"
"github.com/apex/log"
"github.com/metrue/fx/bundle"
"github.com/metrue/fx/constants"
containerruntimes "github.com/metrue/fx/container_runtimes"
"github.com/metrue/fx/context"
"github.com/metrue/fx/hook"
"github.com/metrue/fx/pkg/spinner"
)
// BuildImage build image
func BuildImage(ctx context.Contexter) (err error) {
spinner.Start("building")
defer func() {
spinner.Stop("building", err)
}()
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
defer os.RemoveAll(workdir)
fn := ctx.Get("fn").(string)
deps := ctx.Get("deps").([]string)
language := ctx.Get("language").(string)
if err := bundle.Bundle(workdir, language, fn, deps...); err != nil {
return err
}
if err := hook.RunBeforeBuildHook(workdir); err != nil {
return err
}
docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
nameWithTag := ctx.Get("tag").(string) + ":latest"
if err := docker.BuildImage(ctx.GetContext(), workdir, nameWithTag); err != nil {
return err
}
log.Infof("image built: %s %v", nameWithTag, constants.CheckedSymbol)
image := ctx.Get("image").(string)
log.Infof("image built: %s %v", image, constants.CheckedSymbol)
return nil
}

View File

@@ -1,113 +0,0 @@
package handlers
import (
"fmt"
"path/filepath"
"strings"
"github.com/metrue/fx/config"
"github.com/metrue/fx/context"
dockerInfra "github.com/metrue/fx/infra/docker"
k8sInfra "github.com/metrue/fx/infra/k8s"
"github.com/metrue/fx/pkg/spinner"
)
func setupK8S(configDir string, name, masterInfo string, agentsInfo string) ([]byte, error) {
info := strings.Split(masterInfo, "@")
if len(info) != 2 {
return nil, fmt.Errorf("incorrect master info, should be <user>@<ip> format")
}
master, err := k8sInfra.CreateNode(info[1], info[0], "k3s_master", "master")
if err != nil {
return nil, err
}
nodes := []k8sInfra.Noder{master}
if agentsInfo != "" {
agentsInfoList := strings.Split(agentsInfo, ",")
for idx, agent := range agentsInfoList {
info := strings.Split(agent, "@")
if len(info) != 2 {
return nil, fmt.Errorf("incorrect agent info, should be <user>@<ip> format")
}
node, err := k8sInfra.CreateNode(info[1], info[0], "k3s_agent", fmt.Sprintf("agent-%d", idx))
if err != nil {
return nil, err
}
nodes = append(nodes, node)
}
}
kubeconfigPath := filepath.Join(configDir, name+".kubeconfig")
cloud := k8sInfra.NewCloud(kubeconfigPath, nodes...)
if err := cloud.Provision(); err != nil {
return nil, err
}
return cloud.Dump()
}
func setupDocker(hostInfo string, name string) ([]byte, error) {
info := strings.Split(hostInfo, "@")
if len(info) != 2 {
return nil, fmt.Errorf("incorrect master info, should be <user>@<ip> format")
}
user := info[0]
host := info[1]
cloud, err := dockerInfra.Create(host, user, name)
if err != nil {
return nil, err
}
if err := cloud.Provision(); err != nil {
return nil, err
}
return cloud.Dump()
}
// Setup infra
func Setup(ctx context.Contexter) (err error) {
const task = "setup infra"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
cli := ctx.GetCliContext()
typ := cli.String("type")
name := cli.String("name")
if name == "" {
return fmt.Errorf("name required")
}
if typ == "docker" {
if cli.String("host") == "" {
return fmt.Errorf("host required, eg. 'root@123.1.2.12'")
}
} else if typ == "k8s" {
if cli.String("master") == "" {
return fmt.Errorf("master required, eg. 'root@123.1.2.12'")
}
} else {
return fmt.Errorf("invalid type, 'docker' and 'k8s' support")
}
fxConfig := ctx.Get("config").(*config.Config)
switch strings.ToLower(typ) {
case "k8s":
dir, err := fxConfig.Dir()
if err != nil {
return err
}
kubeconf, err := setupK8S(dir, name, cli.String("master"), cli.String("agents"))
if err != nil {
return err
}
return fxConfig.AddCloud(name, kubeconf)
case "docker":
config, err := setupDocker(cli.String("host"), name)
if err != nil {
return err
}
return fxConfig.AddCloud(name, config)
}
return nil
}

View File

@@ -2,20 +2,27 @@ package handlers
import (
"github.com/metrue/fx/context"
"github.com/metrue/fx/infra"
"github.com/metrue/fx/driver"
"github.com/metrue/fx/pkg/renderrer"
)
// List command handle
func List(ctx context.Contexter) (err error) {
cli := ctx.GetCliContext()
deployer := ctx.Get("deployer").(infra.Deployer)
format := ctx.Get("format").(string)
services, err := deployer.List(ctx.GetContext(), cli.Args().First())
if err != nil {
return err
for _, targetdriver := range []string{"docker_driver", "k8s_driver"} {
driver, ok := ctx.Get(targetdriver).(driver.Driver)
if !ok {
continue
}
services, err := driver.List(ctx.GetContext(), cli.Args().First())
if err != nil {
return err
}
if err := renderrer.Render(services, format); err != nil {
return err
}
}
return renderrer.Render(services, format)
return nil
}

View File

@@ -1,19 +0,0 @@
package handlers
import (
"fmt"
"github.com/metrue/fx/config"
"github.com/metrue/fx/context"
)
// ListInfra list infra
func ListInfra(ctx context.Contexter) (err error) {
fxConfig := ctx.Get("config").(*config.Config)
conf, err := fxConfig.View()
if err != nil {
return err
}
fmt.Println(string(conf))
return nil
}

View File

@@ -3,7 +3,7 @@ package handlers
import (
"github.com/apex/log"
"github.com/metrue/fx/context"
"github.com/metrue/fx/infra"
"github.com/metrue/fx/driver"
"github.com/metrue/fx/pkg/renderrer"
"github.com/metrue/fx/types"
)
@@ -19,28 +19,37 @@ func Up(ctx context.Contexter) (err error) {
image = ""
}
name := ctx.Get("name").(string)
deployer := ctx.Get("deployer").(infra.Deployer)
bindings := ctx.Get("bindings").([]types.PortBinding)
force := ctx.Get("force").(bool)
if force && name != "" {
if err := deployer.Destroy(ctx.GetContext(), name); err != nil {
log.Warnf("destroy service %s failed: %v", name, err)
for _, targetdriver := range []string{"docker_driver", "k8s_driver"} {
driver, ok := ctx.Get(targetdriver).(driver.Driver)
if !ok {
continue
}
if force && name != "" {
if err := driver.Destroy(ctx.GetContext(), name); err != nil {
log.Warnf("destroy service %s failed: %v", name, err)
}
}
if err := driver.Deploy(
ctx.GetContext(),
fn,
name,
image,
bindings,
); err != nil {
return err
}
service, err := driver.GetStatus(ctx.GetContext(), name)
if err != nil {
return err
}
if err := renderrer.Render([]types.Service{service}, "table"); err != nil {
return err
}
}
if err := deployer.Deploy(
ctx.GetContext(),
fn,
name,
image,
bindings,
); err != nil {
return err
}
service, err := deployer.GetStatus(ctx.GetContext(), name)
if err != nil {
return err
}
return renderrer.Render([]types.Service{service}, "table")
return nil
}

View File

@@ -6,7 +6,7 @@ import (
"github.com/golang/mock/gomock"
mockCtx "github.com/metrue/fx/context/mocks"
mockDeployer "github.com/metrue/fx/infra/mocks"
mockDeployer "github.com/metrue/fx/driver/mocks"
"github.com/metrue/fx/types"
)
@@ -16,7 +16,7 @@ func TestUp(t *testing.T) {
defer ctrl.Finish()
ctx := mockCtx.NewMockContexter(ctrl)
deployer := mockDeployer.NewMockDeployer(ctrl)
driver := mockDeployer.NewMockDriver(ctrl)
bindings := []types.PortBinding{}
name := "sample-name"
@@ -24,18 +24,19 @@ func TestUp(t *testing.T) {
data := "sample-data"
ctx.EXPECT().Get("name").Return(name)
ctx.EXPECT().Get("image").Return(image)
ctx.EXPECT().Get("deployer").Return(deployer)
ctx.EXPECT().Get("docker_driver").Return(driver)
ctx.EXPECT().Get("k8s_driver").Return(driver)
ctx.EXPECT().Get("bindings").Return(bindings)
ctx.EXPECT().Get("data").Return(data)
ctx.EXPECT().Get("force").Return(false)
ctx.EXPECT().GetContext().Return(context.Background()).Times(2)
deployer.EXPECT().Deploy(gomock.Any(), data, name, image, bindings).Return(nil)
deployer.EXPECT().GetStatus(gomock.Any(), name).Return(types.Service{
ctx.EXPECT().GetContext().Return(context.Background()).Times(4)
driver.EXPECT().Deploy(gomock.Any(), data, name, image, bindings).Return(nil).Times(2)
driver.EXPECT().GetStatus(gomock.Any(), name).Return(types.Service{
ID: "id-1",
Name: name,
Host: "127.0.0.1",
Port: 2100,
}, nil)
}, nil).Times(2)
if err := Up(ctx); err != nil {
t.Fatal(err)
}
@@ -46,7 +47,7 @@ func TestUp(t *testing.T) {
defer ctrl.Finish()
ctx := mockCtx.NewMockContexter(ctrl)
deployer := mockDeployer.NewMockDeployer(ctrl)
driver := mockDeployer.NewMockDriver(ctrl)
bindings := []types.PortBinding{}
name := "sample-name"
@@ -54,19 +55,20 @@ func TestUp(t *testing.T) {
data := "sample-data"
ctx.EXPECT().Get("name").Return(name)
ctx.EXPECT().Get("image").Return(image)
ctx.EXPECT().Get("deployer").Return(deployer)
ctx.EXPECT().Get("docker_driver").Return(driver)
ctx.EXPECT().Get("k8s_driver").Return(driver)
ctx.EXPECT().Get("bindings").Return(bindings)
ctx.EXPECT().Get("data").Return(data)
ctx.EXPECT().Get("force").Return(true)
ctx.EXPECT().GetContext().Return(context.Background()).Times(3)
deployer.EXPECT().Deploy(gomock.Any(), data, name, image, bindings).Return(nil)
deployer.EXPECT().Destroy(gomock.Any(), name).Return(nil)
deployer.EXPECT().GetStatus(gomock.Any(), name).Return(types.Service{
ctx.EXPECT().GetContext().Return(context.Background()).Times(6)
driver.EXPECT().Deploy(gomock.Any(), data, name, image, bindings).Return(nil).Times(2)
driver.EXPECT().Destroy(gomock.Any(), name).Return(nil).Times(2)
driver.EXPECT().GetStatus(gomock.Any(), name).Return(types.Service{
ID: "id-1",
Name: name,
Host: "127.0.0.1",
Port: 2100,
}, nil)
}, nil).Times(2)
if err := Up(ctx); err != nil {
t.Fatal(err)
}

View File

@@ -1,13 +0,0 @@
package handlers
import (
"github.com/metrue/fx/config"
"github.com/metrue/fx/context"
)
// UseInfra use infra
func UseInfra(ctx context.Contexter) error {
fxConfig := ctx.Get("config").(*config.Config)
cli := ctx.GetCliContext()
return fxConfig.UseCloud(cli.Args().First())
}

View File

@@ -1,180 +0,0 @@
package docker
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/metrue/fx/infra"
"github.com/metrue/fx/types"
"github.com/metrue/go-ssh-client"
"github.com/mitchellh/go-homedir"
)
// Cloud define a docker host
type Cloud struct {
IP string `json:"ip"`
User string `json:"user"`
Name string `json:"name"`
Type string `json:"type"`
sshClient ssh.Clienter
}
// New new a docker cloud
func New(ip string, user string, name string) *Cloud {
return &Cloud{
IP: ip,
User: user,
Name: name,
Type: types.CloudTypeDocker,
}
}
// Create a docker node
func Create(ip string, user string, name string) (*Cloud, error) {
key, err := sshkey()
if err != nil {
return nil, err
}
port := sshport()
sshClient := ssh.New(ip).WithUser(user).WithKey(key).WithPort(port)
return &Cloud{
IP: ip,
User: user,
Name: name,
Type: types.CloudTypeDocker,
sshClient: sshClient,
}, nil
}
// Load a docker node from meta
func Load(meta []byte) (*Cloud, error) {
var cloud Cloud
if err := json.Unmarshal(meta, &cloud); err != nil {
return nil, err
}
key, err := sshkey()
if err != nil {
return nil, err
}
port := sshport()
sshClient := ssh.New(cloud.IP).WithUser(cloud.User).WithKey(key).WithPort(port)
cloud.sshClient = sshClient
return &cloud, nil
}
// Provision a host
func (c *Cloud) Provision() error {
if err := c.runCmd(infra.Scripts["docker_version"].(string)); err != nil {
if err := c.runCmd(infra.Scripts["install_docker"].(string)); err != nil {
return err
}
if err := c.runCmd(infra.Scripts["start_dockerd"].(string)); err != nil {
return err
}
}
if err := c.runCmd(infra.Scripts["check_fx_agent"].(string)); err != nil {
if err := c.runCmd(infra.Scripts["start_fx_agent"].(string)); err != nil {
return err
}
}
return nil
}
// GetType cloud type
func (c *Cloud) GetType() string {
return c.Type
}
func (c *Cloud) GetConfig() (string, error) {
data, err := json.Marshal(c)
if err != nil {
return "", err
}
return string(data), nil
}
func (c *Cloud) Dump() ([]byte, error) {
return json.Marshal(c)
}
// IsHealth check if cloud is in health
func (c *Cloud) IsHealth() (bool, error) {
if err := c.runCmd(infra.Scripts["check_fx_agent"].(string)); err != nil {
if err := c.runCmd(infra.Scripts["start_fx_agent"].(string)); err != nil {
return false, err
}
}
return true, nil
}
// NOTE only using for unit testing
func (c *Cloud) setsshClient(client ssh.Clienter) {
c.sshClient = client
}
// nolint:unparam
func (c *Cloud) runCmd(script string, options ...ssh.CommandOptions) error {
option := ssh.CommandOptions{}
if len(options) >= 1 {
option = options[0]
}
local := c.IP == "127.0.0.1" || c.IP == "localhost"
if local && os.Getenv("CI") == "" {
params := strings.Split(script, " ")
if len(params) == 0 {
return fmt.Errorf("invalid script: %s", script)
}
// nolint
cmd := exec.Command(params[0], params[1:]...)
cmd.Stdout = option.Stdout
cmd.Stderr = option.Stderr
err := cmd.Run()
if err != nil {
return err
}
return nil
}
return c.sshClient.RunCommand(script, option)
}
// NOTE the reason putting sshkey() and sshport here inside node.go is because
// ssh key and ssh port is related to node it self, we may extend this in future
func sshkey() (string, error) {
path := os.Getenv("SSH_KEY_FILE")
if path != "" {
absPath, err := filepath.Abs(path)
if err != nil {
return "", err
}
return absPath, nil
}
key, err := homedir.Expand("~/.ssh/id_rsa")
if err != nil {
return "", err
}
return key, nil
}
func sshport() string {
port := os.Getenv("SSH_PORT")
if port != "" {
return port
}
return "22"
}
var (
_ infra.Clouder = &Cloud{}
)

View File

@@ -1,158 +0,0 @@
package docker
import (
"fmt"
"os"
"testing"
"github.com/golang/mock/gomock"
"github.com/metrue/fx/infra"
"github.com/metrue/go-ssh-client"
sshMocks "github.com/metrue/go-ssh-client/mocks"
"github.com/mitchellh/go-homedir"
)
func TestCloudProvision(t *testing.T) {
t.Run("fx agent started", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
n := New("127.0.0.1", "fx", "master")
sshClient := sshMocks.NewMockClienter(ctrl)
n.setsshClient(sshClient)
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{}).Return(nil)
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}).Return(nil)
if err := n.Provision(); err != nil {
t.Fatal(err)
}
})
t.Run("fx agent not started", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
n := New("127.0.0.1", "fx", "master")
sshClient := sshMocks.NewMockClienter(ctrl)
n.setsshClient(sshClient)
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{}).Return(nil)
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}).Return(fmt.Errorf("no such container"))
sshClient.EXPECT().RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{}).Return(nil)
if err := n.Provision(); err != nil {
t.Fatal(err)
}
})
t.Run("docker not installed and fx agent not started", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
n := New("127.0.0.1", "fx", "master")
sshClient := sshMocks.NewMockClienter(ctrl)
n.setsshClient(sshClient)
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{}).Return(fmt.Errorf("no such command"))
sshClient.EXPECT().RunCommand(infra.Scripts["install_docker"].(string), ssh.CommandOptions{}).Return(nil)
sshClient.EXPECT().RunCommand(infra.Scripts["start_dockerd"].(string), ssh.CommandOptions{}).Return(nil)
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}).Return(fmt.Errorf("no such container"))
sshClient.EXPECT().RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{}).Return(nil)
if err := n.Provision(); err != nil {
t.Fatal(err)
}
})
}
func TestCloudIsHealth(t *testing.T) {
t.Run("agent started", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
cloud := New("127.0.0.1", "fx", "master")
sshClient := sshMocks.NewMockClienter(ctrl)
cloud.setsshClient(sshClient)
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}).Return(nil)
ok, err := cloud.IsHealth()
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatalf("cloud should be healthy")
}
})
t.Run("agent not started, and retart ok", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
cloud := New("127.0.0.1", "fx", "master")
sshClient := sshMocks.NewMockClienter(ctrl)
cloud.setsshClient(sshClient)
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}).Return(fmt.Errorf("fx agent not started"))
sshClient.EXPECT().RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{}).Return(nil)
ok, err := cloud.IsHealth()
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatalf("cloud should be healthy")
}
})
t.Run("agent not started, but restart failed", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
cloud := New("127.0.0.1", "fx", "master")
sshClient := sshMocks.NewMockClienter(ctrl)
cloud.setsshClient(sshClient)
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}).Return(fmt.Errorf("fx agent not started"))
sshClient.EXPECT().RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{}).Return(fmt.Errorf("fx agent started failed"))
ok, err := cloud.IsHealth()
if err == nil {
t.Fatal("should got failed starting")
}
if ok {
t.Fatalf("cloud should not be healthy")
}
})
}
func TestGetSSHKeyFile(t *testing.T) {
t.Run("defaut", func(t *testing.T) {
defau, err := sshkey()
if err != nil {
t.Fatal(err)
}
defaultPath, _ := homedir.Expand("~/.ssh/id_rsa")
if defau != defaultPath {
t.Fatalf("should get %s but got %s", defaultPath, defau)
}
})
t.Run("override from env", func(t *testing.T) {
os.Setenv("SSH_KEY_FILE", "/tmp/id_rsa")
keyFile, err := sshkey()
if err != nil {
t.Fatal(err)
}
if keyFile != "/tmp/id_rsa" {
t.Fatalf("should get %s but got %s", "/tmp/id_rsa", keyFile)
}
})
}
func TestGetSSHPort(t *testing.T) {
t.Run("defaut", func(t *testing.T) {
defau := sshport()
if defau != "22" {
t.Fatalf("should get %s but got %s", "22", defau)
}
})
t.Run("override from env", func(t *testing.T) {
os.Setenv("SSH_PORT", "2222")
defau := sshport()
if defau != "2222" {
t.Fatalf("should get %s but got %s", "2222", defau)
}
})
}

View File

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

View File

@@ -1,8 +0,0 @@
package docker
import containerruntimes "github.com/metrue/fx/container_runtimes"
// CreateDeployer create a deployer
func CreateDeployer(client containerruntimes.ContainerRuntime) (*Deployer, error) {
return &Deployer{cli: client}, nil
}

View File

@@ -1,270 +0,0 @@
package k8s
import (
"encoding/json"
"fmt"
"io/ioutil"
"github.com/metrue/fx/infra"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
)
// Cloud define a cloud
type Cloud struct {
// Define where is the location of kubeconf would be saved to
KubeConfig string `json:"config"`
Type string `json:"type"`
Nodes map[string]Noder `json:"nodes"`
token string
url string
}
// Load a cloud from config
func Load(meta []byte, messup ...func(n Noder) (Noder, error)) (*Cloud, error) {
var cloud Cloud
if err := json.Unmarshal(meta, &cloud); err != nil {
return nil, err
}
for name, n := range cloud.Nodes {
// NOTE messup function is just for unit testing
// we use it to replace the real not with mock node
if len(messup) > 0 {
node, err := messup[0](n)
if err != nil {
return nil, err
}
cloud.Nodes[name] = node
}
}
return &cloud, nil
}
// NewCloud new a cloud
func NewCloud(kubeconf string, node ...Noder) *Cloud {
nodes := map[string]Noder{}
for _, n := range node {
nodes[n.GetName()] = n
}
return &Cloud{
KubeConfig: kubeconf,
Type: types.CloudTypeK8S,
Nodes: nodes,
}
}
// Provision provision cloud
func (c *Cloud) Provision() error {
var master Noder
agents := []Noder{}
for _, n := range c.Nodes {
if n.GetType() == NodeTypeMaster {
master = n
} else {
agents = append(agents, n)
}
}
// when it's k3s cluster
if master != nil {
c.url = fmt.Sprintf("https://%s:6443", master.GetIP())
if err := master.Provision(map[string]string{}); err != nil {
return err
}
tok, err := master.GetToken()
if err != nil {
return err
}
c.token = tok
config, err := master.GetConfig()
if err != nil {
return err
}
if err := utils.EnsureFile(c.KubeConfig); err != nil {
return err
}
if err := ioutil.WriteFile(c.KubeConfig, []byte(config), 0666); err != nil {
return err
}
}
if len(agents) > 0 {
errCh := make(chan error, len(agents))
defer close(errCh)
for _, agent := range agents {
go func(node Noder) {
errCh <- node.Provision(map[string]string{
"url": c.url,
"token": c.token,
})
}(agent)
}
for range agents {
err := <-errCh
if err != nil {
return err
}
}
}
return nil
}
// AddNode a node
func (c *Cloud) AddNode(n Noder, skipProvision bool) error {
if !skipProvision {
if err := n.Provision(map[string]string{
"url": c.url,
"token": c.token,
}); err != nil {
return err
}
}
c.Nodes[n.GetName()] = n
return nil
}
// DeleteNode a node
func (c *Cloud) DeleteNode(name string) error {
node, ok := c.Nodes[name]
if ok {
delete(c.Nodes, name)
}
if node.GetType() == NodeTypeMaster && len(c.Nodes) > 0 {
return fmt.Errorf("could not delete master node since there is still agent node running")
}
return nil
}
// State get cloud state
func (c *Cloud) State() {}
// UnmarshalJSON unmarsha json
func (c *Cloud) UnmarshalJSON(data []byte) error {
var m map[string]interface{}
if err := json.Unmarshal(data, &m); err != nil {
return err
}
c.Nodes = make(map[string]Noder)
for k, v := range m {
if k == "nodes" {
nodes, ok := v.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid nodes data")
}
for name, n := range nodes {
node, ok := n.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid node data")
}
n, err := CreateNode(node["ip"].(string), node["user"].(string), node["type"].(string), node["name"].(string))
if err != nil {
return err
}
c.Nodes[name] = n
}
} else if k == "token" {
tok, ok := v.(string)
if ok {
c.token = tok
} else {
c.token = ""
}
} else if k == "config" {
config, ok := v.(string)
if ok {
c.KubeConfig = config
} else {
c.KubeConfig = ""
}
} else if k == "type" {
typ, ok := v.(string)
if ok {
c.Type = typ
} else {
c.Type = ""
}
} else if k == "url" {
url, ok := v.(string)
if ok {
c.url = url
} else {
c.url = ""
}
}
}
return nil
}
// MarshalJSON cloud information
func (c *Cloud) MarshalJSON() ([]byte, error) {
nodes := map[string]Node{}
for name, node := range c.Nodes {
nodes[name] = Node{
IP: node.GetIP(),
Type: node.GetType(),
User: node.GetUser(),
Name: node.GetName(),
}
}
body, err := json.Marshal(struct {
URL string `json:"url"`
KubeConfig string `json:"config"`
Type string `json:"type"`
Token string `json:"token"`
Nodes map[string]Node `json:"nodes"`
}{
KubeConfig: c.KubeConfig,
Type: c.Type,
Token: c.token,
URL: c.url,
Nodes: nodes,
})
if err != nil {
return nil, err
}
return body, nil
}
// GetType get type of cloud
func (c *Cloud) GetType() string {
return c.Type
}
// Dump cloud data
func (c *Cloud) Dump() ([]byte, error) {
return json.Marshal(c)
}
// GetConfig get config
func (c *Cloud) GetConfig() (string, error) {
if c.KubeConfig != "" {
return c.KubeConfig, nil
}
if err := c.Provision(); err != nil {
return "", err
}
return c.KubeConfig, nil
}
// IsHealth check if cloud is in health
func (c *Cloud) IsHealth() (bool, error) {
return true, nil
}
var (
_ infra.Clouder = &Cloud{}
)

View File

@@ -1,170 +0,0 @@
package k8s
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/golang/mock/gomock"
mock_infra "github.com/metrue/fx/infra/k8s/mocks"
)
func TestLoad(t *testing.T) {
t.Run("empty meta", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
var createNodeFn = func(n Noder) (Noder, error) {
return nil, nil
}
_, err := Load([]byte{}, createNodeFn)
if err == nil {
t.Fatalf("should load with error")
}
})
t.Run("only master node", func(t *testing.T) {
kubeconfig := "./kubeconfig.yml"
defer func() {
if err := os.RemoveAll("./kubeconfig.yml"); err != nil {
t.Fatal(err)
}
}()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
master := mock_infra.NewMockNoder(ctrl)
var createNodeFn = func(n Noder) (Noder, error) {
return master, nil
}
typ := NodeTypeMaster
name := "master"
ip := "127.0.0.1"
user := "testuser"
kubeconfContent := "sample-content"
master.EXPECT().GetName().Return(name)
master.EXPECT().GetType().Return(typ).Times(2)
master.EXPECT().GetIP().Return(ip).Times(2)
master.EXPECT().GetUser().Return(user)
master.EXPECT().GetConfig().Return(kubeconfContent, nil)
claud := &Cloud{
KubeConfig: kubeconfig,
Type: "k8s",
url: "",
token: "",
Nodes: map[string]Noder{"master-node": master},
}
meta, err := json.Marshal(claud)
if err != nil {
t.Fatal(err)
}
cloud, err := Load(meta, createNodeFn)
if err != nil {
t.Fatal(err)
}
if len(cloud.Nodes) != 1 {
t.Fatalf("should get %d but got %d", 1, len(cloud.Nodes))
}
master.EXPECT().Provision(map[string]string{}).Return(nil)
master.EXPECT().GetToken().Return("tok-1", nil)
if err := cloud.Provision(); err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadFile(claud.KubeConfig)
if err != nil {
t.Fatal(err)
}
if string(content) != kubeconfContent {
t.Fatalf("should get %s but got %s", kubeconfContent, content)
}
})
t.Run("one master node and one agent", func(t *testing.T) {
kubeconfig := "./kubeconfig.yml"
defer func() {
if err := os.RemoveAll("./kubeconfig.yml"); err != nil {
t.Fatal(err)
}
}()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
master := mock_infra.NewMockNoder(ctrl)
node := mock_infra.NewMockNoder(ctrl)
var createNodeFn = func(n Noder) (Noder, error) {
if n.GetType() == NodeTypeMaster {
return master, nil
}
return node, nil
}
typ := NodeTypeMaster
name := "master"
ip := "127.0.0.1"
user := "testuser"
kubeconfContent := "sample-config"
master.EXPECT().GetName().Return(name)
master.EXPECT().GetType().Return(typ).Times(2)
master.EXPECT().GetIP().Return(ip).Times(3)
master.EXPECT().GetConfig().Return(kubeconfContent, nil)
master.EXPECT().GetUser().Return(user)
nodeType := NodeTypeAgent
nodeName := "agent_name"
nodeIP := "12.12.12.12"
nodeUser := "testuser"
node.EXPECT().GetName().Return(nodeName)
node.EXPECT().GetType().Return(nodeType).Times(2)
node.EXPECT().GetIP().Return(nodeIP)
node.EXPECT().GetUser().Return(nodeUser)
url := fmt.Sprintf("https://%s:6443", master.GetIP())
tok := "tok-1"
claud := &Cloud{
KubeConfig: kubeconfig,
url: url,
token: tok,
Type: "k8s",
Nodes: map[string]Noder{"master-node": master, "agent-node": node},
}
meta, err := json.Marshal(claud)
if err != nil {
t.Fatal(err)
}
cloud, err := Load(meta, createNodeFn)
if err != nil {
t.Fatal(err)
}
if len(cloud.Nodes) != 2 {
t.Fatalf("should get %d but got %d", 2, len(cloud.Nodes))
}
master.EXPECT().Provision(map[string]string{}).Return(nil)
master.EXPECT().GetToken().Return(tok, nil)
node.EXPECT().Provision(map[string]string{
"url": cloud.url,
"token": cloud.token,
}).Return(nil)
if err := cloud.Provision(); err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadFile(claud.KubeConfig)
if err != nil {
t.Fatal(err)
}
if string(content) != kubeconfContent {
t.Fatalf("should get %s but got %s", kubeconfContent, content)
}
})
}
func TestProvision(t *testing.T) {}

View File

@@ -1,147 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: node.go
// Package mock_k8s is a generated GoMock package.
package mock_k8s
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockNoder is a mock of Noder interface
type MockNoder struct {
ctrl *gomock.Controller
recorder *MockNoderMockRecorder
}
// MockNoderMockRecorder is the mock recorder for MockNoder
type MockNoderMockRecorder struct {
mock *MockNoder
}
// NewMockNoder creates a new mock instance
func NewMockNoder(ctrl *gomock.Controller) *MockNoder {
mock := &MockNoder{ctrl: ctrl}
mock.recorder = &MockNoderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockNoder) EXPECT() *MockNoderMockRecorder {
return m.recorder
}
// Provision mocks base method
func (m *MockNoder) Provision(meta map[string]string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Provision", meta)
ret0, _ := ret[0].(error)
return ret0
}
// Provision indicates an expected call of Provision
func (mr *MockNoderMockRecorder) Provision(meta interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provision", reflect.TypeOf((*MockNoder)(nil).Provision), meta)
}
// GetConfig mocks base method
func (m *MockNoder) GetConfig() (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetConfig")
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetConfig indicates an expected call of GetConfig
func (mr *MockNoderMockRecorder) GetConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfig", reflect.TypeOf((*MockNoder)(nil).GetConfig))
}
// GetType mocks base method
func (m *MockNoder) GetType() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetType")
ret0, _ := ret[0].(string)
return ret0
}
// GetType indicates an expected call of GetType
func (mr *MockNoderMockRecorder) GetType() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetType", reflect.TypeOf((*MockNoder)(nil).GetType))
}
// GetName mocks base method
func (m *MockNoder) GetName() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetName")
ret0, _ := ret[0].(string)
return ret0
}
// GetName indicates an expected call of GetName
func (mr *MockNoderMockRecorder) GetName() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockNoder)(nil).GetName))
}
// GetUser mocks base method
func (m *MockNoder) GetUser() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUser")
ret0, _ := ret[0].(string)
return ret0
}
// GetUser indicates an expected call of GetUser
func (mr *MockNoderMockRecorder) GetUser() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockNoder)(nil).GetUser))
}
// GetToken mocks base method
func (m *MockNoder) GetToken() (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetToken")
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetToken indicates an expected call of GetToken
func (mr *MockNoderMockRecorder) GetToken() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetToken", reflect.TypeOf((*MockNoder)(nil).GetToken))
}
// GetIP mocks base method
func (m *MockNoder) GetIP() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIP")
ret0, _ := ret[0].(string)
return ret0
}
// GetIP indicates an expected call of GetIP
func (mr *MockNoderMockRecorder) GetIP() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIP", reflect.TypeOf((*MockNoder)(nil).GetIP))
}
// Dump mocks base method
func (m *MockNoder) Dump() map[string]string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Dump")
ret0, _ := ret[0].(map[string]string)
return ret0
}
// Dump indicates an expected call of Dump
func (mr *MockNoderMockRecorder) Dump() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dump", reflect.TypeOf((*MockNoder)(nil).Dump))
}

View File

@@ -1,216 +0,0 @@
package k8s
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/metrue/fx/infra"
"github.com/metrue/go-ssh-client"
"github.com/mitchellh/go-homedir"
)
const NodeTypeMaster = "k3s_master"
const NodeTypeAgent = "k3s_agent"
const NodeTypeDocker = "docker_agent"
// Noder node interface
type Noder interface {
Provision(meta map[string]string) error
GetConfig() (string, error)
GetType() string
GetName() string
GetUser() string
GetToken() (string, error)
GetIP() string
Dump() map[string]string
}
// Node define a node
type Node struct {
IP string `json:"ip"`
User string `json:"user"`
Type string `json:"type"`
Name string `json:"name"`
sshClient ssh.Clienter
}
// CreateNode create a node
func CreateNode(ip string, user string, typ string, name string) (*Node, error) {
key, err := sshkey()
if err != nil {
return nil, err
}
port := sshport()
sshClient := ssh.New(ip).WithUser(user).WithKey(key).WithPort(port)
return &Node{
IP: ip,
User: user,
Type: typ,
Name: name,
sshClient: sshClient,
}, nil
}
func (n *Node) runCmd(script string) error {
return n.sshClient.RunCommand(script, ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
})
}
// Provision provision node
func (n *Node) Provision(meta map[string]string) error {
if err := n.runCmd(infra.Scripts["docker_version"].(string)); err != nil {
if err := n.runCmd(infra.Scripts["install_docker"].(string)); err != nil {
return err
}
if err := n.runCmd(infra.Scripts["start_dockerd"].(string)); err != nil {
return err
}
}
if n.Type == NodeTypeMaster {
if err := n.runCmd(infra.Scripts["check_k3s_server"].(string)); err != nil {
cmd := infra.Scripts["setup_k3s_master"].(func(ip string) string)(n.IP)
if err := n.runCmd(cmd); err != nil {
return err
}
}
} else if n.Type == NodeTypeAgent {
if err := n.runCmd(infra.Scripts["check_k3s_agent"].(string)); err != nil {
cmd := infra.Scripts["setup_k3s_agent"].(func(url string, tok string) string)(meta["url"], meta["token"])
if err := n.runCmd(cmd); err != nil {
return err
}
}
}
return nil
}
// GetToken get token from master node
func (n *Node) GetToken() (string, error) {
if n.Type != NodeTypeMaster {
return "", fmt.Errorf("could not get token from a non-master node")
}
var outPipe bytes.Buffer
if err := n.sshClient.RunCommand(infra.Scripts["get_k3s_token"].(string), ssh.CommandOptions{Stdout: bufio.NewWriter(&outPipe)}); err != nil {
return "", err
}
return outPipe.String(), nil
}
// State get node state
func (n *Node) State() {}
// Dump node information to json
func (n *Node) Dump() map[string]string {
return map[string]string{
"ip": n.IP,
"name": n.Name,
"user": n.User,
"type": n.Type,
}
}
// GetType get node type
func (n *Node) GetType() string {
return n.Type
}
// GetName get node type
func (n *Node) GetName() string {
return n.Name
}
// GetIP get node type
func (n *Node) GetIP() string {
return n.IP
}
// GetUser get user
func (n *Node) GetUser() string {
return n.User
}
// GetConfig get config
func (n *Node) GetConfig() (string, error) {
if n.Type == NodeTypeMaster {
var outPipe bytes.Buffer
if err := n.sshClient.RunCommand(infra.Scripts["get_k3s_kubeconfig"].(string), ssh.CommandOptions{
Stdout: bufio.NewWriter(&outPipe),
}); err != nil {
return "", err
}
return string(rewriteKubeconfig(outPipe.String(), n.IP, "default")), nil
} else if n.Type == NodeTypeDocker {
data, err := json.Marshal(n.Dump())
if err != nil {
return "", err
}
return string(data), nil
}
return "", fmt.Errorf("no config for node type of %s", n.Type)
}
// NOTE only using for unit testing
func (n *Node) setsshClient(client ssh.Clienter) {
n.sshClient = client
}
// NOTE the reason putting sshkey() and sshport here inside node.go is because
// ssh key and ssh port is related to node it self, we may extend this in future
func sshkey() (string, error) {
path := os.Getenv("SSH_KEY_FILE")
if path != "" {
absPath, err := filepath.Abs(path)
if err != nil {
return "", err
}
return absPath, nil
}
key, err := homedir.Expand("~/.ssh/id_rsa")
if err != nil {
return "", err
}
return key, nil
}
func sshport() string {
port := os.Getenv("SSH_PORT")
if port != "" {
return port
}
return "22"
}
func rewriteKubeconfig(kubeconfig string, ip string, context string) []byte {
if context == "" {
// nolint
context = "default"
}
kubeconfigReplacer := strings.NewReplacer(
"127.0.0.1", ip,
"localhost", ip,
"default", context,
)
return []byte(kubeconfigReplacer.Replace(kubeconfig))
}
var (
_ Noder = &Node{}
)

View File

@@ -1,211 +0,0 @@
package k8s
import (
"fmt"
"os"
"testing"
"github.com/golang/mock/gomock"
"github.com/metrue/fx/infra"
"github.com/metrue/go-ssh-client"
sshMocks "github.com/metrue/go-ssh-client/mocks"
"github.com/mitchellh/go-homedir"
)
func TestGetSSHKeyFile(t *testing.T) {
t.Run("defaut", func(t *testing.T) {
defau, err := sshkey()
if err != nil {
t.Fatal(err)
}
defaultPath, _ := homedir.Expand("~/.ssh/id_rsa")
if defau != defaultPath {
t.Fatalf("should get %s but got %s", defaultPath, defau)
}
})
t.Run("override from env", func(t *testing.T) {
os.Setenv("SSH_KEY_FILE", "/tmp/id_rsa")
keyFile, err := sshkey()
if err != nil {
t.Fatal(err)
}
if keyFile != "/tmp/id_rsa" {
t.Fatalf("should get %s but got %s", "/tmp/id_rsa", keyFile)
}
})
}
func TestGetSSHPort(t *testing.T) {
t.Run("defaut", func(t *testing.T) {
defau := sshport()
if defau != "22" {
t.Fatalf("should get %s but got %s", "22", defau)
}
})
t.Run("override from env", func(t *testing.T) {
os.Setenv("SSH_PORT", "2222")
defau := sshport()
if defau != "2222" {
t.Fatalf("should get %s but got %s", "2222", defau)
}
})
}
func TestNode(t *testing.T) {
t.Run("master node already has docker and k3s server", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
n, err := CreateNode("127.0.0.1", "fx", NodeTypeMaster, "master")
if err != nil {
t.Fatal(err)
}
if n.sshClient == nil {
t.Fatal("ssh client should not be nil")
}
sshClient := sshMocks.NewMockClienter(ctrl)
n.setsshClient(sshClient)
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(nil)
sshClient.EXPECT().RunCommand(infra.Scripts["check_k3s_server"].(string), ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(nil)
if err := n.Provision(map[string]string{}); err != nil {
t.Fatal(err)
}
})
t.Run("master node no docker and k3s server", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
n, err := CreateNode("127.0.0.1", "fx", NodeTypeMaster, "master")
if err != nil {
t.Fatal(err)
}
if n.sshClient == nil {
t.Fatal("ssh client should not be nil")
}
sshClient := sshMocks.NewMockClienter(ctrl)
n.setsshClient(sshClient)
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(fmt.Errorf("no such command"))
sshClient.EXPECT().RunCommand(infra.Scripts["install_docker"].(string), ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(nil)
sshClient.EXPECT().RunCommand(infra.Scripts["start_dockerd"].(string), ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(nil)
sshClient.EXPECT().RunCommand(infra.Scripts["check_k3s_server"].(string), ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(fmt.Errorf("no such progress"))
cmd := infra.Scripts["setup_k3s_master"].(func(ip string) string)(n.IP)
sshClient.EXPECT().RunCommand(cmd, ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(nil)
if err := n.Provision(map[string]string{}); err != nil {
t.Fatal(err)
}
})
t.Run("agent node already has docker and k3s agent", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
n, err := CreateNode("127.0.0.1", "fx", NodeTypeAgent, "agent")
if err != nil {
t.Fatal(err)
}
if n.sshClient == nil {
t.Fatal("ssh client should not be nil")
}
sshClient := sshMocks.NewMockClienter(ctrl)
n.setsshClient(sshClient)
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(nil)
sshClient.EXPECT().RunCommand(infra.Scripts["check_k3s_agent"].(string), ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(nil)
if err := n.Provision(map[string]string{}); err != nil {
t.Fatal(err)
}
})
t.Run("agent node no docker and k3s agent", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
n, err := CreateNode("127.0.0.1", "fx", NodeTypeAgent, "agent")
if err != nil {
t.Fatal(err)
}
if n.sshClient == nil {
t.Fatal("ssh client should not be nil")
}
sshClient := sshMocks.NewMockClienter(ctrl)
n.setsshClient(sshClient)
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(fmt.Errorf("no such command"))
sshClient.EXPECT().RunCommand(infra.Scripts["install_docker"].(string), ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(nil)
sshClient.EXPECT().RunCommand(infra.Scripts["start_dockerd"].(string), ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(nil)
sshClient.EXPECT().RunCommand(infra.Scripts["check_k3s_agent"].(string), ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(fmt.Errorf("no such progress"))
url := "url-1"
token := "token-1"
cmd := infra.Scripts["setup_k3s_agent"].(func(url string, ip string) string)(url, token)
sshClient.EXPECT().RunCommand(cmd, ssh.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
Stderr: os.Stderr,
}).Return(nil)
if err := n.Provision(map[string]string{"url": url, "token": token}); err != nil {
t.Fatal(err)
}
})
}

View File

@@ -1,313 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: infra.go
// Package mock_infra is a generated GoMock package.
package mock_infra
import (
context "context"
gomock "github.com/golang/mock/gomock"
types "github.com/metrue/fx/types"
reflect "reflect"
)
// MockProvisioner is a mock of Provisioner interface
type MockProvisioner struct {
ctrl *gomock.Controller
recorder *MockProvisionerMockRecorder
}
// MockProvisionerMockRecorder is the mock recorder for MockProvisioner
type MockProvisionerMockRecorder struct {
mock *MockProvisioner
}
// NewMockProvisioner creates a new mock instance
func NewMockProvisioner(ctrl *gomock.Controller) *MockProvisioner {
mock := &MockProvisioner{ctrl: ctrl}
mock.recorder = &MockProvisionerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockProvisioner) EXPECT() *MockProvisionerMockRecorder {
return m.recorder
}
// Provision mocks base method
func (m *MockProvisioner) Provision() ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Provision")
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Provision indicates an expected call of Provision
func (mr *MockProvisionerMockRecorder) Provision() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provision", reflect.TypeOf((*MockProvisioner)(nil).Provision))
}
// HealthCheck mocks base method
func (m *MockProvisioner) HealthCheck() (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HealthCheck")
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// HealthCheck indicates an expected call of HealthCheck
func (mr *MockProvisionerMockRecorder) HealthCheck() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockProvisioner)(nil).HealthCheck))
}
// MockDeployer is a mock of Deployer interface
type MockDeployer struct {
ctrl *gomock.Controller
recorder *MockDeployerMockRecorder
}
// MockDeployerMockRecorder is the mock recorder for MockDeployer
type MockDeployerMockRecorder struct {
mock *MockDeployer
}
// NewMockDeployer creates a new mock instance
func NewMockDeployer(ctrl *gomock.Controller) *MockDeployer {
mock := &MockDeployer{ctrl: ctrl}
mock.recorder = &MockDeployerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockDeployer) EXPECT() *MockDeployerMockRecorder {
return m.recorder
}
// Deploy mocks base method
func (m *MockDeployer) Deploy(ctx context.Context, fn, name, image string, bindings []types.PortBinding) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Deploy", ctx, fn, name, image, bindings)
ret0, _ := ret[0].(error)
return ret0
}
// Deploy indicates an expected call of Deploy
func (mr *MockDeployerMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockDeployer)(nil).Deploy), ctx, fn, name, image, bindings)
}
// Destroy mocks base method
func (m *MockDeployer) Destroy(ctx context.Context, name string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Destroy", ctx, name)
ret0, _ := ret[0].(error)
return ret0
}
// Destroy indicates an expected call of Destroy
func (mr *MockDeployerMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockDeployer)(nil).Destroy), ctx, name)
}
// Update mocks base method
func (m *MockDeployer) Update(ctx context.Context, name string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, name)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update
func (mr *MockDeployerMockRecorder) Update(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockDeployer)(nil).Update), ctx, name)
}
// GetStatus mocks base method
func (m *MockDeployer) GetStatus(ctx context.Context, name string) (types.Service, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetStatus", ctx, name)
ret0, _ := ret[0].(types.Service)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetStatus indicates an expected call of GetStatus
func (mr *MockDeployerMockRecorder) GetStatus(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockDeployer)(nil).GetStatus), ctx, name)
}
// List mocks base method
func (m *MockDeployer) List(ctx context.Context, name string) ([]types.Service, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, name)
ret0, _ := ret[0].([]types.Service)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// List indicates an expected call of List
func (mr *MockDeployerMockRecorder) List(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockDeployer)(nil).List), ctx, name)
}
// Ping mocks base method
func (m *MockDeployer) Ping(ctx context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Ping", ctx)
ret0, _ := ret[0].(error)
return ret0
}
// Ping indicates an expected call of Ping
func (mr *MockDeployerMockRecorder) Ping(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockDeployer)(nil).Ping), ctx)
}
// MockInfra is a mock of Infra interface
type MockInfra struct {
ctrl *gomock.Controller
recorder *MockInfraMockRecorder
}
// MockInfraMockRecorder is the mock recorder for MockInfra
type MockInfraMockRecorder struct {
mock *MockInfra
}
// NewMockInfra creates a new mock instance
func NewMockInfra(ctrl *gomock.Controller) *MockInfra {
mock := &MockInfra{ctrl: ctrl}
mock.recorder = &MockInfraMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInfra) EXPECT() *MockInfraMockRecorder {
return m.recorder
}
// Provision mocks base method
func (m *MockInfra) Provision() ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Provision")
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Provision indicates an expected call of Provision
func (mr *MockInfraMockRecorder) Provision() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provision", reflect.TypeOf((*MockInfra)(nil).Provision))
}
// HealthCheck mocks base method
func (m *MockInfra) HealthCheck() (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HealthCheck")
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// HealthCheck indicates an expected call of HealthCheck
func (mr *MockInfraMockRecorder) HealthCheck() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HealthCheck", reflect.TypeOf((*MockInfra)(nil).HealthCheck))
}
// Deploy mocks base method
func (m *MockInfra) Deploy(ctx context.Context, fn, name, image string, bindings []types.PortBinding) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Deploy", ctx, fn, name, image, bindings)
ret0, _ := ret[0].(error)
return ret0
}
// Deploy indicates an expected call of Deploy
func (mr *MockInfraMockRecorder) Deploy(ctx, fn, name, image, bindings interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockInfra)(nil).Deploy), ctx, fn, name, image, bindings)
}
// Destroy mocks base method
func (m *MockInfra) Destroy(ctx context.Context, name string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Destroy", ctx, name)
ret0, _ := ret[0].(error)
return ret0
}
// Destroy indicates an expected call of Destroy
func (mr *MockInfraMockRecorder) Destroy(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Destroy", reflect.TypeOf((*MockInfra)(nil).Destroy), ctx, name)
}
// Update mocks base method
func (m *MockInfra) Update(ctx context.Context, name string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, name)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update
func (mr *MockInfraMockRecorder) Update(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockInfra)(nil).Update), ctx, name)
}
// GetStatus mocks base method
func (m *MockInfra) GetStatus(ctx context.Context, name string) (types.Service, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetStatus", ctx, name)
ret0, _ := ret[0].(types.Service)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetStatus indicates an expected call of GetStatus
func (mr *MockInfraMockRecorder) GetStatus(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStatus", reflect.TypeOf((*MockInfra)(nil).GetStatus), ctx, name)
}
// List mocks base method
func (m *MockInfra) List(ctx context.Context, name string) ([]types.Service, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, name)
ret0, _ := ret[0].([]types.Service)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// List indicates an expected call of List
func (mr *MockInfraMockRecorder) List(ctx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInfra)(nil).List), ctx, name)
}
// Ping mocks base method
func (m *MockInfra) Ping(ctx context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Ping", ctx)
ret0, _ := ret[0].(error)
return ret0
}
// Ping indicates an expected call of Ping
func (mr *MockInfraMockRecorder) Ping(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ping", reflect.TypeOf((*MockInfra)(nil).Ping), ctx)
}

View File

@@ -6,12 +6,12 @@ import (
"time"
"github.com/metrue/fx/bundle"
containerruntimes "github.com/metrue/fx/container_runtimes"
"github.com/metrue/fx/constants"
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
"github.com/metrue/fx/context"
"github.com/metrue/fx/hook"
"github.com/metrue/fx/packer"
"github.com/metrue/fx/pkg/spinner"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
)
@@ -42,6 +42,9 @@ func Build(ctx context.Contexter) (err error) {
fn := ctx.Get("fn").(string)
deps := ctx.Get("deps").([]string)
language := ctx.Get("language").(string)
host := ctx.Get("host").(string)
kubeconf := ctx.Get("kubeconf").(string)
name := ctx.Get("name").(string)
if err := bundle.Bundle(workdir, language, fn, deps...); err != nil {
return err
@@ -51,16 +54,12 @@ func Build(ctx context.Contexter) (err error) {
return err
}
cloudType := ctx.Get("cloud_type").(string)
name := ctx.Get("name").(string)
if cloudType == types.CloudTypeK8S {
data, err := packer.PackIntoK8SConfigMapFile(workdir)
if host != "" {
// TODO port should be configurable
docker, err := dockerHTTP.Create(host, constants.AgentPort)
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
}
@@ -68,9 +67,16 @@ func Build(ctx context.Contexter) (err error) {
if err := docker.TagImage(ctx.GetContext(), name, nameWithTag); err != nil {
return err
}
ctx.Set("image", nameWithTag)
}
if kubeconf != "" {
data, err := packer.PackIntoK8SConfigMapFile(workdir)
if err != nil {
return err
}
ctx.Set("data", data)
}
return nil
}

64
middlewares/driver.go Normal file
View File

@@ -0,0 +1,64 @@
package middlewares
import (
"fmt"
"runtime"
"time"
"github.com/apex/log"
"github.com/metrue/fx/constants"
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
"github.com/metrue/fx/context"
dockerDriver "github.com/metrue/fx/driver/docker"
k8sInfra "github.com/metrue/fx/driver/k8s"
"github.com/metrue/fx/provisioner"
darwin "github.com/metrue/fx/provisioner/darwin"
linux "github.com/metrue/fx/provisioner/linux"
"github.com/metrue/go-ssh-client"
)
// Driver initialize infrastructure driver
func Driver(ctx context.Contexter) (err error) {
host := ctx.Get("host").(string)
sshClient := ctx.Get("ssh").(ssh.Clienter)
kubeconf := ctx.Get("kubeconf").(string)
if host != "" {
// TODO port should be configurable
docker := dockerHTTP.New(host, constants.AgentPort)
driver := dockerDriver.New(dockerDriver.Options{
DockerClient: docker,
})
if err := driver.Ping(ctx.GetContext()); err != nil {
log.Infof("provisioning %s ...", host)
var provisioner provisioner.Provisioner
// TODO actually we should get os of target host, not the host of fx
// running on
if runtime.GOOS == "darwin" {
provisioner = darwin.New(sshClient)
} else {
provisioner = linux.New(sshClient)
}
isRemote := (host != "127.0.0.1" && host != "localhost")
if err := provisioner.Provision(ctx.GetContext(), isRemote); err != nil {
return err
}
time.Sleep(2 * time.Second)
}
if err := docker.Initialize(); err != nil {
return fmt.Errorf("initialize docker client failed: %s", err)
}
ctx.Set("docker_driver", driver)
}
if kubeconf != "" {
driver, err := k8sInfra.CreateDeployer(kubeconf)
if err != nil {
return err
}
ctx.Set("k8s_driver", driver)
}
return nil
}

View File

@@ -0,0 +1,72 @@
package middlewares
import (
"context"
"io/ioutil"
"os"
"testing"
"time"
"github.com/golang/mock/gomock"
mockCtx "github.com/metrue/fx/context/mocks"
"github.com/metrue/go-ssh-client"
sshMocks "github.com/metrue/go-ssh-client/mocks"
)
func TestDriver(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := mockCtx.NewMockContexter(ctrl)
kubeconf, err := ioutil.TempFile("", "*.kubeconf")
if err != nil {
t.Fatal(err)
}
config := `apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://kubernetes.docker.internal:6443
name: docker-desktop
contexts:
- context:
cluster: docker-desktop
user: docker-desktop
name: docker-desktop
- context:
cluster: docker-desktop
user: docker-desktop
name: docker-for-desktop
current-context: docker-desktop
kind: Config
preferences: {}
users:
- name: docker-desktop
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5RENDQWR5Z0F3SUJBZ0lJZmd6Rml2L0lKVzR3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T1RFeU1UWXdPREU0TWpkYUZ3MHlNVEF6TURrd016RTNNamRhTURZeApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sc3dHUVlEVlFRREV4SmtiMk5yWlhJdFptOXlMV1JsCmMydDBiM0F3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRQzdtQk9tdHArMWU3TEsKYzNnWDVLcHU2aWRvSTd2V3lmeXRpdGxXU3d3cHc0d1pmN0d6QnhncTdXS1l5WERBSjVNR1JIeElkekVQMHcyUQo2WjlpV2d4Yk9YS2NMRCtvc1dPSlR2azB2NzBGUmg5QUNTWmNYTTQrakxsUG1VNXNZY0xBNi9RK24yQitxc3o3Ckhwb0FzZlpieVFmV0MvTG9uUmY5QVVONmVLcjFuZzVSeGdqMDFQNnN2bHBjUy9BTjhLNjZTcFJkczVocGVUU2IKMUYwUXlERFhXT0w0QTNlZUhPZGIzaC9tT3dtOEdkb1dSbGhJQjV5enlKVzF0Ny9pNVVmZm9BLzFZT0pOc2pEUgo2ZElYUTF4djJWQ0IzZnNZc0Z3NWFqb2s0aDNYSlB4N04yUWxxeWlPeXBXVjYzdDc1QmtHR083SHUxWlhUa2RRCndjc0dnN3ZUQWdNQkFBR2pKekFsTUE0R0ExVWREd0VCL3dRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFZbEE0OXdwY0p5eUExbmlWK25UMlI2bTBURXNjMkdYTgpad0RXZFVrRWgwRHN2dGp2NUhqU1BCMmZiOUZ2V3VpcTI3YU1aTmVDdEt4WGlNcnpTUmh2YmNqS3pFQ0M2VVBDCkJQdUw3NysrZk42Rlh5eG9haXExbjRVcGhSNDl0azI4eEJORm1DTWdyKzFLOWExTDgrQ2RtaXNFRHAwKzVPQmgKYWhIUytSYk5pUmhBQ1YrazJSZ01tK0swemNvUm41c0pGS012SitCZWhuTEdUN0VLVjRFMnpOZkZiQUI0b0k3eAo0NmxsSVBKKzN1Uy9oUlEzNkR5bzZ0OUc3K204NXBKTmFFdkFJdmVxaXJ5VlRJbjF5T1BURXVFREVTM1FZaWxYCmJOZFFBNUNXcWxKRnhhZkNlNzMwNE5sbVVhTW0xZTROVHB2cmQyT2FzUDRKT1JPY2dJK1dVUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdTVnVHByYWZ0WHV5eW5ONEYrU3FidW9uYUNPNzFzbjhyWXJaVmtzTUtjT01HWCt4CnN3Y1lLdTFpbU1sd3dDZVRCa1I4U0hjeEQ5TU5rT21mWWxvTVd6bHluQ3cvcUxGamlVNzVOTCs5QlVZZlFBa20KWEZ6T1BveTVUNWxPYkdIQ3dPdjBQcDlnZnFyTSt4NmFBTEgyVzhrSDFndnk2SjBYL1FGRGVuaXE5WjRPVWNZSQo5TlQrckw1YVhFdndEZkN1dWtxVVhiT1lhWGswbTlSZEVNZ3cxMWppK0FOM25oem5XOTRmNWpzSnZCbmFGa1pZClNBZWNzOGlWdGJlLzR1VkgzNkFQOVdEaVRiSXcwZW5TRjBOY2I5bFFnZDM3R0xCY09XbzZKT0lkMXlUOGV6ZGsKSmFzb2pzcVZsZXQ3ZStRWkJoanV4N3RXVjA1SFVNSExCb083MHdJREFRQUJBb0lCQVFDZHV3REs3RUxkRldULwpWSmRsZjU3T0k1Tit2SXp6ekdIb2lSYTB0K1ZDT0dsVUIwb2lmWlNVZzRTamNyeWExS3VLV1lzbVl4R2RmSmVyCmdNUENyblExUDloZDk5YU93Smd3bTNadUk4bUs1YXJnN05DVVdIUVJvOEVzYkhyRUptN2FSNHJXSEt2RjFWY0UKem5ZdW4zUEZPUUtkdHU1SEo4OURyQXhRcmFVUlhxRE5OUUtKTGYydFNIWEJqNUtDam5jSXRBRXBwM2V3eXo3WQpnVmtlU0NrY3BRL0lZanJ0QS83c3dGR3IydWIrek9Jd1dKc3c4V01WcGZrbHZDVmR4VEk5YkdBQVRYNWdXMzZGCjhDSGRkUzd4UU5obEprbjNUVUVjeFU5UUw2TWEwNWdxWkcxVXBGaHpKQ0gybHlmQWhqQkZSbFkvcEN4YmJ1QXYKOFFGUjUyb0JBb0dCQU1HSzQ5bGFWUGhiak5Dc3J5N2dxUWVaYWZWbWQ1V2pMUzVrRFpYb0lQZkgxZWFVS253Ywp0cjd2SjJrNHZQRVR4eGdzOUtDMk5HSUFiUUIySlBFK3daYVo1OTk3NFdiUTNIaXVwWTBxMit3aFNseWpyWkxPCjI2MyswUzBLUVFDYUk1SkNseUJxTjFUOUdTeEMwYzlPTE9YMnpMbkMzUEthRHpZeHRyWlZGNDc5QW9HQkFQZ2gKdzhrSWxXYUdZd2RvMHNaeHowTzJoL2lmQ1U5ZmFGaTFpNjBrN2tBR2hxVVB4TCtyN0F5TDc4N2FlcnoxKzN5bApqVzB4YnFPZi9VTFZlaFBNWU40b0pFZkJpWmtCNnZ2Ri9HMllKeWg0MnJvQ2VDUmNLRVBqSjBST3FyQVI3Nks1CkZuMitWL2Nqc29Dd2ZsTjI4VC9LcUl4bjU3TWd1Wll1Ykt4Uk1qY1BBb0dBVWdvUnN4eDdVQnRlZ1VYeHJDbEcKL1JXbXVJTUt4Yjg1YzZTdHJaR01CL3dKUzRnYXlpbFJ2WFdhZXh1MTIyckt4aENvVVVkcXhPL3hSSFRRREFMUwpCSWlRcFViWnNMOXY5U2Z5dlBnaDZPSGpwNGtxRmtUaEVjd2wxclcyQUE5V2JMVVZZb1FqbUQ4QTRLWWlVWUdOCnZwenpBdnI2dFV0Z2oxUmJZc2FIQ2ZFQ2dZRUE3eDd5NDZoKythZW1oWHh5SzBXQVhSdnBteUlBUWRxSzMzcE4KR2RYT09DdFIxSDRHdUVRQkhmSTVieG5EVUppbysrMDdCckN0azhmWnRHK3Z6cWFWNzJHMTNPVFpLbmZic1RpUwpWRGRkL1RYQ2E2RjNrR3F6YndEWVZZNk9GVkdqb3loRlVYWitwUzlrbFhvQXM0U2JaME53L0tZaGR0R2hwK1lqCldraUJZT2NDZ1lCVmZDZGpCWDJvUmZIWkNJWm9NRVpuckNOS04rWWp1dHBnQTg5V1BEMmhPNlJLTG1DeG5GZ0oKbnFBUnUrZDBzMkhzaUErUzBKRXVMTVcxRjdnaml3Zm1zc3lSWkttWlNWaTZJZnFKUlFnSktDR3VzM1lPc2RHZQovSm5hTUlaRG1DRzNyNXpwZVl4VEowM0NIR2pReDl4dDRrallFU1F5MHdlVUtwelRITEhmWEE9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=`
if err := ioutil.WriteFile(kubeconf.Name(), []byte(config), 0644); err != nil {
t.Fatal(err)
}
defer os.Remove(kubeconf.Name())
sshClient := sshMocks.NewMockClienter(ctrl)
sshClient.EXPECT().Connectable(10*time.Second).Return(true, nil).Times(2)
sshClient.EXPECT().RunCommand("docker version", ssh.CommandOptions{
Timeout: 10 * time.Second,
}).Return(nil)
sshClient.EXPECT().RunCommand("docker inspect fx-agent", ssh.CommandOptions{
Timeout: 10 * time.Second,
}).Return(nil)
cntx := context.Background()
ctx.EXPECT().GetContext().Return(cntx).Times(2)
ctx.EXPECT().Get("ssh").Return(sshClient)
ctx.EXPECT().Get("host").Return("1.2.3.4")
ctx.EXPECT().Get("kubeconf").Return(kubeconf.Name())
// TODO mock http call
if err := Driver(ctx); err == nil {
t.Fatal("should failed on initial docker client dude to /version api not ready")
}
}

View File

@@ -1,16 +0,0 @@
package middlewares
import (
"github.com/metrue/fx/config"
"github.com/metrue/fx/context"
)
// LoadConfig load default config
func LoadConfig(ctx context.Contexter) error {
config, err := config.LoadDefault()
if err != nil {
return err
}
ctx.Set("config", config)
return nil
}

View File

@@ -2,12 +2,49 @@ package middlewares
import (
"fmt"
"os"
"strings"
"github.com/google/uuid"
"github.com/metrue/fx/context"
"github.com/metrue/fx/utils"
"github.com/urfave/cli"
)
type argsField struct {
Type string
Name string
Env string
}
func set(ctx context.Contexter, cli *cli.Context, fields []argsField) error {
for _, f := range fields {
if f.Type == "string" {
if f.Name == "host" {
addr := strings.Split(cli.String(f.Name), "@")
if len(addr) != 2 {
return fmt.Errorf("invalid host information, should be format of <user>@<ip>")
}
user := addr[0]
ip := addr[1]
ctx.Set("host", ip)
ctx.Set("user", user)
} else {
ctx.Set(f.Name, cli.String(f.Name))
}
} else if f.Type == "int" {
ctx.Set(f.Name, cli.Int(f.Name))
} else if f.Type == "bool" {
ctx.Set(f.Name, cli.Bool(f.Name))
}
if f.Env != "" && os.Getenv(f.Env) != "" {
ctx.Set(f.Name, os.Getenv(f.Env))
}
}
return nil
}
// Parse parse input
func Parse(action string) func(ctx context.Contexter) (err error) {
return func(ctx context.Contexter) error {
@@ -31,12 +68,18 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
}
ctx.Set("deps", deps)
name := cli.String("name")
ctx.Set("name", name)
port := cli.Int("port")
ctx.Set("port", port)
force := cli.Bool("force")
ctx.Set("force", force)
if err := set(ctx, cli, []argsField{
argsField{Name: "name", Type: "string"},
argsField{Name: "port", Type: "int"},
argsField{Name: "force", Type: "bool"},
argsField{Name: "ssh_port", Type: "string", Env: "SSH_PORT"},
argsField{Name: "ssh_key", Type: "string", Env: "SSH_KEY_FILE"},
argsField{Name: "host", Type: "string", Env: "FX_HOST"},
argsField{Name: "kubeconf", Type: "string", Env: "FX_KUBECONF"},
}); err != nil {
return err
}
case "down":
services := cli.Args()
if len(services) == 0 {
@@ -47,11 +90,30 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
svc = append(svc, service)
}
ctx.Set("services", svc)
if err := set(ctx, cli, []argsField{
argsField{Name: "ssh_port", Type: "string", Env: "SSH_PORT"},
argsField{Name: "ssh_key", Type: "string", Env: "SSH_KEY_FILE"},
argsField{Name: "host", Type: "string", Env: "FX_HOST"},
argsField{Name: "kubeconf", Type: "string", Env: "FX_KUBECONF"},
}); err != nil {
return err
}
case "list":
name := cli.Args().First()
ctx.Set("filter", name)
format := cli.String("format")
ctx.Set("format", format)
if err := set(ctx, cli, []argsField{
argsField{Name: "ssh_port", Type: "string", Env: "SSH_PORT"},
argsField{Name: "ssh_key", Type: "string", Env: "SSH_KEY_FILE"},
argsField{Name: "host", Type: "string", Env: "FX_HOST"},
argsField{Name: "kubeconf", Type: "string", Env: "FX_KUBECONF"},
}); err != nil {
return err
}
case "image_build":
if !cli.Args().Present() {
return fmt.Errorf("no function given")
@@ -69,12 +131,16 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
}
}
ctx.Set("deps", deps)
tag := cli.String("tag")
if tag == "" {
tag = uuid.New().String()
if err := set(ctx, cli, []argsField{
argsField{Name: "name", Type: "string"},
argsField{Name: "ssh_port", Type: "string", Env: "SSH_PORT"},
argsField{Name: "ssh_key", Type: "string", Env: "SSH_KEY_FILE"},
argsField{Name: "host", Type: "string", Env: "FX_HOST"},
argsField{Name: "kubeconf", Type: "string", Env: "FX_KUBECONF"},
}); err != nil {
return err
}
ctx.Set("tag", tag)
case "image_export":
if !cli.Args().Present() {
return fmt.Errorf("no function given")

View File

@@ -12,8 +12,20 @@ import (
"github.com/urfave/cli"
)
func TestParse(t *testing.T) {
t.Run("source code not existed", func(t *testing.T) {
type stringValue string
func (s stringValue) Set(v string) error {
// nolint
s = stringValue(v)
return nil
}
func (s stringValue) String() string {
return string(s)
}
func TestParseUp(t *testing.T) {
t.Run("SourceCodeNotReady", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
@@ -26,12 +38,15 @@ func TestParse(t *testing.T) {
t.Fatal("should got file or directory not existed error")
}
})
t.Run("source code ready", func(t *testing.T) {
t.Run("SourceCodeReady", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
ctx := mockCtx.NewMockContexter(ctrl)
argset := flag.NewFlagSet("test", 0)
host := "127.0.0.1"
user := "root"
argset.Var(stringValue(user+"@"+host), "host", "host info")
cli := cli.NewContext(nil, argset, nil)
fd, err := ioutil.TempFile("", "fx_func_*.js")
if err != nil {
@@ -43,6 +58,11 @@ func TestParse(t *testing.T) {
ctx.EXPECT().GetCliContext().Return(cli)
ctx.EXPECT().Set("fn", fd.Name())
ctx.EXPECT().Set("deps", []string{})
ctx.EXPECT().Set("host", host)
ctx.EXPECT().Set("user", user)
ctx.EXPECT().Set("ssh_port", "")
ctx.EXPECT().Set("ssh_key", "")
ctx.EXPECT().Set("kubeconf", "")
ctx.EXPECT().Set("name", "")
ctx.EXPECT().Set("port", 0)
ctx.EXPECT().Set("force", false)

View File

@@ -1,93 +0,0 @@
package middlewares
import (
"encoding/json"
"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/metrue/fx/types"
"github.com/pkg/errors"
)
// Provision make sure infrastructure is healthy
func Provision(ctx context.Contexter) (err error) {
fxConfig := ctx.Get("config").(*config.Config)
meta, err := fxConfig.GetCurrentCloud()
if err != nil {
return err
}
cloudType, err := fxConfig.GetCurrentCloudType()
if err != nil {
return err
}
ctx.Set("cloud_type", cloudType)
var cloud infra.Clouder
switch cloudType {
case types.CloudTypeK8S:
cloud, err = k8sInfra.Load(meta)
case types.CloudTypeDocker:
cloud, err = dockerInfra.Load(meta)
}
if err != nil {
return err
}
ok, err := cloud.IsHealth()
if err != nil {
return err
}
if !ok {
return fmt.Errorf("infrastrure is not health, please try to run create infrastructure use 'fx infra create ...' command")
}
ctx.Set("cloud", cloud)
conf, err := cloud.GetConfig()
if err != nil {
return err
}
var deployer infra.Deployer
if os.Getenv("KUBECONFIG") != "" {
cloudType = types.CloudTypeK8S
conf = os.Getenv("KUBECONFIG")
ctx.Set("cloud_type", types.CloudTypeK8S)
}
if cloudType == types.CloudTypeDocker {
var meta map[string]string
if err := json.Unmarshal([]byte(conf), &meta); err != nil {
return err
}
docker, err := dockerHTTP.Create(meta["ip"], 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
}
} else if cloudType == types.CloudTypeK8S {
deployer, err = k8sInfra.CreateDeployer(conf)
if err != nil {
return err
}
} else {
return fmt.Errorf("unsupport cloud type %s, please make sure you config is correct", cloud.GetType())
}
ctx.Set("deployer", deployer)
return nil
}

17
middlewares/ssh.go Normal file
View File

@@ -0,0 +1,17 @@
package middlewares
import (
"github.com/metrue/fx/context"
"github.com/metrue/go-ssh-client"
)
// SSH create a ssh client
func SSH(ctx context.Contexter) error {
host := ctx.Get("host").(string)
user := ctx.Get("user").(string)
port := ctx.Get("ssh_port").(string)
keyfile := ctx.Get("ssh_key").(string)
sshClient := ssh.New(host).WithUser(user).WithPort(port).WithKey(keyfile)
ctx.Set("ssh", sshClient)
return nil
}

View File

@@ -0,0 +1,88 @@
package darwin
import (
"context"
"errors"
"fmt"
"os/exec"
"strings"
"time"
"github.com/metrue/fx/provisioner"
"github.com/metrue/go-ssh-client"
)
const sshConnectionTimeout = 10 * time.Second
var scripts = map[string]string{
"docker_version": "docker version",
"has_docker": "type docker",
"check_fx_agent": "docker inspect fx-agent",
"start_fx_agent": "docker run -d --name=fx-agent --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:8866:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock",
}
// Docker define a fx docker host
type Docker struct {
sshClient ssh.Clienter
}
// New a docker provioner
func New(sshClient ssh.Clienter) *Docker {
return &Docker{
sshClient: sshClient,
}
}
// Provision a host
func (d *Docker) Provision(ctx context.Context, isRemote bool) error {
if err := d.runCmd(scripts["docker_version"], isRemote); err != nil {
if err := d.runCmd(scripts["has_docker"], isRemote); err != nil {
return errors.New("could not find docker on the $PATH")
}
return errors.New("cannot connect to the Docker daemon, is the docker daemon running?")
}
if err := d.runCmd(scripts["check_fx_agent"], isRemote); err != nil {
if err := d.runCmd(scripts["start_fx_agent"], isRemote); err != nil {
return err
}
}
return nil
}
func (d *Docker) runCmd(script string, isRemote bool, options ...ssh.CommandOptions) error {
option := ssh.CommandOptions{
Timeout: sshConnectionTimeout,
}
if len(options) >= 1 {
option = options[0]
}
if !isRemote {
params := strings.Split(script, " ")
if len(params) == 0 {
return fmt.Errorf("invalid script: %s", script)
}
// nolint
cmd := exec.Command(params[0], params[1:]...)
cmd.Stdout = option.Stdout
cmd.Stderr = option.Stderr
err := cmd.Run()
if err != nil {
return err
}
return nil
}
ok, err := d.sshClient.Connectable(sshConnectionTimeout)
if err != nil {
return fmt.Errorf("could not connect via SSH: '%s'", err)
}
if !ok {
return fmt.Errorf("could not connect via SSH")
}
return d.sshClient.RunCommand(script, option)
}
var (
_ provisioner.Provisioner = &Docker{}
)

View File

@@ -0,0 +1,117 @@
package darwin
import (
"context"
"errors"
"testing"
"github.com/golang/mock/gomock"
"github.com/metrue/go-ssh-client"
sshMocks "github.com/metrue/go-ssh-client/mocks"
)
func TestDriverProvision(t *testing.T) {
t.Run("SSHConnectError", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sshClient := sshMocks.NewMockClienter(ctrl)
n := &Docker{sshClient: sshClient}
err := errors.New("could not connect to host")
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(false, err).AnyTimes()
if err := n.Provision(context.Background(), true); err == nil {
t.Fatalf("should get error when SSH connection not ok")
}
})
t.Run("SSHConnectionNotOK", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sshClient := sshMocks.NewMockClienter(ctrl)
n := New(sshClient)
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(false, nil).AnyTimes()
if err := n.Provision(context.Background(), true); err == nil {
t.Fatalf("should get error when SSH connection not ok")
}
})
t.Run("DockerAndFxAgentOK", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sshClient := sshMocks.NewMockClienter(ctrl)
n := New(sshClient)
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil).AnyTimes()
sshClient.EXPECT().RunCommand(scripts["docker_version"], ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
sshClient.EXPECT().RunCommand(scripts["check_fx_agent"], ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
if err := n.Provision(context.Background(), true); err != nil {
t.Fatal(err)
}
})
t.Run("DockerNotReady", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sshClient := sshMocks.NewMockClienter(ctrl)
n := New(sshClient)
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil).AnyTimes()
err := errors.New("docker command not found")
sshClient.EXPECT().RunCommand(scripts["docker_version"], ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(err)
sshClient.EXPECT().RunCommand(scripts["has_docker"], ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(err)
if err := n.Provision(context.Background(), true); err == nil {
t.Fatal("should tell user to install docker first")
}
})
t.Run("FxAgentNotReady", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sshClient := sshMocks.NewMockClienter(ctrl)
n := New(sshClient)
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil).AnyTimes()
err := errors.New("fx agent not found")
sshClient.EXPECT().RunCommand(scripts["docker_version"], ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
sshClient.EXPECT().RunCommand(scripts["check_fx_agent"], ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(err)
sshClient.EXPECT().RunCommand(scripts["start_fx_agent"], ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
if err := n.Provision(context.Background(), true); err != nil {
t.Fatal(err)
}
})
t.Run("DockerAndFxAgentReady", func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sshClient := sshMocks.NewMockClienter(ctrl)
n := New(sshClient)
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil).AnyTimes()
sshClient.EXPECT().RunCommand(scripts["docker_version"], ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
sshClient.EXPECT().RunCommand(scripts["check_fx_agent"], ssh.CommandOptions{Timeout: sshConnectionTimeout}).Return(nil)
if err := n.Provision(context.Background(), true); err != nil {
t.Fatal(err)
}
})
}
func TestRunCommand(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
sshClient := sshMocks.NewMockClienter(ctrl)
n := &Docker{
sshClient: sshClient,
}
script := "script"
option := ssh.CommandOptions{
Timeout: sshConnectionTimeout,
}
sshClient.EXPECT().Connectable(sshConnectionTimeout).Return(true, nil)
sshClient.EXPECT().RunCommand(script, option).Return(nil)
if err := n.runCmd(script, true, option); err != nil {
t.Fatal(err)
}
}

Some files were not shown because too many files have changed in this diff Show More