diff --git a/bin/pre-commit.sh b/bin/pre-commit.sh index 5e0b45626..67edf1578 100755 --- a/bin/pre-commit.sh +++ b/bin/pre-commit.sh @@ -11,6 +11,10 @@ cd "$base_dir" || { rc=0 +function buildPlugins { +go build -buildmode plugin -tags=plugin -o ./pkg/plugins/builtin/executable.so ./pkg/plugins/builtin/executable.go +} + function runTest { local name=$1 local result="SUCCESS" @@ -36,6 +40,7 @@ function testExamples { mdrip --mode test --label test README.md ./examples } +runTest buildPlugins runTest testGoLangCILint runTest testGoTest runTest testExamples diff --git a/pkg/plugins/builtin/executable.go b/pkg/plugins/builtin/executable.go new file mode 100644 index 000000000..f3114f472 --- /dev/null +++ b/pkg/plugins/builtin/executable.go @@ -0,0 +1,72 @@ +// +build plugin + +package main + +import ( + "bytes" + "os" + "os/exec" + "path/filepath" + + "github.com/ghodss/yaml" + "sigs.k8s.io/kustomize/pkg/ifc" + "sigs.k8s.io/kustomize/pkg/pgmconfig" + "sigs.k8s.io/kustomize/pkg/resmap" +) + +type plugin struct { + name string + input string + rf *resmap.Factory +} + +var KustomizePlugin plugin + +func (p *plugin) Config( + ldr ifc.Loader, rf *resmap.Factory, k ifc.Kunstructured) error { + dir := filepath.Join(pgmconfig.ConfigRoot(), "plugins") + id := k.GetGvk() + p.name = filepath.Join(dir, id.Group, id.Version, id.Kind) + content, err := yaml.Marshal(k) + if err != nil { + return err + } + p.input = string(content) + p.rf = rf + return nil +} + +func (p *plugin) Generate() (resmap.ResMap, error) { + return p.run(nil) +} + +func (p *plugin) Transformer(rm resmap.ResMap) error { + result, err := p.run(rm) + if err != nil { + return err + } + for id := range rm { + delete(rm, id) + } + for id, r := range result { + rm[id] = r + } + return nil +} + +func (p *plugin) run(rm resmap.ResMap) (resmap.ResMap, error) { + cmd := exec.Command(p.name, p.input) + cmd.Env = os.Environ() + if rm != nil { + content, err := rm.EncodeAsYaml() + if err != nil { + return nil, err + } + cmd.Stdin = bytes.NewReader(content) + } + output, err := cmd.Output() + if err != nil { + return nil, err + } + return p.rf.NewResMapFromBytes(output) +} diff --git a/pkg/plugins/generators.go b/pkg/plugins/generators.go index d40707e44..61ec5f156 100644 --- a/pkg/plugins/generators.go +++ b/pkg/plugins/generators.go @@ -47,14 +47,13 @@ func (l generatorLoader) Load( } var result []transformers.Generator for id, res := range rm { - fileName := pluginFileName(l.pc, id) - c, err := loadAndConfigurePlugin(fileName, l.ldr, l.rf, res) + c, err := loadAndConfigurePlugin(l.pc.DirectoryPath, id, l.ldr, l.rf, res) if err != nil { return nil, err } g, ok := c.(transformers.Generator) if !ok { - return nil, fmt.Errorf("plugin %s not a generator", fileName) + return nil, fmt.Errorf("plugin %s not a generator", id.String()) } result = append(result, g) } diff --git a/pkg/plugins/transformers.go b/pkg/plugins/transformers.go index c691fb7e4..920446dd9 100644 --- a/pkg/plugins/transformers.go +++ b/pkg/plugins/transformers.go @@ -18,8 +18,10 @@ package plugins import ( "fmt" + "os" "path/filepath" "plugin" + "runtime" "github.com/pkg/errors" kplugin "sigs.k8s.io/kustomize/k8sdeps/kv/plugin" @@ -57,29 +59,53 @@ func (l transformerLoader) Load( } var result []transformers.Transformer for id, res := range rm { - fileName := pluginFileName(l.pc, id) - c, err := loadAndConfigurePlugin(fileName, l.ldr, l.rf, res) + c, err := loadAndConfigurePlugin(l.pc.DirectoryPath, id, l.ldr, l.rf, res) if err != nil { return nil, err } t, ok := c.(transformers.Transformer) if !ok { - return nil, fmt.Errorf("plugin %s not a transformer", fileName) + return nil, fmt.Errorf("plugin %s not a transformer", id.String()) } result = append(result, t) } return result, nil } -func pluginFileName(pc *types.PluginConfig, id resid.ResId) string { +func goPluginFileName(dir string, id resid.ResId) string { + return execPluginFileName(dir, id) + ".so" +} + +func execPluginFileName(dir string, id resid.ResId) string { return filepath.Join( - pc.DirectoryPath, - id.Gvk().Group, id.Gvk().Version, id.Gvk().Kind+".so") + dir, + id.Gvk().Group, id.Gvk().Version, id.Gvk().Kind) +} + +// isExecAvailable checks if an executable is available +func isExecAvailable(name string) bool { + f, err := os.Stat(name) + if os.IsNotExist(err) { + return false + } + if f.Mode()&0111 != 0000 { + return true + } + return false } func loadAndConfigurePlugin( - fileName string, ldr ifc.Loader, + dir string, id resid.ResId, + ldr ifc.Loader, rf *resmap.Factory, res *resource.Resource) (Configurable, error) { + var fileName string + exec := execPluginFileName(dir, id) + if isExecAvailable(exec) { + _, f, _, _ := runtime.Caller(1) + fileName = filepath.Join(filepath.Dir(f), "builtin", "executable.so") + } else { + fileName = goPluginFileName(dir, id) + } goPlugin, err := plugin.Open(fileName) if err != nil { return nil, errors.Wrapf(err, "plugin %s fails to load", fileName) diff --git a/pkg/target/generatorplugin_test.go b/pkg/target/generatorplugin_test.go index 097417efd..d64d4ff02 100644 --- a/pkg/target/generatorplugin_test.go +++ b/pkg/target/generatorplugin_test.go @@ -127,3 +127,37 @@ metadata: type: Opaque `) } + +func TestConfigMapGenerator(t *testing.T) { + tc := NewTestEnvController(t).Set() + //defer tc.Reset() + + tc.BuildExecPlugin( + "someteam.example.com", "v1", "ConfigMapGenerator") + + th := NewKustTestHarnessWithPluginConfig( + t, "/app", plugin.ActivePluginConfig()) + th.writeK("/app", ` +generators: +- configmapGenerator.yaml +`) + th.writeF("/app/configmapGenerator.yaml", ` +apiVersion: someteam.example.com/v1 +kind: ConfigMapGenerator +metadata: + name: some-random-name +`) + m, err := th.makeKustTarget().MakeCustomizedResMap() + if err != nil { + t.Fatalf("Err: %v", err) + } + th.assertActualEqualsExpected(m, ` +apiVersion: v1 +data: + password: secret + username: admin +kind: ConfigMap +metadata: + name: example-configmap-test +`) +} diff --git a/pkg/target/testenvcontroller_test.go b/pkg/target/testenvcontroller_test.go index f68c182f1..58ed53d3c 100644 --- a/pkg/target/testenvcontroller_test.go +++ b/pkg/target/testenvcontroller_test.go @@ -16,6 +16,7 @@ package target_test import ( "io/ioutil" "os" + "os/exec" "path/filepath" "sigs.k8s.io/kustomize/k8sdeps/kv/plugin" "testing" @@ -57,6 +58,28 @@ func (x *TestEnvController) BuildGoPlugin(g, v, k string) { } } +func (x *TestEnvController) BuildExecPlugin(name ...string) { + obj := filepath.Join( + append([]string{x.workDir, pgmconfig.ProgramName, plugin.PluginRoot}, name...)...) + + srcRoot, err := plugins.DefaultSrcRoot() + if err != nil { + x.t.Error(err) + } + + src := filepath.Join( + append([]string{srcRoot}, name...)...) + + if err := os.MkdirAll(filepath.Dir(obj), 0755); err != nil { + x.t.Errorf("error making directory: %s", filepath.Dir(obj)) + } + cmd := exec.Command("cp", src, obj) + cmd.Env = os.Environ() + if err := cmd.Run(); err != nil { + x.t.Errorf("error copying %s: %v", src, err) + } +} + func (x *TestEnvController) makeCompiler() *plugins.Compiler { // The plugin loader wants to find object code under // $XDG_CONFIG_HOME/kustomize/plugins diff --git a/plugins/someteam.example.com/v1/ConfigMapGenerator b/plugins/someteam.example.com/v1/ConfigMapGenerator new file mode 100755 index 000000000..d0df0adaa --- /dev/null +++ b/plugins/someteam.example.com/v1/ConfigMapGenerator @@ -0,0 +1,11 @@ +#!/bin/bash + +echo " +kind: ConfigMap +apiVersion: v1 +metadata: + name: example-configmap-test +data: + username: admin + password: secret +" \ No newline at end of file