From bdc454e7e5a8da98b33f6b2a79b6e31af4f0a2bc Mon Sep 17 00:00:00 2001 From: Minghe Date: Tue, 11 Feb 2020 20:26:46 +0800 Subject: [PATCH] support before_build hook (#455) --- .golangci.yml | 4 +- .../dockerfiles/base/node/package-lock.json | 15 +++- assets/dockerfiles/base/node/package.json | 1 + handlers/image.go | 7 ++ hook/.hooks/before_build | 1 + hook/fixture/package.json | 15 ++++ hook/hook.go | 73 +++++++++++++++++++ hook/hook_manager.go | 54 ++++++++++++++ hook/hook_manager_test.go | 40 ++++++++++ hook/hook_test.go | 21 ++++++ middlewares/build.go | 4 + packer/a_packer-packr.go | 2 +- packer/images/node/app.js | 4 + 13 files changed, 237 insertions(+), 4 deletions(-) create mode 100644 hook/.hooks/before_build create mode 100644 hook/fixture/package.json create mode 100644 hook/hook.go create mode 100644 hook/hook_manager.go create mode 100644 hook/hook_manager_test.go create mode 100644 hook/hook_test.go diff --git a/.golangci.yml b/.golangci.yml index 489866bd..78d3dbd5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,6 @@ run: - deadline: 10m - timeout: 10m + deadline: 20m + timeout: 20m issues-exit-code: 1 tests: true skip-dirs: diff --git a/assets/dockerfiles/base/node/package-lock.json b/assets/dockerfiles/base/node/package-lock.json index f0bc39e3..3035aa38 100644 --- a/assets/dockerfiles/base/node/package-lock.json +++ b/assets/dockerfiles/base/node/package-lock.json @@ -1,13 +1,26 @@ { - "name": "aok", + "name": "fx-node-base", "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@koa/cors": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-2.2.3.tgz", + "integrity": "sha512-tCVVXa39ETsit5kGBtEWWimjLn1sDaeu8+0phgb8kT3GmBDZOykkI3ZO8nMjV2p3MGkJI4K5P+bxR8Ztq0bwsA==", + "requires": { + "vary": "^1.1.2" + } + }, "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" } } } diff --git a/assets/dockerfiles/base/node/package.json b/assets/dockerfiles/base/node/package.json index c40025dc..782597b3 100644 --- a/assets/dockerfiles/base/node/package.json +++ b/assets/dockerfiles/base/node/package.json @@ -10,6 +10,7 @@ "author": "", "license": "ISC", "dependencies": { + "@koa/cors": "^2.2.3", "get-port": "^3.2.0", "is-generator-function": "^1.0.6", "koa": "^2.3.0", diff --git a/handlers/image.go b/handlers/image.go index 0802023f..d54e8316 100644 --- a/handlers/image.go +++ b/handlers/image.go @@ -9,6 +9,7 @@ import ( "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/packer" "github.com/metrue/fx/pkg/spinner" "github.com/metrue/fx/utils" @@ -39,6 +40,9 @@ func BuildImage(ctx context.Contexter) (err error) { if err := packer.Pack(workdir, sources...); err != nil { return err } + if err := hook.RunBeforeBuildHook(workdir); err != nil { + return err + } } docker := ctx.Get("docker").(containerruntimes.ContainerRuntime) @@ -68,6 +72,9 @@ func ExportImage(ctx context.Contexter) (err error) { if err := packer.Pack(outputDir, sources...); err != nil { return err } + if err := hook.RunBeforeBuildHook(outputDir); err != nil { + return err + } } log.Infof("exported to %v: %v", outputDir, constants.CheckedSymbol) diff --git a/hook/.hooks/before_build b/hook/.hooks/before_build new file mode 100644 index 00000000..5fdd18ff --- /dev/null +++ b/hook/.hooks/before_build @@ -0,0 +1 @@ +npm install diff --git a/hook/fixture/package.json b/hook/fixture/package.json new file mode 100644 index 00000000..b77ebc5a --- /dev/null +++ b/hook/fixture/package.json @@ -0,0 +1,15 @@ +{ + "name": "fixture", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "leftpad": "0.0.1" + } +} diff --git a/hook/hook.go b/hook/hook.go new file mode 100644 index 00000000..c98defe6 --- /dev/null +++ b/hook/hook.go @@ -0,0 +1,73 @@ +package hook + +import ( + "io/ioutil" + "os" + "os/exec" + "path/filepath" + + "github.com/metrue/fx/utils" +) + +// Hooker defines hook interface +type Hooker interface { + Run() error +} + +// Hook to run +type Hook struct { + name string + script string +} + +// New a hook +func New(name string, script string, workdir string) *Hook { + return &Hook{ + name: name, + script: script, + } +} + +// Run execute a hook +func (h *Hook) Run(workdir string) error { + var script string + if !utils.IsRegularFile(h.script) { + hookScript, err := ioutil.TempFile(os.TempDir(), "fx-hook-script-") + if err != nil { + return err + } + defer os.Remove(hookScript.Name()) + + content := []byte(h.script) + if _, err = hookScript.Write(content); err != nil { + return err + } + if err := hookScript.Close(); err != nil { + return err + } + script = hookScript.Name() + } else { + absScript, err := filepath.Abs(h.script) + if err != nil { + return err + } + script = absScript + } + + cmd := exec.Command("/bin/sh", script) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if workdir != "" { + cmd.Dir = workdir + } + + if err := cmd.Run(); err != nil { + return err + } + return nil +} + +// Name hook name +func (h *Hook) Name() string { + return h.name +} diff --git a/hook/hook_manager.go b/hook/hook_manager.go new file mode 100644 index 00000000..909d8616 --- /dev/null +++ b/hook/hook_manager.go @@ -0,0 +1,54 @@ +package hook + +import ( + "os" + "path/filepath" + + "github.com/metrue/fx/utils" +) + +// HookNameBeforeBuild before build hook +const HookNameBeforeBuild = "before_build" + +// RunBeforeBuildHook trigger before_build hook +func RunBeforeBuildHook(workdir string) error { + hooks, err := descovery("") + if err != nil { + return err + } + for _, h := range hooks { + if h.Name() == HookNameBeforeBuild { + if err := h.Run(workdir); err != nil { + return err + } + } + } + return nil +} + +func descovery(hookdir string) ([]*Hook, error) { + if hookdir == "" { + dir, err := os.Getwd() + if err != nil { + return nil, err + } + hookdir = filepath.Join(dir, ".hooks") + } + + hooks := []*Hook{} + if !utils.IsDir(hookdir) { + return hooks, nil + } + if err := filepath.Walk(hookdir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.Name() == HookNameBeforeBuild { + hooks = append(hooks, New("before_build", path, "")) + } + return nil + }); err != nil { + return nil, err + } + return hooks, nil +} diff --git a/hook/hook_manager_test.go b/hook/hook_manager_test.go new file mode 100644 index 00000000..a034f313 --- /dev/null +++ b/hook/hook_manager_test.go @@ -0,0 +1,40 @@ +package hook + +import ( + "os" + "path/filepath" + "testing" +) + +func TestHookManager(t *testing.T) { + t.Run("descovery in default hookdir .hooks", func(t *testing.T) { + hooks, err := descovery("") + if err != nil { + t.Fatal(err) + } + + if len(hooks) != 1 { + t.Fatalf("should have one hook, but got %d", len(hooks)) + } + + if hooks[0].Name() != HookNameBeforeBuild { + t.Fatalf("should be before_build hook, but got %s", hooks[0].Name()) + } + }) + + t.Run("descovery in empty hookdir", func(t *testing.T) { + hooks, err := descovery(filepath.Join(os.TempDir(), ".hooks")) + if err != nil { + t.Fatal(err) + } + if len(hooks) != 0 { + t.Fatalf("should get 0 hooks, but got %d", len(hooks)) + } + }) + + t.Run("run before_build hook", func(t *testing.T) { + if err := RunBeforeBuildHook("fixture"); err != nil { + t.Fatal(err) + } + }) +} diff --git a/hook/hook_test.go b/hook/hook_test.go new file mode 100644 index 00000000..f9e5ca5f --- /dev/null +++ b/hook/hook_test.go @@ -0,0 +1,21 @@ +package hook + +import ( + "testing" +) + +func TestHook(t *testing.T) { + t.Run("text", func(t *testing.T) { + h := New("before_build", "npm install leftpad", "fixture") + if err := h.Run("fixture"); err != nil { + t.Fatal(err) + } + }) + + t.Run("script", func(t *testing.T) { + h := New("before_build", ".hooks/before_build", "fixture") + if err := h.Run("fixture"); err != nil { + t.Fatal(err) + } + }) +} diff --git a/middlewares/build.go b/middlewares/build.go index 6436c4e9..471724c1 100644 --- a/middlewares/build.go +++ b/middlewares/build.go @@ -7,6 +7,7 @@ import ( containerruntimes "github.com/metrue/fx/container_runtimes" "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" @@ -54,6 +55,9 @@ func Build(ctx context.Contexter) (err error) { if err := packer.Pack(workdir, sources...); err != nil { return err } + if err := hook.RunBeforeBuildHook(workdir); err != nil { + return err + } } cloudType := ctx.Get("cloud_type").(string) diff --git a/packer/a_packer-packr.go b/packer/a_packer-packr.go index 5b69ffdc..d9372a28 100644 --- a/packer/a_packer-packr.go +++ b/packer/a_packer-packr.go @@ -24,7 +24,7 @@ func init() { packr.PackJSONBytes("./images", "julia/deps.jl", "\"b3BlbigiL2FwcC9SRVFVSVJFIikgZG8gZgoJZGVwcyA9IHJlYWRsaW5lcyhmKQoJZm9yIGQgaW4gZGVwcwoJCVBrZy5hZGQoZCkKCWVuZAplbmQKUGtnLmJ1aWxkKCJIdHRwUGFyc2VyIikK\"") packr.PackJSONBytes("./images", "julia/fx.jl", "\"CnN0cnVjdCBJbnB1dAogICAgYTo6TnVtYmVyCiAgICBiOjpOdW1iZXIKZW5kCgpmeCA9IGZ1bmN0aW9uKGlucHV0OjpJbnB1dCkKICAgIHJldHVybiBpbnB1dC5hICsgaW5wdXQuYgplbmQK\"") packr.PackJSONBytes("./images", "node/Dockerfile", "\"RlJPTSBtZXRydWUvZngtbm9kZS1iYXNlCgpDT1BZIC4gLgpFWFBPU0UgMzAwMApDTUQgWyJub2RlIiwgImFwcC5qcyJdCg==\"") - packr.PackJSONBytes("./images", "node/app.js", "\"Y29uc3QgS29hID0gcmVxdWlyZSgna29hJyk7CmNvbnN0IGJvZHlQYXJzZXIgPSByZXF1aXJlKCdrb2EtYm9keXBhcnNlcicpOwpjb25zdCBmeCA9IHJlcXVpcmUoJy4vZngnKTsKCmNvbnN0IGFwcCA9IG5ldyBLb2EoKTsKYXBwLnVzZShib2R5UGFyc2VyKCkpOwphcHAudXNlKGZ4KTsKCmFwcC5saXN0ZW4oMzAwMCk7Cg==\"") + packr.PackJSONBytes("./images", "node/app.js", "\"Y29uc3QgS29hID0gcmVxdWlyZSgna29hJyk7CmNvbnN0IGJvZHlQYXJzZXIgPSByZXF1aXJlKCdrb2EtYm9keXBhcnNlcicpOwpjb25zdCBjb3JzID0gcmVxdWlyZSgnQGtvYS9jb3JzJyk7CmNvbnN0IGZ4ID0gcmVxdWlyZSgnLi9meCcpOwoKY29uc3QgYXBwID0gbmV3IEtvYSgpOwphcHAudXNlKGNvcnMoewogIG9yaWdpbjogJyonLAp9KSk7CmFwcC51c2UoYm9keVBhcnNlcigpKTsKYXBwLnVzZShmeCk7CgphcHAubGlzdGVuKDMwMDApOwo=\"") packr.PackJSONBytes("./images", "node/fx.js", "\"bW9kdWxlLmV4cG9ydHMgPSAoY3R4KSA9PiB7CiAgY3R4LmJvZHkgPSAnaGVsbG8gd29ybGQnCn0K\"") packr.PackJSONBytes("./images", "perl/Dockerfile", "\"RlJPTSBtZXRydWUvZngtcGVybC1iYXNlCgpBREQgLiAuCgpFWFBPU0UgMzAwMApDTUQgWyJwZXJsIiwgImFwcC5wbCIsICJkYWVtb24iXQo=\"") packr.PackJSONBytes("./images", "perl/app.pl", "\"dXNlIE1vam9saWNpb3VzOjpMaXRlOwoKcmVxdWlyZSAiLi9meC5wbCI7CgpnZXQgJy8nID0+IHN1YiB7CiAgbXkgJGN0eCA9IHNoaWZ0OwogIG15ICRyZXMgPSBmeCgkY3R4KTsKICAkY3R4LT5yZW5kZXIoanNvbiA9PiAkcmVzKTsKfTsKCnBvc3QgJy8nID0+IHN1YiB7CiAgbXkgJGN0eCA9IHNoaWZ0OwogIG15ICRyZXMgPSBmeCgkY3R4KTsKICAkY3R4LT5yZW5kZXIoanNvbiA9PiAkcmVzKTsKfTsKCmFwcC0+c3RhcnQ7Cg==\"") diff --git a/packer/images/node/app.js b/packer/images/node/app.js index fcdc6449..83f2f0f3 100644 --- a/packer/images/node/app.js +++ b/packer/images/node/app.js @@ -1,8 +1,12 @@ 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);