Compare commits

..

2 Commits

Author SHA1 Message Date
Minghe
2298f39cca k3s on docker (#401)
* since we do the image build in initialize container of pod, so we have to make sure the image built can be access from kubelet on same node, containerd could not support that
* update docs
* bump version
* clean up
2019-12-07 01:42:13 +08:00
Minghe
23d68bc27b Refactor packer (#399) 2019-12-06 20:16:05 +08:00
19 changed files with 458 additions and 417 deletions

View File

@@ -20,8 +20,6 @@ Poor man's function as a service.
## Introduction
![workflow](https://raw.githubusercontent.com/metrue/fx/master/docs/fx-workflow.png)
fx is a tool to help you do Function as a Service on your own server, fx can make your stateless function a service in seconds, both Docker host and Kubernetes cluster supported. The most exciting thing is that you can write your functions with most programming languages.
Feel free hacking fx to support the languages not listed. Welcome to tweet me [@_metrue](https://twitter.com/_metrue) on Twitter, [@metrue](https://www.weibo.com/u/2165714507) on Weibo.
@@ -211,8 +209,6 @@ But we would suggest you run `kubectl config current-context` to check if the cu
* Setup your own Kubernetes cluster
![init workflow](https://raw.githubusercontent.com/metrue/fx/master/docs/fx-init-cluster.png)
```shell
fx infra create --type k3s --name fx-cluster-1 --master root@123.11.2.3 --agents 'root@1.1.1.1,root@2.2.2.2'
```

View File

@@ -106,7 +106,7 @@ func (c *Config) AddK8SCloud(name string, kubeconfig []byte) error {
cloud := map[string]string{
"type": "k8s",
"kubeConfig": kubecfg,
"kubeconfig": kubecfg,
}
return c.addCloud(name, cloud)

View File

@@ -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
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

4
fx.go
View File

@@ -16,7 +16,7 @@ import (
"github.com/urfave/cli"
)
const version = "0.8.71"
const version = "0.8.73"
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,
),
},

View File

@@ -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
}

View File

@@ -35,7 +35,6 @@ func setupK8S(masterInfo string, agentsInfo string) ([]byte, error) {
}
}
fmt.Println(master, agents, len(agents))
k8sOperator := k8s.New(master, agents)
return k8sOperator.Provision()
}

View File

@@ -62,7 +62,7 @@ func (k *Provisioner) HealthCheck() (bool, error) {
func (k *Provisioner) SetupMaster() error {
sshKeyFile, _ := infra.GetSSHKeyFile()
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
installCmd := fmt.Sprintf("curl -sLS https://get.k3s.io | INSTALL_K3S_EXEC='server --tls-san %s' INSTALL_K3S_VERSION='%s' sh -", k.master.IP, version)
installCmd := fmt.Sprintf("curl -sLS https://get.k3s.io | INSTALL_K3S_EXEC='server --docker --tls-san %s' INSTALL_K3S_VERSION='%s' sh -", k.master.IP, version)
if err := ssh.RunCommand(infra.Sudo(installCmd, k.master.User), sshOperator.CommandOptions{
Stdout: os.Stdout,
Stdin: os.Stdin,
@@ -97,7 +97,7 @@ func (k *Provisioner) SetupAgent() error {
if err != nil {
return err
}
const k3sExtraArgs = ""
const k3sExtraArgs = "--docker"
joinCmd := fmt.Sprintf("curl -fL https://get.k3s.io/ | K3S_URL='https://%s:6443' K3S_TOKEN='%s' INSTALL_K3S_VERSION='%s' sh -s - %s", k.master.IP, tok, version, k3sExtraArgs)
for _, agent := range k.agents {
ssh := sshOperator.New(agent.IP).WithUser(agent.User).WithKey(sshKeyFile)

View File

@@ -10,6 +10,8 @@ import (
"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
@@ -23,8 +25,32 @@ func Build(ctx context.Contexter) (err error) {
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")
}
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)

View File

@@ -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

14
packer/doc.go Normal file
View 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

View File

@@ -1,63 +0,0 @@
package packer
import (
"fmt"
"path/filepath"
"strings"
"github.com/gobuffalo/packr"
"github.com/metrue/fx/types"
)
// DockerPacker pack a function source code to a Docker build-able project
type DockerPacker struct {
box packr.Box
}
func isHandler(name string) bool {
basename := filepath.Base(name)
nameWithoutExt := strings.TrimSuffix(basename, filepath.Ext(basename))
return nameWithoutExt == "fx" ||
nameWithoutExt == "Fx" || // Fx is for Java
nameWithoutExt == "mod" // mod.rs is for Rust
}
// NewDockerPacker new a Docker packer
func NewDockerPacker(box packr.Box) *DockerPacker {
return &DockerPacker{box: box}
}
// Pack pack a single function source code to be project
func (p *DockerPacker) Pack(serviceName string, fn types.Func) (types.Project, error) {
var files []types.ProjectSourceFile
for _, name := range p.box.List() {
prefix := fmt.Sprintf("%s/", fn.Language)
if strings.HasPrefix(name, prefix) {
content, err := p.box.FindString(name)
if err != nil {
return types.Project{}, err
}
// if preset's file is handler function of project, replace it with give one
if isHandler(name) {
files = append(files, types.ProjectSourceFile{
Path: strings.Replace(name, prefix, "", 1),
Body: fn.Source,
IsHandler: true,
})
} else {
files = append(files, types.ProjectSourceFile{
Path: strings.Replace(name, prefix, "", 1),
Body: content,
IsHandler: false,
})
}
}
}
return types.Project{
Name: serviceName,
Files: files,
Language: fn.Language,
}, nil
}

View File

@@ -1,64 +0,0 @@
package packer
import (
"testing"
"github.com/gobuffalo/packr"
"github.com/golang/mock/gomock"
"github.com/metrue/fx/types"
)
func TestDockerPacker(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
box := packr.NewBox("./images")
p := NewDockerPacker(box)
mockSource := `
module.exports = ({a, b}) => {
return a + b
}
`
fn := types.Func{
Language: "node",
Source: mockSource,
}
serviceName := "service-mock"
project, err := p.Pack(serviceName, fn)
if err != nil {
t.Fatal(err)
}
if project.Name != serviceName {
t.Fatalf("should get %s but got %s", serviceName, project.Name)
}
if project.Language != "node" {
t.Fatal("incorrect Language")
}
if len(project.Files) != 3 {
t.Fatal("node project should have 3 files")
}
for _, file := range project.Files {
if file.Path == "fx.js" {
if file.IsHandler == false {
t.Fatal("fx.js should be handler")
}
if file.Body != mockSource {
t.Fatalf("should get %s but got %v", mockSource, file.Body)
}
} else if file.Path == "Dockerfile" {
if file.IsHandler == true {
t.Fatalf("should get %v but got %v", false, file.IsHandler)
}
} else {
if file.IsHandler == true {
t.Fatalf("should get %v but %v", false, file.IsHandler)
}
}
}
}

View File

@@ -1,142 +1,207 @@
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 isHandler(name string) bool {
basename := filepath.Base(name)
nameWithoutExt := strings.TrimSuffix(basename, filepath.Ext(basename))
return nameWithoutExt == "fx" ||
nameWithoutExt == "Fx" || // Fx is for Java
nameWithoutExt == "mod" // mod.rs is for Rust
}
func langFromFileName(fileName string) string {
extLangMap := map[string]string{
".js": "node",
".go": "go",
".rb": "ruby",
".py": "python",
".php": "php",
".jl": "julia",
".java": "java",
".d": "d",
".rs": "rust",
}
return extLangMap[filepath.Ext(fileName)]
}
func hasFxHandleFile(input ...string) bool {
var handleFile string
for _, file := range input {
if utils.IsRegularFile(file) && isHandler(file) {
handleFile = file
break
} else if utils.IsDir(file) {
if err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if utils.IsRegularFile(path) && isHandler(info.Name()) {
handleFile = path
}
return nil
}); err != nil {
return false
}
}
}
return handleFile != ""
}
// PackIntoK8SConfigMapFile pack function a K8S config map file
func PackIntoK8SConfigMapFile(dir string) (string, error) {
tree := map[string]string{}
@@ -176,18 +241,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)
}

View File

@@ -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)
}
})
}

View File

@@ -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
View 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")
}
}

View File

@@ -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)