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

|
||||
@@ -13,6 +14,7 @@ Poor man's function as a service.
|
||||
- [Introduction](#introduction)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Manage Infrastructure](#manage-infrastructure)
|
||||
- [Contribute](#contribute)
|
||||
|
||||
|
||||
@@ -79,7 +81,7 @@ USAGE:
|
||||
fx [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
0.8.4
|
||||
0.8.7
|
||||
|
||||
COMMANDS:
|
||||
infra manage infrastructure
|
||||
@@ -96,44 +98,36 @@ GLOBAL OPTIONS:
|
||||
--version, -v print the version
|
||||
```
|
||||
|
||||
1. Write a function
|
||||
### Deploy your function to Docker
|
||||
|
||||
You can check out [examples](https://github.com/metrue/fx/tree/master/examples/functions) for reference. Let's write a function as an example, it calculates the sum of two numbers then returns:
|
||||
|
||||
```js
|
||||
module.exports = (ctx) => {
|
||||
ctx.body = 'hello world'
|
||||
}
|
||||
```
|
||||
Then save it to a file `func.js`.
|
||||
$ fx up --name hello-fx ./examples/functions/JavaScript/func.js
|
||||
|
||||
2. Deploy your function as a service
|
||||
|
||||
Give your service a port with `--port`, and name with `--name`, heath checking with `--healthcheck` if you want.
|
||||
|
||||
```shell
|
||||
$ fx up -name fx_service_name -p 10001 --healthcheck func.js
|
||||
|
||||
2019/08/10 13:26:37 info Pack Service: ✓
|
||||
2019/08/10 13:26:39 info Build Service: ✓
|
||||
2019/08/10 13:26:39 info Run Service: ✓
|
||||
2019/08/10 13:26:39 info Service (fx_service_name) is running on: 0.0.0.0:10001
|
||||
2019/08/10 13:26:39 info up function fx_service_name(func.js) to machine localhost: ✓
|
||||
+------------------------------------------------------------------+-----------+---------------+
|
||||
| ID | NAME | ENDPOINT |
|
||||
+------------------------------------------------------------------+-----------+---------------+
|
||||
| 5b24d36608ee392c937a61a530805f74551ddec304aea3aca2ffa0fabcf98cf3 | /hello-fx | 0.0.0.0:58328 |
|
||||
+------------------------------------------------------------------+-----------+---------------+
|
||||
```
|
||||
|
||||
if you want see what the source code of your service looks like, you can export it into a dirctory,
|
||||
### Deploy your function to Kubernetes
|
||||
|
||||
```shell
|
||||
$ fx image export -o <path of dir> func.js
|
||||
2019/09/25 19:31:19 info exported to <path of dir>: ✓
|
||||
```
|
||||
$ KUBECONFIG=~/.kube/config ./build/fx up examples/functions/JavaScript/func.js --name hello-fx
|
||||
|
||||
+-------------------------------+------+----------------+
|
||||
| ID | NAME | ENDPOINT |
|
||||
+----+--------------------------+-----------------------+
|
||||
| 5b24d36608ee392c937a | hello-fx | 10.0.242.75:80 |
|
||||
+------------------------+-------------+----------------+
|
||||
```
|
||||
|
||||
3. Test your service
|
||||
### Test your service
|
||||
|
||||
then you can test your service:
|
||||
|
||||
```shell
|
||||
$ curl -v 0.0.0.0:10001
|
||||
$ curl -v 0.0.0.0:58328
|
||||
|
||||
|
||||
GET / HTTP/1.1
|
||||
@@ -155,39 +149,32 @@ hello world
|
||||
|
||||
```
|
||||
|
||||
## Docker
|
||||
## Manage Infrastructure
|
||||
|
||||
**fx** is originally designed to turn a function into a runnable Docker container in a easiest way, on a host with Docker running, you can just deploy your function with `fx up` command,
|
||||
**fx** is originally designed to turn a function into a runnable Docker container in a easiest way, on a host with Docker running, you can just deploy your function with `fx up` command, and now **fx** supports deploy function to be a service onto Kubernetes cluster infrasture, and we encourage you to do that other than on bare Docker environment, there are lots of advantage to run your function on Kubernetes like self-healing, load balancing, easy horizontal scaling, etc. It's pretty simple to deploy your function onto Kubernetes with **fx**, you just set KUBECONFIG in your enviroment.
|
||||
|
||||
By default. **fx** use localhost as target infrastructure to run your service, and you can also setup your remote virtual machines as **fx**'s infrastructure and deploy your functions onto it.
|
||||
|
||||
### `fx infra create`
|
||||
|
||||
You can create types (docker and k8s) of infrastructures for **fx** to deploy functions
|
||||
|
||||
```shell
|
||||
fx up --name hello-svc --port 7777 hello.js # onto localhost
|
||||
DOCKER_REMOTE_HOST_ADDR=xx.xx.xx.xx DOCKER_REMOTE_HOST_USER=xxxx DOCKER_REMOTE_HOST_PASSWORD=xxxx fx up --name hello-svc --port 7777 hello.js # onto remote host
|
||||
$ fx infra create --name infra_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
|
||||
```
|
||||
|
||||
## Kubernetes
|
||||
### `fx infra use`
|
||||
|
||||
**fx** supports deploy function to be a service onto Kubernetes cluster infrasture, and we encourage you to do that other than on bare Docker environment, there are lots of advantage to run your function on Kubernetes like self-healing, load balancing, easy horizontal scaling, etc. It's pretty simple to deploy your function onto Kubernetes with **fx**, you just set KUBECONFIG in your enviroment.
|
||||
To use a infrastructure, you can use `fx infra use` command to activate it.
|
||||
|
||||
```shell
|
||||
KUBECONFIG=<Your KUBECONFIG> fx deploy -n fx-service-abc_js -p 12349 examples/functions/JavaScript/func.js # function will be deploy to your Kubernetes cluster and expose a IP address of your loadbalencer
|
||||
fx infra use <infrastructure name>
|
||||
```
|
||||
|
||||
or
|
||||
and you can list your infrastructure with `fx infra list`
|
||||
|
||||
```shell
|
||||
$ export KUBECONFIG=<Your KUBECONFIG>
|
||||
$ fx deploy -n fx-service-abc_js -p 12349 examples/functions/JavaScript/func.js # function will be deploy to your Kubernetes cluster and expose a IP address of your loadbalencer
|
||||
```
|
||||
|
||||
* Local Kubernetes Cluster
|
||||
|
||||
Docker for Mac and Docker for Windows already support Kubernetes with single node cluster, we can use it directly, and the default `KUBECONFIG` is `~/.kube/config`.
|
||||
|
||||
```shell
|
||||
$ export KUBECONFIG=~/.kube/config # then fx will take the config to deloy function
|
||||
```
|
||||
|
||||
if you have multiple Kubernetes clusters configured, you have to set context correctly. FYI [configure-access-multiple-clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/)
|
||||
## Use Public Cloud Kubernetes Service as infrastructure to run your functions
|
||||
|
||||
* Azure Kubernetes Service (AKS)
|
||||
|
||||
|
||||
@@ -37,27 +37,29 @@ type API struct {
|
||||
|
||||
// Create a API
|
||||
func Create(host string, port string) (*API, error) {
|
||||
version, err := utils.DockerVersion(host, port)
|
||||
addr := host + ":" + port
|
||||
v, err := version(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, version)
|
||||
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, v)
|
||||
return &API{
|
||||
endpoint: endpoint,
|
||||
version: version,
|
||||
version: v,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MustCreate a api object, panic if not
|
||||
func MustCreate(host string, port string) *API {
|
||||
version, err := utils.DockerVersion(host, port)
|
||||
addr := host + ":" + port
|
||||
v, err := version(addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, version)
|
||||
endpoint := fmt.Sprintf("http://%s:%s/v%s", host, port, v)
|
||||
return &API{
|
||||
endpoint: endpoint,
|
||||
version: version,
|
||||
version: v,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +133,11 @@ func (api *API) post(path string, body []byte, expectStatus int, v interface{})
|
||||
|
||||
// Version get version of docker engine
|
||||
func (api *API) Version(ctx context.Context) (string, error) {
|
||||
path := api.endpoint + "/version"
|
||||
return version(api.endpoint)
|
||||
}
|
||||
|
||||
func version(endpoint string) (string, error) {
|
||||
path := endpoint + "/version"
|
||||
if !strings.HasPrefix(path, "http") {
|
||||
path = "http://" + path
|
||||
}
|
||||
|
||||
47
examples/functions/Rust/README.md
vendored
Normal file
47
examples/functions/Rust/README.md
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# Make a Rust function a service with fx
|
||||
|
||||
Write a function like,
|
||||
|
||||
```rust
|
||||
pub mod fns {
|
||||
#[derive(Serialize)]
|
||||
pub struct Response {
|
||||
pub result: i32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Request {
|
||||
pub a: i32,
|
||||
pub b: i32,
|
||||
}
|
||||
|
||||
pub fn func(req: Request) -> Response {
|
||||
Response {
|
||||
result: req.a + req.b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
then deploy it with `fx up` command,
|
||||
|
||||
```shell
|
||||
$ fx up -p 8080 func.rs
|
||||
```
|
||||
|
||||
test it using `curl`
|
||||
|
||||
```shell
|
||||
$ curl -X 'POST' --header 'Content-Type: application/json' --data '{"a":1,"b":1}' '0.0.0.0:3000'
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Length: 12
|
||||
Content-Type: application/json
|
||||
Date: Fri, 06 Dec 2019 06:45:14 GMT
|
||||
Server: Rocket
|
||||
|
||||
{
|
||||
"result": 2
|
||||
}
|
||||
```
|
||||
4
fx.go
4
fx.go
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const version = "0.8.6"
|
||||
const version = "0.8.72"
|
||||
|
||||
func init() {
|
||||
go checkForUpdate()
|
||||
@@ -212,6 +212,7 @@ func main() {
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Provision,
|
||||
middlewares.Parse("image_build"),
|
||||
handlers.BuildImage,
|
||||
),
|
||||
},
|
||||
@@ -227,6 +228,7 @@ func main() {
|
||||
Action: handle(
|
||||
middlewares.LoadConfig,
|
||||
middlewares.Provision,
|
||||
middlewares.Parse("image_export"),
|
||||
handlers.ExportImage,
|
||||
),
|
||||
},
|
||||
|
||||
2
go.mod
2
go.mod
@@ -27,7 +27,7 @@ require (
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/nwaples/rardecode v1.0.0 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.3
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/otiai10/copy v1.0.2
|
||||
|
||||
4
go.sum
4
go.sum
@@ -163,6 +163,8 @@ github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
|
||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b h1:JGD0sJ44XzhsT1voOg00zji4ubuMNcVNK3m7d9GI88k=
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b/go.mod h1:ERHOEBrDy6+8vfoJjjmhdmBpOzdvvP7bLtwYTTK6LOs=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
@@ -187,6 +189,8 @@ github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMB
|
||||
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/olekukonko/tablewriter v0.0.3 h1:i0LBnzgiChAWHJYTQAZJDOgf8MNxAVYZJ2m63SIDimI=
|
||||
github.com/olekukonko/tablewriter v0.0.3/go.mod h1:YZeBtGzYYEsCHp2LST/u/0NDwGkRoBtmn1cIWCJiS6M=
|
||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
|
||||
@@ -2,80 +2,74 @@ package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/google/uuid"
|
||||
"github.com/metrue/fx/constants"
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/otiai10/copy"
|
||||
)
|
||||
|
||||
// BuildImage build image
|
||||
func BuildImage(ctx context.Contexter) error {
|
||||
cli := ctx.GetCliContext()
|
||||
funcFile := cli.Args().First()
|
||||
tag := cli.String("tag")
|
||||
if tag == "" {
|
||||
tag = uuid.New().String()
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
body, err := ioutil.ReadFile(funcFile)
|
||||
if err != nil {
|
||||
log.Fatalf("function code load failed: %v", err)
|
||||
return err
|
||||
sources := ctx.Get("sources").([]string)
|
||||
|
||||
if len(sources) == 0 {
|
||||
return fmt.Errorf("source file/directory of function required")
|
||||
}
|
||||
log.Infof("function code loaded: %v", constants.CheckedSymbol)
|
||||
lang := utils.GetLangFromFileName(funcFile)
|
||||
|
||||
fn := types.Func{Language: lang, Source: string(body)}
|
||||
|
||||
if err := packer.PackIntoDir(fn, workdir); err != nil {
|
||||
log.Fatalf("could not pack function %v: %v", fn, err)
|
||||
return err
|
||||
}
|
||||
|
||||
docker, ok := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
||||
if ok {
|
||||
nameWithTag := tag + ":latest"
|
||||
if err := docker.BuildImage(ctx.GetContext(), workdir, nameWithTag); err != nil {
|
||||
if len(sources) == 1 &&
|
||||
utils.IsDir(sources[0]) &&
|
||||
utils.HasDockerfile(sources[0]) {
|
||||
if err := copy.Copy(sources[0], workdir); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := packer.Pack(workdir, sources...); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("image built: %v", constants.CheckedSymbol)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("no available docker cli")
|
||||
|
||||
docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
||||
nameWithTag := ctx.Get("tag").(string) + ":latest"
|
||||
if err := docker.BuildImage(ctx.GetContext(), workdir, nameWithTag); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("image built: %s %v", nameWithTag, constants.CheckedSymbol)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExportImage export service's code into a directory
|
||||
func ExportImage(ctx context.Contexter) (err error) {
|
||||
cli := ctx.GetCliContext()
|
||||
funcFile := cli.Args().First()
|
||||
outputDir := cli.String("output")
|
||||
if outputDir == "" {
|
||||
log.Fatalf("output directory required")
|
||||
return nil
|
||||
outputDir := ctx.Get("output").(string)
|
||||
sources := ctx.Get("sources").([]string)
|
||||
|
||||
if len(sources) == 0 {
|
||||
return fmt.Errorf("source file/directory of function required")
|
||||
}
|
||||
if len(sources) == 1 &&
|
||||
utils.IsDir(sources[0]) &&
|
||||
utils.HasDockerfile(sources[0]) {
|
||||
if err := copy.Copy(sources[0], outputDir); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := packer.Pack(outputDir, sources...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadFile(funcFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read source failed")
|
||||
}
|
||||
lang := utils.GetLangFromFileName(funcFile)
|
||||
|
||||
if err := packer.PackIntoDir(types.Func{Language: lang, Source: string(body)}, outputDir); err != nil {
|
||||
log.Fatalf("write source code to file failed: %v", constants.UncheckedSymbol)
|
||||
return err
|
||||
}
|
||||
log.Infof("exported to %v: %v", outputDir, constants.CheckedSymbol)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,8 +9,14 @@ import (
|
||||
|
||||
// Up command handle
|
||||
func Up(ctx context.Contexter) (err error) {
|
||||
fn := ctx.Get("data").(string)
|
||||
image := ctx.Get("image").(string)
|
||||
fn, ok := ctx.Get("data").(string)
|
||||
if !ok {
|
||||
fn = ""
|
||||
}
|
||||
image, ok := ctx.Get("image").(string)
|
||||
if !ok {
|
||||
image = ""
|
||||
}
|
||||
name := ctx.Get("name").(string)
|
||||
deployer := ctx.Get("deployer").(infra.Deployer)
|
||||
bindings := ctx.Get("bindings").([]types.PortBinding)
|
||||
|
||||
@@ -5,10 +5,13 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/otiai10/copy"
|
||||
)
|
||||
|
||||
// Build image
|
||||
@@ -19,40 +22,68 @@ func Build(ctx context.Contexter) (err error) {
|
||||
spinner.Stop(task, err)
|
||||
}()
|
||||
|
||||
name := ctx.Get("name").(string)
|
||||
docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
||||
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
|
||||
defer os.RemoveAll(workdir)
|
||||
|
||||
if err := packer.Pack(workdir, ctx.Get("sources").([]string)...); err != nil {
|
||||
return err
|
||||
// Cases supports
|
||||
// 1. a single file function
|
||||
// fx up func.js
|
||||
// 2. a directory with Docker in it
|
||||
// fx up ./func/
|
||||
// 3. a directory without Dockerfile in it, but has fx handle function file
|
||||
// 4. a fx handlefunction file and its dependencies files or/and directory
|
||||
// fx up func.js helper.js ./lib/
|
||||
|
||||
// When only one directory given and there is a Dockerfile in given directory, treat it as a containerized project and skip packing
|
||||
sources := ctx.Get("sources").([]string)
|
||||
|
||||
if len(sources) == 0 {
|
||||
return fmt.Errorf("source file/directory of function required")
|
||||
}
|
||||
|
||||
data, err := packer.PackIntoK8SConfigMapFile(workdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("data", data)
|
||||
|
||||
if err := docker.BuildImage(ctx.GetContext(), workdir, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nameWithTag := name + ":latest"
|
||||
if err := docker.TagImage(ctx.GetContext(), name, nameWithTag); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("image", nameWithTag)
|
||||
|
||||
if os.Getenv("K3S") != "" {
|
||||
username := os.Getenv("DOCKER_USERNAME")
|
||||
password := os.Getenv("DOCKER_PASSWORD")
|
||||
if username != "" && password != "" {
|
||||
if _, err := docker.PushImage(ctx.GetContext(), name); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("image", username+"/"+name)
|
||||
if len(sources) == 1 &&
|
||||
utils.IsDir(sources[0]) &&
|
||||
utils.HasDockerfile(sources[0]) {
|
||||
if err := copy.Copy(sources[0], workdir); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := packer.Pack(workdir, sources...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cloudType := ctx.Get("cloud_type").(string)
|
||||
name := ctx.Get("name").(string)
|
||||
if cloudType == config.CloudTypeK8S && os.Getenv("K3S") == "" {
|
||||
data, err := packer.PackIntoK8SConfigMapFile(workdir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("data", data)
|
||||
} else {
|
||||
docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
|
||||
if err := docker.BuildImage(ctx.GetContext(), workdir, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nameWithTag := name + ":latest"
|
||||
if err := docker.TagImage(ctx.GetContext(), name, nameWithTag); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("image", nameWithTag)
|
||||
|
||||
if os.Getenv("K3S") != "" {
|
||||
username := os.Getenv("DOCKER_USERNAME")
|
||||
password := os.Getenv("DOCKER_PASSWORD")
|
||||
if username != "" && password != "" {
|
||||
if _, err := docker.PushImage(ctx.GetContext(), name); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("image", username+"/"+name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Parse parse input
|
||||
@@ -17,13 +17,6 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
|
||||
for _, s := range cli.Args() {
|
||||
sources = append(sources, s)
|
||||
}
|
||||
if len(sources) == 0 {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sources = append(sources, pwd)
|
||||
}
|
||||
ctx.Set("sources", sources)
|
||||
name := cli.String("name")
|
||||
ctx.Set("name", name)
|
||||
@@ -32,7 +25,7 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
|
||||
case "down":
|
||||
services := cli.Args()
|
||||
if len(services) == 0 {
|
||||
return errors.New("service name required")
|
||||
return fmt.Errorf("service name required")
|
||||
}
|
||||
svc := []string{}
|
||||
for _, service := range services {
|
||||
@@ -42,6 +35,28 @@ func Parse(action string) func(ctx context.Contexter) (err error) {
|
||||
case "list":
|
||||
name := cli.Args().First()
|
||||
ctx.Set("filter", name)
|
||||
case "image_build":
|
||||
sources := []string{}
|
||||
for _, s := range cli.Args() {
|
||||
sources = append(sources, s)
|
||||
}
|
||||
ctx.Set("sources", sources)
|
||||
tag := cli.String("tag")
|
||||
if tag == "" {
|
||||
tag = uuid.New().String()
|
||||
}
|
||||
ctx.Set("tag", tag)
|
||||
case "image_export":
|
||||
sources := []string{}
|
||||
for _, s := range cli.Args() {
|
||||
sources = append(sources, s)
|
||||
}
|
||||
ctx.Set("sources", sources)
|
||||
outputDir := cli.String("output")
|
||||
if outputDir == "" {
|
||||
return fmt.Errorf("output directory required")
|
||||
}
|
||||
ctx.Set("output", outputDir)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -20,7 +20,13 @@ func Provision(ctx context.Contexter) (err error) {
|
||||
cloud := fxConfig.Clouds[fxConfig.CurrentCloud]
|
||||
|
||||
var deployer infra.Deployer
|
||||
if cloud["type"] == config.CloudTypeDocker {
|
||||
if os.Getenv("KUBECONFIG") != "" {
|
||||
deployer, err = k8sInfra.CreateDeployer(os.Getenv("KUBECONFIG"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("cloud_type", config.CloudTypeK8S)
|
||||
} else if cloud["type"] == config.CloudTypeDocker {
|
||||
provisioner := dockerInfra.CreateProvisioner(cloud["host"], cloud["user"])
|
||||
ok, err := provisioner.HealthCheck()
|
||||
if err != nil {
|
||||
@@ -43,13 +49,13 @@ func Provision(ctx context.Contexter) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("cloud_type", config.CloudTypeDocker)
|
||||
} else if cloud["type"] == config.CloudTypeK8S {
|
||||
if os.Getenv("KUBECONFIG") != "" {
|
||||
deployer, err = k8sInfra.CreateDeployer(cloud["kubeconfig"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deployer, err = k8sInfra.CreateDeployer(cloud["kubeconfig"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("cloud_type", config.CloudTypeK8S)
|
||||
} else {
|
||||
return fmt.Errorf("unsupport cloud type %s, please make sure you config is correct", cloud["type"])
|
||||
}
|
||||
|
||||
14
packer/doc.go
Normal file
14
packer/doc.go
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
|
||||
Packer takes source codes of a function, and pack them into a containerized service, that means there is Dockerfile generated in the output directory
|
||||
|
||||
e.g.
|
||||
|
||||
Pack(output, "hello.js") # a single file function
|
||||
Pack(output, "hello.js", "helper.js") # multiple files function
|
||||
Pack(output, "./func/") # a directory of function
|
||||
Pack(output, "hello.js", "./func/") # a directory and files of function
|
||||
|
||||
*/
|
||||
|
||||
package packer
|
||||
284
packer/packer.go
284
packer/packer.go
@@ -1,142 +1,199 @@
|
||||
package packer
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gobuffalo/packr"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/otiai10/copy"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var presets packr.Box
|
||||
|
||||
func init() {
|
||||
presets = packr.NewBox("./images")
|
||||
}
|
||||
|
||||
// Pack pack a file or directory into a Docker project
|
||||
func Pack(output string, input ...string) error {
|
||||
if len(input) == 0 {
|
||||
return fmt.Errorf("source file or directory required")
|
||||
}
|
||||
|
||||
var lang string
|
||||
for _, f := range input {
|
||||
if utils.IsRegularFile(f) {
|
||||
lang = langFromFileName(f)
|
||||
} else if utils.IsDir(f) {
|
||||
if err := filepath.Walk(f, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if utils.IsRegularFile(path) {
|
||||
lang = langFromFileName(path)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if lang == "" {
|
||||
return fmt.Errorf("could not tell programe language of your input source codes")
|
||||
}
|
||||
|
||||
if err := restore(output, lang); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(input) == 1 {
|
||||
file := input[0]
|
||||
stat, err := os.Stat(input[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stat.Mode().IsRegular() {
|
||||
if err := filepath.Walk(output, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isHandler(path) {
|
||||
if err := copy.Copy(input[0], path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if !hasFxHandleFile(input...) {
|
||||
msg := `it requires a fx handle file when input is not a single file function, e.g.
|
||||
fx.go for Golang
|
||||
Fx.java for Java
|
||||
fx.php for PHP
|
||||
fx.py for Python
|
||||
fx.js for JavaScript or Node
|
||||
fx.rb for Ruby
|
||||
fx.jl for Julia
|
||||
fx.d for D`
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
if err := merge(output, input...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func restore(output string, lang string) error {
|
||||
for _, name := range presets.List() {
|
||||
prefix := fmt.Sprintf("%s/", lang)
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
content, err := presets.FindString(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filePath := filepath.Join(output, strings.Replace(name, prefix, "", 1))
|
||||
if err := utils.EnsureFile(filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(filePath, []byte(content), 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func merge(dest string, input ...string) error {
|
||||
for _, file := range input {
|
||||
stat, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !stat.IsDir() {
|
||||
lang := utils.GetLangFromFileName(file)
|
||||
body, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read source failed")
|
||||
}
|
||||
fn := types.Func{
|
||||
Language: lang,
|
||||
Source: string(body),
|
||||
}
|
||||
if err := PackIntoDir(fn, output); err != nil {
|
||||
if stat.Mode().IsRegular() {
|
||||
targetFilePath := filepath.Join(dest, stat.Name())
|
||||
if err := utils.EnsureFile(targetFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
body, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(targetFilePath, body, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if stat.Mode().IsDir() {
|
||||
if err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workdir := fmt.Sprintf("./fx-%d", time.Now().Unix())
|
||||
defer os.RemoveAll(workdir)
|
||||
|
||||
for _, f := range input {
|
||||
if err := copy.Copy(f, filepath.Join(workdir, f)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dockerfile, has := hasDockerfileInDir(workdir); has {
|
||||
return copy.Copy(filepath.Dir(dockerfile), output)
|
||||
}
|
||||
|
||||
if f, has := hasFxHandleFileInDir(workdir); has {
|
||||
lang := utils.GetLangFromFileName(f)
|
||||
body, err := ioutil.ReadFile(f)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read source failed")
|
||||
}
|
||||
fn := types.Func{
|
||||
Language: lang,
|
||||
Source: string(body),
|
||||
}
|
||||
if err := PackIntoDir(fn, output); err != nil {
|
||||
return err
|
||||
}
|
||||
return copy.Copy(filepath.Dir(f), output)
|
||||
}
|
||||
|
||||
return fmt.Errorf("input directories or files has no Dockerfile or file with fx as name, e.g. fx.js")
|
||||
}
|
||||
|
||||
func hasDockerfileInDir(dir string) (string, bool) {
|
||||
var dockerfile string
|
||||
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
// nolint
|
||||
if !info.IsDir() && info.Name() == "Dockerfile" {
|
||||
dockerfile = path
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return "", false
|
||||
}
|
||||
if dockerfile == "" {
|
||||
return "", false
|
||||
}
|
||||
return dockerfile, true
|
||||
}
|
||||
|
||||
func hasFxHandleFileInDir(dir string) (string, bool) {
|
||||
var handleFile string
|
||||
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() && isHandler(info.Name()) {
|
||||
handleFile = path
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return "", false
|
||||
}
|
||||
if handleFile == "" {
|
||||
return "", false
|
||||
}
|
||||
return handleFile, true
|
||||
}
|
||||
|
||||
// Pack a function to be a docker project which is web service, handle the imcome request with given function
|
||||
func pack(svcName string, fn types.Func) (types.Project, error) {
|
||||
box := packr.NewBox("./images")
|
||||
pkr := NewDockerPacker(box)
|
||||
return pkr.Pack(svcName, fn)
|
||||
}
|
||||
|
||||
// PackIntoDir pack service code into directory
|
||||
func PackIntoDir(fn types.Func, outputDir string) error {
|
||||
project, err := pack("", fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range project.Files {
|
||||
tmpfn := filepath.Join(outputDir, file.Path)
|
||||
if err := utils.EnsureFile(tmpfn); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(tmpfn, []byte(file.Body), 0666); err != nil {
|
||||
return err
|
||||
if err := copy.Copy(file, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func langFromFileName(fileName string) string {
|
||||
extLangMap := map[string]string{
|
||||
".js": "node",
|
||||
".go": "go",
|
||||
".rb": "ruby",
|
||||
".py": "python",
|
||||
".php": "php",
|
||||
".jl": "julia",
|
||||
".java": "java",
|
||||
".d": "d",
|
||||
".rs": "rust",
|
||||
}
|
||||
return extLangMap[filepath.Ext(fileName)]
|
||||
}
|
||||
|
||||
func hasFxHandleFile(input ...string) bool {
|
||||
var handleFile string
|
||||
for _, file := range input {
|
||||
if utils.IsRegularFile(file) && isHandler(file) {
|
||||
handleFile = file
|
||||
break
|
||||
} else if utils.IsDir(file) {
|
||||
if err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if utils.IsRegularFile(path) && isHandler(info.Name()) {
|
||||
handleFile = path
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handleFile != ""
|
||||
}
|
||||
|
||||
// PackIntoK8SConfigMapFile pack function a K8S config map file
|
||||
func PackIntoK8SConfigMapFile(dir string) (string, error) {
|
||||
tree := map[string]string{}
|
||||
@@ -176,18 +233,3 @@ func TreeToDir(tree map[string]string, outputDir string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackIntoTar pack service code into directory
|
||||
func PackIntoTar(fn types.Func, path string) error {
|
||||
tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tarDir)
|
||||
|
||||
if err := PackIntoDir(fn, tarDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utils.TarDir(tarDir, path)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package packer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
func TestPacker(t *testing.T) {
|
||||
@@ -64,55 +69,6 @@ func TestPacker(t *testing.T) {
|
||||
t.Fatalf("should report error when there is not Dockerfile or fx.[ext] in it")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pack", func(t *testing.T) {
|
||||
mockSource := `
|
||||
module.exports = ({a, b}) => {
|
||||
return a + b
|
||||
}
|
||||
`
|
||||
fn := types.Func{
|
||||
Language: "node",
|
||||
Source: mockSource,
|
||||
}
|
||||
|
||||
serviceName := "service-mock"
|
||||
project, err := pack(serviceName, fn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if project.Name != serviceName {
|
||||
t.Fatalf("should get %s but got %s", serviceName, project.Name)
|
||||
}
|
||||
|
||||
if project.Language != "node" {
|
||||
t.Fatal("incorrect Language")
|
||||
}
|
||||
|
||||
if len(project.Files) != 3 {
|
||||
t.Fatal("node project should have 3 files")
|
||||
}
|
||||
|
||||
for _, file := range project.Files {
|
||||
if file.Path == "fx.js" {
|
||||
if file.IsHandler == false {
|
||||
t.Fatal("fx.js should be handler")
|
||||
}
|
||||
if file.Body != mockSource {
|
||||
t.Fatalf("should get %s but got %v", mockSource, file.Body)
|
||||
}
|
||||
} else if file.Path == "Dockerfile" {
|
||||
if file.IsHandler == true {
|
||||
t.Fatalf("should get %v but got %v", false, file.IsHandler)
|
||||
}
|
||||
} else {
|
||||
if file.IsHandler == true {
|
||||
t.Fatalf("should get %v but %v", false, file.IsHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTreeAndUnTree(t *testing.T) {
|
||||
@@ -121,3 +77,106 @@ func TestTreeAndUnTree(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
langs := []string{
|
||||
"d",
|
||||
"go",
|
||||
"java",
|
||||
"julia",
|
||||
"node",
|
||||
"php",
|
||||
"python",
|
||||
"ruby",
|
||||
"rust",
|
||||
}
|
||||
for _, lang := range langs {
|
||||
output := fmt.Sprintf("output-%s-%d", lang, time.Now().Unix())
|
||||
defer func() {
|
||||
os.RemoveAll(output)
|
||||
}()
|
||||
if err := restore(output, lang); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
diffCmd := exec.Command("diff", "-r", output, "./images/"+lang)
|
||||
if stdoutStderr, err := diffCmd.CombinedOutput(); err != nil {
|
||||
fmt.Printf("%s\n", stdoutStderr)
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
// TODO should check the merge result
|
||||
t.Run("NoInput", func(t *testing.T) {
|
||||
dest := "./dest"
|
||||
_ = utils.EnsureDir("./dest")
|
||||
defer func() {
|
||||
os.RemoveAll(dest)
|
||||
}()
|
||||
|
||||
if err := merge(dest); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Files", func(t *testing.T) {
|
||||
dest := "./dest"
|
||||
_ = utils.EnsureDir("./dest")
|
||||
defer func() {
|
||||
os.RemoveAll(dest)
|
||||
}()
|
||||
|
||||
f1, err := ioutil.TempFile("", "fx.*.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f1.Name())
|
||||
|
||||
f2, err := ioutil.TempFile("", "fx.*.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f2.Name())
|
||||
|
||||
if err := merge(dest, f1.Name(), f2.Name()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Directories", func(t *testing.T) {
|
||||
dest := "./dest"
|
||||
_ = utils.EnsureDir("./dest")
|
||||
defer func() {
|
||||
os.RemoveAll(dest)
|
||||
}()
|
||||
|
||||
if err := merge(dest, "./fixture/p1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Files and Directories", func(t *testing.T) {
|
||||
dest := "./dest"
|
||||
_ = utils.EnsureDir("./dest")
|
||||
defer func() {
|
||||
os.RemoveAll(dest)
|
||||
}()
|
||||
|
||||
f1, err := ioutil.TempFile("", "fx.*.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f1.Name())
|
||||
|
||||
f2, err := ioutil.TempFile("", "fx.*.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f2.Name())
|
||||
|
||||
if err := merge(dest, "./fixture/p1", f1.Name(), f2.Name()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,47 +1,24 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dockerTypes "github.com/docker/docker/api/types"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// DockerVersion docker verion
|
||||
func DockerVersion(host string, port string) (string, error) {
|
||||
path := host + ":" + port + "/version"
|
||||
if !strings.HasPrefix(path, "http") {
|
||||
path = "http://" + path
|
||||
// HasDockerfile check if there is Dockerfile in dir
|
||||
func HasDockerfile(dir string) bool {
|
||||
var dockerfile string
|
||||
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
// nolint
|
||||
if info.Mode().IsRegular() && info.Name() == "Dockerfile" {
|
||||
dockerfile = path
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if dockerfile == "" {
|
||||
return false
|
||||
}
|
||||
client := &http.Client{Timeout: 20 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("request %s failed: %d - %s", path, resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var res dockerTypes.Version
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.APIVersion, nil
|
||||
return true
|
||||
}
|
||||
|
||||
12
utils/docker_test.go
Normal file
12
utils/docker_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestHasDockerfile(t *testing.T) {
|
||||
dir := "tmp"
|
||||
_ = EnsureDir(dir)
|
||||
|
||||
if HasDockerfile(dir) {
|
||||
t.Fatalf("should get false but got true")
|
||||
}
|
||||
}
|
||||
@@ -176,8 +176,8 @@ func CopyDir(src string, dst string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// EnsurerDir Create Dir if not exist
|
||||
func EnsurerDir(dir string) (err error) {
|
||||
// EnsureDir Create Dir if not exist
|
||||
func EnsureDir(dir string) (err error) {
|
||||
if _, statError := os.Stat(dir); os.IsNotExist(statError) {
|
||||
mkError := os.MkdirAll(dir, os.ModePerm)
|
||||
return mkError
|
||||
@@ -188,7 +188,7 @@ func EnsurerDir(dir string) (err error) {
|
||||
// EnsureFile ensure a file
|
||||
func EnsureFile(fullpath string) error {
|
||||
dir := path.Dir(fullpath)
|
||||
err := EnsurerDir(dir)
|
||||
err := EnsureDir(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -199,6 +199,24 @@ func EnsureFile(fullpath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsDir if given path is a directory
|
||||
func IsDir(dir string) bool {
|
||||
stat, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return stat.IsDir()
|
||||
}
|
||||
|
||||
// IsRegularFile if given path is a regular
|
||||
func IsRegularFile(file string) bool {
|
||||
stat, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return stat.Mode().IsRegular()
|
||||
}
|
||||
|
||||
// IsPathExists checks whether a path exists or if failed to check
|
||||
func IsPathExists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
|
||||
Reference in New Issue
Block a user