diff --git a/bin/pluginator.sh b/bin/pluginator.sh deleted file mode 100755 index 7e0c93b47..000000000 --- a/bin/pluginator.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -# -# Converts Go-based kustomize plugins in -# sigs.k8s.io/kustomize/plugin/builtin -# (all in package 'main') to generator and -# transformer factory functions in -# sigs.k8s.io/kustomize/plugin/builtingen -# (all in package 'builtingen'). -# -# Cannot put all these in the same dir, since -# plugins must be in the 'main' package, -# the factory functions cannot be in 'main', -# Go disallows multiple packages in one dir. - -set -e - -myGoPath=$1 -if [ -z ${1+x} ]; then - myGoPath=$GOPATH -fi - -if [ -z "$myGoPath" ]; then - echo "Must specify a GOPATH" - exit 1 -fi - -dir=$myGoPath/src/sigs.k8s.io/kustomize - -if [ ! -d "$dir" ]; then - echo "$dir is not a directory." - exit 1 -fi - -echo Generating linkable plugins... - -pushd $dir >& /dev/null - -/bin/rm -rf plugin/builtingen -mkdir plugin/builtingen -GOPATH=$myGoPath go generate --tags plugin \ - sigs.k8s.io/kustomize/plugin/builtin -ls -C1 plugin/builtingen - -popd >& /dev/null - -echo All done. diff --git a/bin/pre-commit.sh b/bin/pre-commit.sh index f2ca63916..dd71d8f9e 100755 --- a/bin/pre-commit.sh +++ b/bin/pre-commit.sh @@ -47,7 +47,7 @@ function testExamples { } function generateCode { - ./bin/pluginator.sh $oldGoPath + ./plugin/generateBuiltins.sh $oldGoPath } # Use of GOPATH is optional if go modules are diff --git a/cmd/pluginator/main.go b/cmd/pluginator/main.go deleted file mode 100644 index f3e646b6d..000000000 --- a/cmd/pluginator/main.go +++ /dev/null @@ -1,338 +0,0 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// pluginator is a code generator that converts -// kustomize generator (G) and/or transformer (T) -// Go plugins to statically linkable code. -// -// Arises from following requirements: -// -// * extension -// -// kustomize does two things - generate or -// transform k8s resources. Plugins let -// users write their own G&T's without -// having to fork kustomize and learn its -// internals. -// -// * dogfooding -// -// A G&T extension framework one can trust -// should be used by its authors to deliver -// builtin G&T's. -// -// * distribution -// -// kustomize should be distributable via -// `go get` and should run where Go -// programs are expected to run. -// -// The extension requirement led to the creation -// of a framework that accommodates writing a -// G or T as either -// -// * an 'exec' plugin (any executable file -// runnable as a kustomize subprocess), or -// -// * as a Go plugin - see -// https://golang.org/pkg/plugin. -// -// The dogfooding (and an implicit performance -// requirement) requires a 'builtin' G or T to -// be written as a Go plugin. -// -// The distribution ('go get') requirement demands -// conversion of Go plugins to statically linked -// code, hence this program. -// -// -// HOW PLUGINS RUN -// -// Assume a file 'secGen.yaml' containing -// -// apiVersion: someteam.example.com/v1 -// kind: SecretGenerator -// metadata: -// name: makesecrets -// name: mySecret -// behavior: merge -// envs: -// - db.env -// - fruit.env -// -// If this file were referenced by a kustomization -// file in its 'generators' field, kustomize would -// -// * Read 'secGen.yaml'. -// -// * Use the value of $XGD_CONFIG_HOME and -// 'apiversion' and to find an executable -// named 'SecretGenerator' to use as -// an exec plugin, or failing that, -// -// * use the same info to load a Go plugin -// object file called 'SecretGenerator.so'. -// -// * Send either the file name 'secGen.yaml' as -// the first arg to the exec plugin, or send its -// contents to the go plugin's Config method. -// -// * Use the plugin to generate and/or transform. -// -// -// GO PLUGIN CONVENTIONS -// -// A .go file can be a Go plugin if it declares -// 'main' as it's package, and exports a symbol to -// which useful functions are attached. It can -// further be used as a _kustomize_ plugin if -// those functions implement the Configurable, -// Generator and Transformer interfaces. -// -// Converting the plugin file to a normal .go package -// file is a matter string substitution permitted -// by the following conventions. -// -// * Configuration of builtin plugins: -// -// Config file looks like -// -// --------------------------------------------- -// apiVersion: builtin -// kind: SecretGenerator -// metadata: -// name: whatever -// otherFields: whatever -// --------------------------------------------- -// -// The apiVersion must be 'builtin'. -// -// For non-builtins the apiVersion can be any legal -// apiVersion value, e.g. 'someteam.example.com/v1beta1' -// -// The builtin source must be at: -// -// ${repo}/plugin/${apiVersion}/${kind}.go -// -// where repo=$GOPATH/src/sigs.k8s.io/kustomize -// -// k8s wants 'kind' values to follow CamelCase, -// while Go style wants (but doesn't demand) -// lowercase file names. -// -// kustomize will accept either idiom, but the Go file -// name must be ${kind}.go (CamelCase allowed). -// -// * Source follows this pattern -// -// --------------------------------------------- -// // +build plugin -// -// //go:generate go run sigs.k8s.io/kustomize/cmd/pluginator -// package main -// import ... -// type plugin struct{...} -// var KustomizePlugin plugin -// func (p *plugin) Config( -// ldr ifc.Loader, rf *resmap.Factory, -// k ifc.Kunstructured) error {...} -// func (p *plugin) Generate( -// ) (resmap.ResMap, error) {...} -// func (p *plugin) Transform( -// m resmap.ResMap) error {...} -// --------------------------------------------- -// -// - The 2nd line must be empty. -// - One may `go fmt` this file. -// - There's no mention of 'SecretGenerator' -// in this file; that binding is done by -// the plugin loader or pluginator. -// -// * To compile this for loading as a Go plugin: -// -// repo=$GOPATH/src/sigs.k8s.io/kustomize -// dir=$repo/plugin/builtin -// go build -buildmode plugin -tags=plugin \ -// -o $dir/SecretGenerator.so \ -// $dir/SecretGenerator.go -// -// * To generate code: -// -// repo=$GOPATH/src/sigs.k8s.io/kustomize -// cd $repo/plugin/builtin -// go generate --tags plugin . -// -// This creates -// -// $repo/plugin/builtingen/SecretGenerator.go -// -// etc. -// -// * Generated plugins are used in kustomize via -// -// --------------------------------------------- -// package whatever -// import "sigs.k8s.io/kustomize/plugin/builtingen -// ... -// g := builtingen.NewSecretGenerator() -// g.Config(l, rf, k) -// resources, err := g.Generate() -// err = g.Transform(resources) -// // Eventually emit resources. -// --------------------------------------------- -// -package main - -import ( - "bufio" - "fmt" - "log" - "os" - "path/filepath" - "strings" - - "sigs.k8s.io/kustomize/pkg/pgmconfig" - "sigs.k8s.io/kustomize/pkg/plugins" -) - -func main() { - root := fileRoot() - file, err := os.Open(root + ".go") - if err != nil { - log.Fatal(err) - } - defer file.Close() - scanner := bufio.NewScanner(file) - processBoilerPlate(scanner, file.Name()) - - w := NewWriter(root) - defer w.close() - - // This particular phrasing is required. - w.write( - fmt.Sprintf( - "// Code generated by pluginator on %s; DO NOT EDIT.", - root)) - w.write("package builtingen") - - for scanner.Scan() { - l := scanner.Text() - if strings.HasPrefix(l, "//go:generate") { - continue - } - if l == "var "+plugins.PluginSymbol+" plugin" { - w.write("func New" + root + "Plugin() *" + root + "Plugin {") - w.write(" return &" + root + "Plugin{}") - w.write("}") - continue - } - w.write(l) - } - if err := scanner.Err(); err != nil { - log.Fatal(err) - } -} - -func fileRoot() string { - n := os.Getenv("GOFILE") - if !strings.HasSuffix(n, ".go") { - log.Fatalf("expecting .go suffix on %s", n) - } - return n[:len(n)-len(".go")] -} - -func processBoilerPlate(s *bufio.Scanner, f string) { - if !s.Scan() { - log.Fatalf("1: %s not long enough", f) - } - first := s.Text() - if !s.Scan() { - log.Fatalf("2: %s not long enough", f) - } - next := s.Text() - if !hasPluginTag(first, next) { - log.Fatalf("%s lacks plugin tag", f) - } - gotMain := false - for !gotMain && s.Scan() { - next = s.Text() - gotMain = strings.HasPrefix(next, "package main") - } - if !gotMain { - log.Fatalf("%s missing package main", f) - } -} - -func hasPluginTag(first, next string) bool { - return strings.HasPrefix(first, "// +build plugin") && - len(next) == 0 -} - -type writer struct { - root string - f *os.File -} - -func NewWriter(r string) *writer { - n := makeSrcFileName(r) - f, err := os.Create(n) - if err != nil { - log.Fatalf("unable to create `%s`; %v", n, err) - } - return &writer{root: r, f: f} -} - -func makeSrcFileName(root string) string { - return filepath.Join( - os.Getenv("GOPATH"), - "src", - pgmconfig.DomainName, - pgmconfig.ProgramName, - pgmconfig.PluginRoot, - "builtingen", - root+".go") -} - -func (w *writer) close() { w.f.Close() } - -func (w *writer) write(line string) { - _, err := w.f.WriteString(w.filter(line) + "\n") - if err != nil { - log.Printf("Trouble writing: %s", line) - log.Fatal(err) - } -} - -func (w *writer) filter(in string) string { - if ok, newer := w.replace(in, "type plugin struct"); ok { - return newer - } - if ok, newer := w.replace(in, "*plugin)"); ok { - return newer - } - return in -} - -// replace 'plugin' with 'FooPlugin' in context -// sensitive manner. -func (w *writer) replace(in, target string) (bool, string) { - if !strings.Contains(in, target) { - return false, "" - } - newer := strings.Replace( - target, "plugin", w.root+"Plugin", 1) - return true, strings.Replace(in, target, newer, 1) -} diff --git a/docs/plugins.md b/docs/plugins.md index 397ab7bbf..fe0ca45a9 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -87,8 +87,8 @@ to a sea of objects that kustomize transforms and emits. The specified order of transformers in the -`transformers` field is, however, respected, as -transformers aren't expected to be commutative. +`transformers` field should be respected, as +transformers cannot be expected to be commutative. ## Execution @@ -115,7 +115,8 @@ other [k8s object]), kustomize will first look for an _executable_ file called ``` -$XDG_CONFIG_HOME/kustomize/plugin/${apiVersion}/${kind} +$XDG_CONFIG_HOME/kustomize/plugin + /${apiVersion}/LOWERCASE(${kind})/${kind} ``` The default value of `XDG_CONFIG_HOME` is `$HOME/.config`. @@ -150,7 +151,7 @@ reminder. ### Exec plugins -[chartinflator]: ../plugin/someteam.example.com/v1/ChartInflator +[chartinflator]: ../plugin/someteam.example.com/v1/chartinflator/ChartInflator See this example [helm chart inflator][chartInflator]. @@ -183,15 +184,13 @@ marshalled resources on `stdin` and capture ### Go plugins [Go plugin]: https://golang.org/pkg/plugin/ -[secretgenerator]: ../plugin/builtin/SecretGenerator.go +[servicegenerator]: ../plugin/someteam.example.com/v1/someservicegenerator/SomeServiceGenerator.go -See this example [secret generator][secretGenerator]. +See this example [service generator][serviceGenerator]. A [Go plugin] for kustomize looks like this: > ``` -> +build plugin -> > package main > > import ( @@ -205,8 +204,9 @@ A [Go plugin] for kustomize looks like this: > var KustomizePlugin plugin > > func (p *plugin) Config( -> ldr ifc.Loader, rf *resmap.Factory, -> k ifc.Kunstructured) error {...} +> ldr ifc.Loader, +> rf *resmap.Factory, +> c []byte) error {...} > > func (p *plugin) Generate() (resmap.ResMap, error) {...} > @@ -218,7 +218,7 @@ The use of the identifiers `plugin`, `Configurable`, `Generator`, `Transformer` as shown is _required_. -The plugin author should of course change the +The plugin author will change the contents of the `plugin` struct, and the three method bodies, and add imports as desired. @@ -227,10 +227,8 @@ source code is sitting right next to where the shared object (`.so`) files are expected to be: ``` -dir=$XDG_CONFIG_HOME/kustomize/plugin/${apiVersion} -go build -buildmode plugin -tags=plugin \ - -o $dir/${kind}.so \ - $dir/${kind}.go +d=$XDG_CONFIG_HOME/kustomize/plugin/${apiVersion}/LOWERCASE(${kind}) +go build -buildmode plugin -o $d/${kind}.so $d/${kind}.go ``` #### Caveats @@ -241,9 +239,9 @@ Go plugins allow kustomize extensions that uses to test its _builtin_ generators and transformers, - * run without the performance hit of firing up a - subprocess and marshalling/unmarshalling data - for each plugin run. + * run without the performance cost of firing up a + subprocess and marshalling/unmarshalling all + resource data for each plugin run. [ELF]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format @@ -263,3 +261,12 @@ a failure at load time. Exec plugins also lack provenance, but don't suffer from the skew problem. + +In either case, at the time of writing the proper +way to share a plugin is as a tar file of source code +and associated data, developed and unpacked under +`kustomize/plugin`. In the case of a Go plugin, the +end user must compile it (described above), and may +need to compile kustomize as well. If people use +Go plugins, more tooling will be built to make +plugin sharing easier. diff --git a/pkg/plugins/compiler.go b/pkg/plugins/compiler.go index f156f1969..601b45065 100644 --- a/pkg/plugins/compiler.go +++ b/pkg/plugins/compiler.go @@ -21,6 +21,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "time" "sigs.k8s.io/kustomize/k8sdeps/kv/plugin" @@ -95,7 +96,8 @@ func goBin() string { // Compile reads ${srcRoot}/${g}/${v}/${k}.go // and writes ${objRoot}/${g}/${v}/${k}.so func (b *Compiler) Compile(g, v, k string) error { - objDir := filepath.Join(b.objRoot, g, v) + lowK := strings.ToLower(k) + objDir := filepath.Join(b.objRoot, g, v, lowK) objFile := filepath.Join(objDir, k) + ".so" if RecentFileExists(objFile) { // Skip rebuilding it. @@ -105,7 +107,7 @@ func (b *Compiler) Compile(g, v, k string) error { if err != nil { return err } - srcFile := filepath.Join(b.srcRoot, g, v, k) + ".go" + srcFile := filepath.Join(b.srcRoot, g, v, lowK, k) + ".go" if !FileExists(srcFile) { return fmt.Errorf( "cannot find source %s", srcFile) @@ -114,7 +116,6 @@ func (b *Compiler) Compile(g, v, k string) error { "build", "-buildmode", "plugin", - "-tags=plugin", "-o", objFile, srcFile, } goBin := goBin() diff --git a/pkg/plugins/compiler_test.go b/pkg/plugins/compiler_test.go index 233f6bfbe..3d67b1269 100644 --- a/pkg/plugins/compiler_test.go +++ b/pkg/plugins/compiler_test.go @@ -42,7 +42,7 @@ func TestCompiler(t *testing.T) { expectObj := filepath.Join( c.ObjRoot(), - "someteam.example.com", "v1", "DatePrefixer.so") + "someteam.example.com", "v1", "dateprefixer", "DatePrefixer.so") if FileExists(expectObj) { t.Errorf("obj file should not exist yet: %s", expectObj) } @@ -56,7 +56,7 @@ func TestCompiler(t *testing.T) { expectObj = filepath.Join( c.ObjRoot(), - "builtin", "", "SecretGenerator.so") + "builtin", "", "secretgenerator", "SecretGenerator.so") if FileExists(expectObj) { t.Errorf("obj file should not exist yet: %s", expectObj) } diff --git a/pkg/plugins/execplugin.go b/pkg/plugins/execplugin.go index bf46c0925..294097eb1 100644 --- a/pkg/plugins/execplugin.go +++ b/pkg/plugins/execplugin.go @@ -22,7 +22,6 @@ import ( "io/ioutil" "os" "os/exec" - "path/filepath" "strings" "syscall" @@ -39,8 +38,8 @@ const ( // ExecPlugin record the name and args of an executable // It triggers the executable generator and transformer type ExecPlugin struct { - // name of the executable - name string + // absolute path of the executable + path string // Optional command line arguments to the executable // pulled from specially named fields in cfg. @@ -57,15 +56,13 @@ type ExecPlugin struct { ldr ifc.Loader } -func NewExecPlugin(root string, id resid.ResId) *ExecPlugin { - return &ExecPlugin{ - name: filepath.Join(root, pluginPath(id)), - } +func NewExecPlugin(p string) *ExecPlugin { + return &ExecPlugin{path: p} } // isAvailable checks to see if the plugin is available func (p *ExecPlugin) isAvailable() bool { - f, err := os.Stat(p.name) + f, err := os.Stat(p.path) if os.IsNotExist(err) { return false } @@ -164,7 +161,7 @@ func (p *ExecPlugin) invokePlugin(input []byte) ([]byte, error) { if err != nil { return nil, err } - cmd := exec.Command(p.name, args...) + cmd := exec.Command(p.path, args...) cmd.Env = p.getEnv() cmd.Stdin = bytes.NewReader(input) cmd.Stderr = os.Stderr @@ -226,7 +223,7 @@ func (p *ExecPlugin) updateResMapValues(output []byte, rm resmap.ResMap) error { idString, ok := annotations[idAnnotation] if !ok { return fmt.Errorf("the transformer %s should not remove annotation %s", - p.name, idAnnotation) + p.path, idAnnotation) } id := resid.ResId{} err := yaml.Unmarshal([]byte(idString), &id) diff --git a/pkg/plugins/execplugin_test.go b/pkg/plugins/execplugin_test.go index 9651ffc05..2b0daf8ad 100644 --- a/pkg/plugins/execplugin_test.go +++ b/pkg/plugins/execplugin_test.go @@ -51,8 +51,9 @@ s/$BAR/bar/g `)) p := NewExecPlugin( - plugin.DefaultPluginConfig().DirectoryPath, - pluginConfig.Id()) + AbsolutePluginPath( + plugin.DefaultPluginConfig(), + pluginConfig.Id())) yaml, err := pluginConfig.AsYAML() if err != nil { @@ -60,9 +61,9 @@ s/$BAR/bar/g } p.Config(ldr, rf, yaml) - expected := "/kustomize/plugin/someteam.example.com/v1/SedTransformer" - if !strings.HasSuffix(p.name, expected) { - t.Fatalf("expected suffix '%s', got '%s'", expected, p.name) + expected := "/kustomize/plugin/someteam.example.com/v1/sedtransformer/SedTransformer" + if !strings.HasSuffix(p.path, expected) { + t.Fatalf("expected suffix '%s', got '%s'", expected, p.path) } expected = `apiVersion: someteam.example.com/v1 diff --git a/pkg/plugins/loader.go b/pkg/plugins/loader.go index 42ea16061..c5ef29ce0 100644 --- a/pkg/plugins/loader.go +++ b/pkg/plugins/loader.go @@ -20,6 +20,7 @@ import ( "fmt" "path/filepath" "plugin" + "strings" "github.com/pkg/errors" "sigs.k8s.io/kustomize/pkg/ifc" @@ -96,8 +97,20 @@ func (l *Loader) LoadTransformer( return t, nil } -func pluginPath(id resid.ResId) string { - return filepath.Join(id.Gvk().Group, id.Gvk().Version, id.Gvk().Kind) +func relativePluginPath(id resid.ResId) string { + return filepath.Join( + id.Gvk().Group, + id.Gvk().Version, + strings.ToLower(id.Gvk().Kind)) +} + +func AbsolutePluginPath(pc *types.PluginConfig, id resid.ResId) string { + return filepath.Join( + pc.DirectoryPath, relativePluginPath(id), id.Gvk().Kind) +} + +func (l *Loader) absolutePluginPath(id resid.ResId) string { + return AbsolutePluginPath(l.pc, id) } func (l *Loader) loadAndConfigurePlugin( @@ -106,7 +119,8 @@ func (l *Loader) loadAndConfigurePlugin( return nil, errors.Errorf( "plugins not enabled, but trying to load %s", res.Id()) } - if p := NewExecPlugin(l.pc.DirectoryPath, res.Id()); p.isAvailable() { + if p := NewExecPlugin( + l.absolutePluginPath(res.Id())); p.isAvailable() { c = p } else { c, err = l.loadGoPlugin(res.Id()) @@ -135,26 +149,26 @@ func (l *Loader) loadAndConfigurePlugin( var registry = make(map[string]Configurable) func (l *Loader) loadGoPlugin(id resid.ResId) (c Configurable, err error) { + regId := relativePluginPath(id) var ok bool - path := pluginPath(id) - if c, ok = registry[path]; ok { + if c, ok = registry[regId]; ok { return c, nil } - name := filepath.Join(l.pc.DirectoryPath, path) - p, err := plugin.Open(name + ".so") + absPath := l.absolutePluginPath(id) + p, err := plugin.Open(absPath + ".so") if err != nil { - return nil, errors.Wrapf(err, "plugin %s fails to load", name) + return nil, errors.Wrapf(err, "plugin %s fails to load", absPath) } symbol, err := p.Lookup(PluginSymbol) if err != nil { return nil, errors.Wrapf( err, "plugin %s doesn't have symbol %s", - name, PluginSymbol) + regId, PluginSymbol) } c, ok = symbol.(Configurable) if !ok { - return nil, fmt.Errorf("plugin %s not configurable", name) + return nil, fmt.Errorf("plugin %s not configurable", regId) } - registry[path] = c + registry[regId] = c return } diff --git a/pkg/plugins/loader_test.go b/pkg/plugins/loader_test.go index 7b6ae2f9c..69d12626a 100644 --- a/pkg/plugins/loader_test.go +++ b/pkg/plugins/loader_test.go @@ -43,7 +43,7 @@ port: "12345" ) func TestLoader(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildGoPlugin( diff --git a/pkg/target/chartinflatorplugin_test.go b/pkg/target/chartinflatorplugin_test.go index 585529da4..4112cfac7 100644 --- a/pkg/target/chartinflatorplugin_test.go +++ b/pkg/target/chartinflatorplugin_test.go @@ -28,7 +28,7 @@ import ( // TODO: Download and inflate the chart, and check that // in for the test. func TestChartInflatorPlugin(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildExecPlugin( diff --git a/pkg/target/kusttarget_configplugin.go b/pkg/target/kusttarget_configplugin.go index 29aef78ed..874b57db4 100644 --- a/pkg/target/kusttarget_configplugin.go +++ b/pkg/target/kusttarget_configplugin.go @@ -1,18 +1,5 @@ -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 package target @@ -23,7 +10,7 @@ import ( "sigs.k8s.io/kustomize/pkg/transformers" "sigs.k8s.io/kustomize/pkg/transformers/config" "sigs.k8s.io/kustomize/pkg/types" - "sigs.k8s.io/kustomize/plugin/builtingen" + "sigs.k8s.io/kustomize/plugin/builtin" "sigs.k8s.io/yaml" ) @@ -104,7 +91,7 @@ func (kt *KustTarget) configureBuiltinSecretGenerator() ( } for _, args := range kt.kustomization.SecretGenerator { c.SecretArgs = args - p := builtingen.NewSecretGeneratorPlugin() + p := builtin.NewSecretGeneratorPlugin() err = kt.configureBuiltinPlugin(p, c, "secret") if err != nil { return nil, err @@ -125,7 +112,7 @@ func (kt *KustTarget) configureBuiltinConfigMapGenerator() ( } for _, args := range kt.kustomization.ConfigMapGenerator { c.ConfigMapArgs = args - p := builtingen.NewConfigMapGeneratorPlugin() + p := builtin.NewConfigMapGeneratorPlugin() err = kt.configureBuiltinPlugin(p, c, "configmap") if err != nil { return nil, err @@ -146,7 +133,7 @@ func (kt *KustTarget) configureBuiltinNameTransformer( c.Prefix = kt.kustomization.NamePrefix c.Suffix = kt.kustomization.NameSuffix c.FieldSpecs = tConfig.NamePrefix - p := builtingen.NewNameTransformerPlugin() + p := builtin.NewNameTransformerPlugin() err = kt.configureBuiltinPlugin(p, c, "name") if err != nil { return nil, err @@ -165,7 +152,7 @@ func (kt *KustTarget) configureBuiltinImageTagTransformer( for _, args := range kt.kustomization.Images { c.ImageTag = args c.FieldSpecs = tConfig.Images - p := builtingen.NewImageTagTransformerPlugin() + p := builtin.NewImageTagTransformerPlugin() err = kt.configureBuiltinPlugin(p, c, "imageTag") if err != nil { return nil, err diff --git a/pkg/target/plugindir_test.go b/pkg/target/plugindir_test.go index 5cd427c65..bb9b100b3 100644 --- a/pkg/target/plugindir_test.go +++ b/pkg/target/plugindir_test.go @@ -24,7 +24,7 @@ import ( ) func TestPluginDir(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildExecPlugin( diff --git a/pkg/target/transformerplugin_test.go b/pkg/target/transformerplugin_test.go index 4f586cbb8..530365fc0 100644 --- a/pkg/target/transformerplugin_test.go +++ b/pkg/target/transformerplugin_test.go @@ -47,7 +47,7 @@ metadata: } func TestOrderedTransformers(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildGoPlugin( @@ -92,7 +92,7 @@ spec: } func TestSedTransformer(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildExecPlugin( @@ -161,7 +161,7 @@ metadata: } func TestTransformedTransformers(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildGoPlugin( diff --git a/plugin/builtin/ConfigMapGenerator.go b/plugin/builtin/ConfigMapGenerator.go index 52683df58..783a0515c 100644 --- a/plugin/builtin/ConfigMapGenerator.go +++ b/plugin/builtin/ConfigMapGenerator.go @@ -1,10 +1,5 @@ -// +build plugin - -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -//go:generate go run sigs.k8s.io/kustomize/cmd/pluginator -package main +// Code generated by pluginator on ConfigMapGenerator; DO NOT EDIT. +package builtin import ( "sigs.k8s.io/kustomize/pkg/ifc" @@ -13,16 +8,18 @@ import ( "sigs.k8s.io/yaml" ) -type plugin struct { +type ConfigMapGeneratorPlugin struct { ldr ifc.Loader rf *resmap.Factory types.GeneratorOptions types.ConfigMapArgs } -var KustomizePlugin plugin +func NewConfigMapGeneratorPlugin() *ConfigMapGeneratorPlugin { + return &ConfigMapGeneratorPlugin{} +} -func (p *plugin) Config( +func (p *ConfigMapGeneratorPlugin) Config( ldr ifc.Loader, rf *resmap.Factory, config []byte) (err error) { p.GeneratorOptions = types.GeneratorOptions{} p.ConfigMapArgs = types.ConfigMapArgs{} @@ -32,7 +29,7 @@ func (p *plugin) Config( return } -func (p *plugin) Generate() (resmap.ResMap, error) { +func (p *ConfigMapGeneratorPlugin) Generate() (resmap.ResMap, error) { argsList := make([]types.ConfigMapArgs, 1) argsList[0] = p.ConfigMapArgs return p.rf.NewResMapFromConfigMapArgs( diff --git a/plugin/builtin/ImageTagTransformer.go b/plugin/builtin/ImageTagTransformer.go index 07998c904..4b04cfbfb 100644 --- a/plugin/builtin/ImageTagTransformer.go +++ b/plugin/builtin/ImageTagTransformer.go @@ -1,10 +1,5 @@ -// +build plugin - -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -//go:generate go run sigs.k8s.io/kustomize/cmd/pluginator -package main +// Code generated by pluginator on ImageTagTransformer; DO NOT EDIT. +package builtin import ( "sigs.k8s.io/kustomize/pkg/ifc" @@ -17,21 +12,23 @@ import ( // Find matching image declarations and replace // the name, tag and/or digest. -type plugin struct { +type ImageTagTransformerPlugin struct { ImageTag image.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"` FieldSpecs []config.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"` } -var KustomizePlugin plugin +func NewImageTagTransformerPlugin() *ImageTagTransformerPlugin { + return &ImageTagTransformerPlugin{} +} -func (p *plugin) Config( +func (p *ImageTagTransformerPlugin) Config( ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) { p.ImageTag = image.Image{} p.FieldSpecs = nil return yaml.Unmarshal(c, p) } -func (p *plugin) Transform(m resmap.ResMap) error { +func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error { argsList := make([]image.Image, 1) argsList[0] = p.ImageTag t, err := transformers.NewImageTransformer(argsList, p.FieldSpecs) diff --git a/plugin/builtin/NameTransformer.go b/plugin/builtin/NameTransformer.go index bb301869b..69e805723 100644 --- a/plugin/builtin/NameTransformer.go +++ b/plugin/builtin/NameTransformer.go @@ -1,10 +1,5 @@ -// +build plugin - -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -//go:generate go run sigs.k8s.io/kustomize/cmd/pluginator -package main +// Code generated by pluginator on NameTransformer; DO NOT EDIT. +package builtin import ( "sigs.k8s.io/kustomize/pkg/ifc" @@ -15,15 +10,17 @@ import ( ) // Add the given prefix and suffix to the resource name. -type plugin struct { +type NameTransformerPlugin struct { Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"` Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"` FieldSpecs []config.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"` } -var KustomizePlugin plugin +func NewNameTransformerPlugin() *NameTransformerPlugin { + return &NameTransformerPlugin{} +} -func (p *plugin) Config( +func (p *NameTransformerPlugin) Config( ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) { p.Prefix = "" p.Suffix = "" @@ -31,7 +28,7 @@ func (p *plugin) Config( return yaml.Unmarshal(c, p) } -func (p *plugin) Transform(m resmap.ResMap) error { +func (p *NameTransformerPlugin) Transform(m resmap.ResMap) error { t, err := transformers.NewNamePrefixSuffixTransformer( p.Prefix, p.Suffix, diff --git a/plugin/builtin/SecretGenerator.go b/plugin/builtin/SecretGenerator.go index 1490d4c2a..ff6e0dd78 100644 --- a/plugin/builtin/SecretGenerator.go +++ b/plugin/builtin/SecretGenerator.go @@ -1,10 +1,5 @@ -// +build plugin - -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -//go:generate go run sigs.k8s.io/kustomize/cmd/pluginator -package main +// Code generated by pluginator on SecretGenerator; DO NOT EDIT. +package builtin import ( "sigs.k8s.io/kustomize/pkg/ifc" @@ -13,16 +8,18 @@ import ( "sigs.k8s.io/yaml" ) -type plugin struct { +type SecretGeneratorPlugin struct { ldr ifc.Loader rf *resmap.Factory types.GeneratorOptions types.SecretArgs } -var KustomizePlugin plugin +func NewSecretGeneratorPlugin() *SecretGeneratorPlugin { + return &SecretGeneratorPlugin{} +} -func (p *plugin) Config( +func (p *SecretGeneratorPlugin) Config( ldr ifc.Loader, rf *resmap.Factory, config []byte) (err error) { p.GeneratorOptions = types.GeneratorOptions{} p.SecretArgs = types.SecretArgs{} @@ -32,7 +29,7 @@ func (p *plugin) Config( return } -func (p *plugin) Generate() (resmap.ResMap, error) { +func (p *SecretGeneratorPlugin) Generate() (resmap.ResMap, error) { argsList := make([]types.SecretArgs, 1) argsList[0] = p.SecretArgs return p.rf.NewResMapFromSecretArgs( diff --git a/plugin/builtingen/ConfigMapGenerator.go b/plugin/builtin/configmapgenerator/ConfigMapGenerator.go similarity index 63% rename from plugin/builtingen/ConfigMapGenerator.go rename to plugin/builtin/configmapgenerator/ConfigMapGenerator.go index d06833f8c..fd1e3f92e 100644 --- a/plugin/builtingen/ConfigMapGenerator.go +++ b/plugin/builtin/configmapgenerator/ConfigMapGenerator.go @@ -1,5 +1,8 @@ -// Code generated by pluginator on ConfigMapGenerator; DO NOT EDIT. -package builtingen +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +//go:generate go run sigs.k8s.io/kustomize/plugin/pluginator +package main import ( "sigs.k8s.io/kustomize/pkg/ifc" @@ -8,18 +11,16 @@ import ( "sigs.k8s.io/yaml" ) -type ConfigMapGeneratorPlugin struct { +type plugin struct { ldr ifc.Loader rf *resmap.Factory types.GeneratorOptions types.ConfigMapArgs } -func NewConfigMapGeneratorPlugin() *ConfigMapGeneratorPlugin { - return &ConfigMapGeneratorPlugin{} -} +var KustomizePlugin plugin -func (p *ConfigMapGeneratorPlugin) Config( +func (p *plugin) Config( ldr ifc.Loader, rf *resmap.Factory, config []byte) (err error) { p.GeneratorOptions = types.GeneratorOptions{} p.ConfigMapArgs = types.ConfigMapArgs{} @@ -29,7 +30,7 @@ func (p *ConfigMapGeneratorPlugin) Config( return } -func (p *ConfigMapGeneratorPlugin) Generate() (resmap.ResMap, error) { +func (p *plugin) Generate() (resmap.ResMap, error) { argsList := make([]types.ConfigMapArgs, 1) argsList[0] = p.ConfigMapArgs return p.rf.NewResMapFromConfigMapArgs( diff --git a/plugin/builtin/ConfigMapGenerator_test.go b/plugin/builtin/configmapgenerator/ConfigMapGenerator_test.go similarity index 95% rename from plugin/builtin/ConfigMapGenerator_test.go rename to plugin/builtin/configmapgenerator/ConfigMapGenerator_test.go index 44bb4bc47..051499ddb 100644 --- a/plugin/builtin/ConfigMapGenerator_test.go +++ b/plugin/builtin/configmapgenerator/ConfigMapGenerator_test.go @@ -11,7 +11,7 @@ import ( ) func TestConfigMapGenerator(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildGoPlugin( diff --git a/plugin/builtingen/ImageTagTransformer.go b/plugin/builtin/imagetagtransformer/ImageTagTransformer.go similarity index 70% rename from plugin/builtingen/ImageTagTransformer.go rename to plugin/builtin/imagetagtransformer/ImageTagTransformer.go index 11946547f..f15721f5e 100644 --- a/plugin/builtingen/ImageTagTransformer.go +++ b/plugin/builtin/imagetagtransformer/ImageTagTransformer.go @@ -1,5 +1,8 @@ -// Code generated by pluginator on ImageTagTransformer; DO NOT EDIT. -package builtingen +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +//go:generate go run sigs.k8s.io/kustomize/plugin/pluginator +package main import ( "sigs.k8s.io/kustomize/pkg/ifc" @@ -12,23 +15,21 @@ import ( // Find matching image declarations and replace // the name, tag and/or digest. -type ImageTagTransformerPlugin struct { +type plugin struct { ImageTag image.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"` FieldSpecs []config.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"` } -func NewImageTagTransformerPlugin() *ImageTagTransformerPlugin { - return &ImageTagTransformerPlugin{} -} +var KustomizePlugin plugin -func (p *ImageTagTransformerPlugin) Config( +func (p *plugin) Config( ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) { p.ImageTag = image.Image{} p.FieldSpecs = nil return yaml.Unmarshal(c, p) } -func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error { +func (p *plugin) Transform(m resmap.ResMap) error { argsList := make([]image.Image, 1) argsList[0] = p.ImageTag t, err := transformers.NewImageTransformer(argsList, p.FieldSpecs) diff --git a/plugin/builtin/ImageTagTransformer_test.go b/plugin/builtin/imagetagtransformer/ImageTagTransformer_test.go similarity index 97% rename from plugin/builtin/ImageTagTransformer_test.go rename to plugin/builtin/imagetagtransformer/ImageTagTransformer_test.go index fabf15599..afc7dbec3 100644 --- a/plugin/builtin/ImageTagTransformer_test.go +++ b/plugin/builtin/imagetagtransformer/ImageTagTransformer_test.go @@ -11,7 +11,7 @@ import ( ) func TestImageTagTransformer(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildGoPlugin( diff --git a/plugin/builtingen/NameTransformer.go b/plugin/builtin/nametransformer/NameTransformer.go similarity index 71% rename from plugin/builtingen/NameTransformer.go rename to plugin/builtin/nametransformer/NameTransformer.go index 1d218fe52..e5f3f1fc7 100644 --- a/plugin/builtingen/NameTransformer.go +++ b/plugin/builtin/nametransformer/NameTransformer.go @@ -1,5 +1,8 @@ -// Code generated by pluginator on NameTransformer; DO NOT EDIT. -package builtingen +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +//go:generate go run sigs.k8s.io/kustomize/plugin/pluginator +package main import ( "sigs.k8s.io/kustomize/pkg/ifc" @@ -10,17 +13,15 @@ import ( ) // Add the given prefix and suffix to the resource name. -type NameTransformerPlugin struct { +type plugin struct { Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"` Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"` FieldSpecs []config.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"` } -func NewNameTransformerPlugin() *NameTransformerPlugin { - return &NameTransformerPlugin{} -} +var KustomizePlugin plugin -func (p *NameTransformerPlugin) Config( +func (p *plugin) Config( ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) { p.Prefix = "" p.Suffix = "" @@ -28,7 +29,7 @@ func (p *NameTransformerPlugin) Config( return yaml.Unmarshal(c, p) } -func (p *NameTransformerPlugin) Transform(m resmap.ResMap) error { +func (p *plugin) Transform(m resmap.ResMap) error { t, err := transformers.NewNamePrefixSuffixTransformer( p.Prefix, p.Suffix, diff --git a/plugin/builtin/NameTransformer_test.go b/plugin/builtin/nametransformer/NameTransformer_test.go similarity index 95% rename from plugin/builtin/NameTransformer_test.go rename to plugin/builtin/nametransformer/NameTransformer_test.go index d89a49a23..1b6f0c5e0 100644 --- a/plugin/builtin/NameTransformer_test.go +++ b/plugin/builtin/nametransformer/NameTransformer_test.go @@ -11,7 +11,7 @@ import ( ) func TestNameTransformer(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildGoPlugin( diff --git a/plugin/builtingen/SecretGenerator.go b/plugin/builtin/secretgenerator/SecretGenerator.go similarity index 64% rename from plugin/builtingen/SecretGenerator.go rename to plugin/builtin/secretgenerator/SecretGenerator.go index cdf33f5f5..c7a5baabd 100644 --- a/plugin/builtingen/SecretGenerator.go +++ b/plugin/builtin/secretgenerator/SecretGenerator.go @@ -1,5 +1,8 @@ -// Code generated by pluginator on SecretGenerator; DO NOT EDIT. -package builtingen +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +//go:generate go run sigs.k8s.io/kustomize/plugin/pluginator +package main import ( "sigs.k8s.io/kustomize/pkg/ifc" @@ -8,18 +11,16 @@ import ( "sigs.k8s.io/yaml" ) -type SecretGeneratorPlugin struct { +type plugin struct { ldr ifc.Loader rf *resmap.Factory types.GeneratorOptions types.SecretArgs } -func NewSecretGeneratorPlugin() *SecretGeneratorPlugin { - return &SecretGeneratorPlugin{} -} +var KustomizePlugin plugin -func (p *SecretGeneratorPlugin) Config( +func (p *plugin) Config( ldr ifc.Loader, rf *resmap.Factory, config []byte) (err error) { p.GeneratorOptions = types.GeneratorOptions{} p.SecretArgs = types.SecretArgs{} @@ -29,7 +30,7 @@ func (p *SecretGeneratorPlugin) Config( return } -func (p *SecretGeneratorPlugin) Generate() (resmap.ResMap, error) { +func (p *plugin) Generate() (resmap.ResMap, error) { argsList := make([]types.SecretArgs, 1) argsList[0] = p.SecretArgs return p.rf.NewResMapFromSecretArgs( diff --git a/plugin/builtin/SecretGenerator_test.go b/plugin/builtin/secretgenerator/SecretGenerator_test.go similarity index 96% rename from plugin/builtin/SecretGenerator_test.go rename to plugin/builtin/secretgenerator/SecretGenerator_test.go index bb2b7ae33..fa603213f 100644 --- a/plugin/builtin/SecretGenerator_test.go +++ b/plugin/builtin/secretgenerator/SecretGenerator_test.go @@ -11,7 +11,7 @@ import ( ) func TestSecretGenerator(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildGoPlugin( diff --git a/plugin/doc.go b/plugin/doc.go new file mode 100644 index 000000000..eafb4081c --- /dev/null +++ b/plugin/doc.go @@ -0,0 +1,237 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +/* +Package plugin contains builtin and example +plugins, tests and test libraries, and a code +generator for converting a plugin to statically +loadable code (see pluginator). + + +HOW PLUGINS RUN + +Assume a file 'secGen.yaml' containing + + apiVersion: someteam.example.com/v1 + kind: SecretGenerator + metadata: + name: makesecrets + name: mySecret + behavior: merge + envs: + - db.env + - fruit.env + +If this file were referenced by a kustomization +file in its 'generators' field, kustomize would + + * Read 'secGen.yaml'. + + * Use the value of $XGD_CONFIG_HOME and + 'apiversion' and to find an executable + named 'SecretGenerator' to use as + an exec plugin, or failing that, + + * use the same info to load a Go plugin + object file called 'SecretGenerator.so'. + + * Send either the file name 'secGen.yaml' as + the first arg to the exec plugin, or send its + contents to the go plugin's Config method. + + * Use the plugin to generate and/or transform. + + +GO PLUGINS + +A .go file can be a Go plugin if it declares +'main' as it's package, and exports a symbol to +which useful functions are attached. + +It can further be used as a _kustomize_ plugin if +the symbol is named 'KustomizePlugin' and the +attached functions implement the Configurable, +Generator and Transformer interfaces. + +A plugin won't load into some program foo/main.go +if there is any package version mismatch in the +dependencies of the plugin and the dependencies of +foo/main.go. Control this with matching +declarations in go.mod files. The versions of the +builtin packages "fmt", "io", "os" (not normally +listed in go.mod) etc have the same version as the +compiler. + + +ONE PLUGIN PER DIRECTORY + +For kustomize (and perhaps anyone), it's simplest +to put each plugin into its own directory. + +Go plugins must be in package `main`, and so +having more than one plugin in a directory means +their loading symbols have to differ, which makes +it hard to standardize around how they get loaded, +or it means one must use build tags to suppress +full directory compilation - which creates +difficulties using IDEs, the `go mod` tool, `go +test ./...`, etc. + +A one plugin per directory policy makes it easy to +define the plugin as a module, with its own +`go.mod` file - which is vital for resolving +package version dependency mismatches at load +time. It also makes it easy to create a plugin +tarball (source, test, go.mod, plugin data files, +etc.) for distribution. + + +BUILTIN PLUGIN CONFIGURATION + +For performance reasons, all builting plugins are +Go plugins (not exec plugins). + +Using "SecretGenerator" as an example in what +follows. + +The plugin config file looks like + + --------------------------------------------- + apiVersion: builtin + kind: SecretGenerator + metadata: + name: whatever + otherField1: whatever + otherField2: whatever + ... + --------------------------------------------- + +The apiVersion must be 'builtin'. The kind is the +CamelCase name of the plugin. + +For non-builtins the apiVersion can be any legal +apiVersion value, e.g. 'someteam.example.com/v1beta1' + +The builtin source must be at: + + repo=$GOPATH/src/sigs.k8s.io/kustomize + ${repo}/plugin/${apiVersion}/LOWERCASE(${kind})/${kind}.go + +(dropping the ".go" for exec plugins). + +k8s wants 'kind' values to follow CamelCase, while +Go style doesn't like but does allow such names. + +The lowercased value of kind is used as the name of the +directory holding the plugin, its test, and any +optional associated files (possibly a go.mod file). + + +PLUGIN SOURCE + + * Pattern + + secretgenerator.go + --------------------------------------------- + //go:generate go run sigs.k8s.io/kustomize/cmd/pluginator + package main + import ... + type plugin struct{...} + var KustomizePlugin plugin + func (p *plugin) Config( + ldr ifc.Loader, + rf *resmap.Factory, + c []byte) error {...} + func (p *plugin) Generate( + ) (resmap.ResMap, error) {...} + func (p *plugin) Transform( + m resmap.ResMap) error {...} + --------------------------------------------- + + The plugin name doesn't appear in the file itself. + + * Compilation + + repo=$GOPATH/src/sigs.k8s.io/kustomize + dir=$repo/plugin/builtin + go build -buildmode plugin \ + -o $dir/secretgenerator.so \ + $dir/secretgenerator.go + + + +BUILTIN PLUGIN GENERATION + +The pluginator program is a code generator that +converts kustomize generator (G) and/or +transformer (T) Go plugins to statically linkable +code. + +It arises from following requirements: + + * extension + + kustomize does two things - generate or + transform k8s resources. Plugins let + users write their own G&T's without + having to fork kustomize and learn its + internals. + + * dogfooding + + A G&T extension framework one can trust + should be used by its authors to deliver + builtin G&T's. + + * distribution + + kustomize should be distributable via + `go get` and should run where Go + programs are expected to run. + + The extension requirement led to the creation + of a framework that accommodates writing a + G or T as either + + * an 'exec' plugin (any executable file + runnable as a kustomize subprocess), or + + * as a Go plugin - see + https://golang.org/pkg/plugin. + + The dogfooding (and an implicit performance + requirement) requires a 'builtin' G or T to + be written as a Go plugin. + + The distribution ('go get') requirement demands + conversion of Go plugins to statically linked + code, hence this program. + + +TO GENERATE CODE + + repo=$GOPATH/src/sigs.k8s.io/kustomize + cd $repo/plugin/builtin + go generate ./... + +This creates + + $repo/plugin/builtin/SecretGenerator.go + +etc. + +Generated plugins are used in kustomize via + + --------------------------------------------- + package whatever + import "sigs.k8s.io/kustomize/plugin/builtin + ... + g := builtin.NewSecretGenerator() + g.Config(l, rf, k) + resources, err := g.Generate() + err = g.Transform(resources) + // Eventually emit resources. + --------------------------------------------- + +*/ +package plugin diff --git a/plugin/plugintestenv.go b/plugin/envfortest.go similarity index 72% rename from plugin/plugintestenv.go rename to plugin/envfortest.go index dbd0d346f..25639a1a3 100644 --- a/plugin/plugintestenv.go +++ b/plugin/envfortest.go @@ -8,15 +8,16 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" "sigs.k8s.io/kustomize/pkg/pgmconfig" "sigs.k8s.io/kustomize/pkg/plugins" ) -// PluginTestEnv manages the plugin test environment. +// EnvForTest manages the plugin test environment. // It sets/resets XDG_CONFIG_HOME, makes/removes a temp objRoot. -type PluginTestEnv struct { +type EnvForTest struct { t *testing.T compiler *plugins.Compiler workDir string @@ -24,34 +25,33 @@ type PluginTestEnv struct { wasSet bool } -func NewPluginTestEnv(t *testing.T) *PluginTestEnv { - return &PluginTestEnv{t: t} +func NewEnvForTest(t *testing.T) *EnvForTest { + return &EnvForTest{t: t} } -func (x *PluginTestEnv) Set() *PluginTestEnv { +func (x *EnvForTest) Set() *EnvForTest { x.createWorkDir() x.compiler = x.makeCompiler() x.setEnv() return x } -func (x *PluginTestEnv) Reset() { +func (x *EnvForTest) Reset() { x.resetEnv() x.removeWorkDir() } -func (x *PluginTestEnv) BuildGoPlugin(g, v, k string) { +func (x *EnvForTest) BuildGoPlugin(g, v, k string) { err := x.compiler.Compile(g, v, k) if err != nil { x.t.Errorf("compile failed: %v", err) } } -func (x *PluginTestEnv) BuildExecPlugin(name ...string) { - obj := filepath.Join( - append([]string{x.compiler.ObjRoot()}, name...)...) - src := filepath.Join( - append([]string{x.compiler.SrcRoot()}, name...)...) +func (x *EnvForTest) BuildExecPlugin(g, v, k string) { + lowK := strings.ToLower(k) + obj := filepath.Join(x.compiler.ObjRoot(), g, v, lowK, k) + src := filepath.Join(x.compiler.SrcRoot(), g, v, lowK, k) if err := os.MkdirAll(filepath.Dir(obj), 0755); err != nil { x.t.Errorf("error making directory: %s", filepath.Dir(obj)) } @@ -62,7 +62,7 @@ func (x *PluginTestEnv) BuildExecPlugin(name ...string) { } } -func (x *PluginTestEnv) makeCompiler() *plugins.Compiler { +func (x *EnvForTest) makeCompiler() *plugins.Compiler { // The plugin loader wants to find object code under // $XDG_CONFIG_HOME/kustomize/plugins // and the compiler writes object code to @@ -81,7 +81,7 @@ func (x *PluginTestEnv) makeCompiler() *plugins.Compiler { return plugins.NewCompiler(srcRoot, objRoot) } -func (x *PluginTestEnv) createWorkDir() { +func (x *EnvForTest) createWorkDir() { var err error x.workDir, err = ioutil.TempDir("", "kustomize-plugin-tests") if err != nil { @@ -89,7 +89,7 @@ func (x *PluginTestEnv) createWorkDir() { } } -func (x *PluginTestEnv) removeWorkDir() { +func (x *EnvForTest) removeWorkDir() { err := os.RemoveAll(x.workDir) if err != nil { x.t.Errorf( @@ -97,12 +97,12 @@ func (x *PluginTestEnv) removeWorkDir() { } } -func (x *PluginTestEnv) setEnv() { +func (x *EnvForTest) setEnv() { x.oldXdg, x.wasSet = os.LookupEnv(pgmconfig.XDG_CONFIG_HOME) os.Setenv(pgmconfig.XDG_CONFIG_HOME, x.workDir) } -func (x *PluginTestEnv) resetEnv() { +func (x *EnvForTest) resetEnv() { if x.wasSet { os.Setenv(pgmconfig.XDG_CONFIG_HOME, x.oldXdg) } else { diff --git a/plugin/generateBuiltins.sh b/plugin/generateBuiltins.sh new file mode 100755 index 000000000..5774c3330 --- /dev/null +++ b/plugin/generateBuiltins.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# +# This script creates the generator and +# transformer factory functions in +# sigs.k8s.io/kustomize/plugin/builtin +# by generating code based on the plugins +# found below that directory. + +set -e + +myGoPath=$1 +if [ -z ${1+x} ]; then + myGoPath=$GOPATH +fi + +if [ -z "$myGoPath" ]; then + echo "Must specify a GOPATH" + exit 1 +fi + +dir=$myGoPath/src/sigs.k8s.io/kustomize + +if [ ! -d "$dir" ]; then + echo "$dir is not a directory." + exit 1 +fi + +echo Generating linkable plugins... + +pushd $dir >& /dev/null + +GOPATH=$myGoPath go generate \ + sigs.k8s.io/kustomize/plugin/builtin/... + +popd >& /dev/null + +echo All done. diff --git a/plugin/pluginator/main.go b/plugin/pluginator/main.go new file mode 100644 index 000000000..91c13e532 --- /dev/null +++ b/plugin/pluginator/main.go @@ -0,0 +1,132 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +// See plugin/doc.go for an explanation. +package main + +import ( + "bufio" + "fmt" + "log" + "os" + "path/filepath" + "strings" + + "sigs.k8s.io/kustomize/pkg/pgmconfig" + "sigs.k8s.io/kustomize/pkg/plugins" +) + +func main() { + root := inputFileRoot() + file, err := os.Open(root + ".go") + if err != nil { + log.Fatal(err) + } + defer file.Close() + scanner := bufio.NewScanner(file) + readToPackageMain(scanner, file.Name()) + + w := NewWriter(root) + defer w.close() + + // This particular phrasing is required. + w.write( + fmt.Sprintf( + "// Code generated by pluginator on %s; DO NOT EDIT.", + root)) + w.write("package builtin") + + for scanner.Scan() { + l := scanner.Text() + if strings.HasPrefix(l, "//go:generate") { + continue + } + if l == "var "+plugins.PluginSymbol+" plugin" { + w.write("func New" + root + "Plugin() *" + root + "Plugin {") + w.write(" return &" + root + "Plugin{}") + w.write("}") + continue + } + w.write(l) + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } +} + +func inputFileRoot() string { + n := os.Getenv("GOFILE") + if !strings.HasSuffix(n, ".go") { + log.Fatalf("expecting .go suffix on %s", n) + } + return n[:len(n)-len(".go")] +} + +func readToPackageMain(s *bufio.Scanner, f string) { + gotMain := false + for !gotMain && s.Scan() { + gotMain = strings.HasPrefix(s.Text(), "package main") + } + if !gotMain { + log.Fatalf("%s missing package main", f) + } +} + +type writer struct { + root string + f *os.File +} + +func NewWriter(r string) *writer { + n := makeOutputFileName(r) + f, err := os.Create(n) + if err != nil { + log.Fatalf("unable to create `%s`; %v", n, err) + } + return &writer{root: r, f: f} +} + +func makeOutputFileName(root string) string { + return filepath.Join( + os.Getenv("GOPATH"), + "src", + pgmconfig.DomainName, + pgmconfig.ProgramName, + pgmconfig.PluginRoot, + "builtin", + root+".go") +} + +func (w *writer) close() { + fmt.Println("Generated " + w.root) + w.f.Close() +} + +func (w *writer) write(line string) { + _, err := w.f.WriteString(w.filter(line) + "\n") + if err != nil { + log.Printf("Trouble writing: %s", line) + log.Fatal(err) + } +} + +func (w *writer) filter(in string) string { + if ok, newer := w.replace(in, "type plugin struct"); ok { + return newer + } + if ok, newer := w.replace(in, "*plugin)"); ok { + return newer + } + return in +} + +// replace 'plugin' with 'FooPlugin' in context +// sensitive manner. +func (w *writer) replace(in, target string) (bool, string) { + if !strings.Contains(in, target) { + return false, "" + } + newer := strings.Replace( + target, "plugin", w.root+"Plugin", 1) + return true, strings.Replace(in, target, newer, 1) +} diff --git a/plugin/someteam.example.com/v1/ChartInflator b/plugin/someteam.example.com/v1/chartinflator/ChartInflator similarity index 96% rename from plugin/someteam.example.com/v1/ChartInflator rename to plugin/someteam.example.com/v1/chartinflator/ChartInflator index d499cc9af..127b371b2 100755 --- a/plugin/someteam.example.com/v1/ChartInflator +++ b/plugin/someteam.example.com/v1/chartinflator/ChartInflator @@ -1,8 +1,9 @@ #!/bin/bash -set -e +# Copyright 2019 The Kubernetes Authors. +# SPDX-License-Identifier: Apache-2.0 # Helm chart inflator - +# # Reads a file like this # # apiVersion: kustomize.config.k8s.io/v1 @@ -23,6 +24,7 @@ set -e # Example execution: # ./plugin/someteam.example.com/v1/ChartInflator configFile.yaml +set -e # Yaml parsing is a ridiculous thing to do in bash, # but let's try: diff --git a/plugin/someteam.example.com/v1/ChartInflator_test.go b/plugin/someteam.example.com/v1/chartinflator/ChartInflator_test.go similarity index 97% rename from plugin/someteam.example.com/v1/ChartInflator_test.go rename to plugin/someteam.example.com/v1/chartinflator/ChartInflator_test.go index ffbc5cab1..0fca6b53d 100644 --- a/plugin/someteam.example.com/v1/ChartInflator_test.go +++ b/plugin/someteam.example.com/v1/chartinflator/ChartInflator_test.go @@ -19,7 +19,7 @@ import ( // TODO: Download and inflate the chart, and check that // in for the test. func TestChartInflator(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildExecPlugin( diff --git a/plugin/someteam.example.com/v1/ConfigMapGenerator b/plugin/someteam.example.com/v1/configmapgenerator/ConfigMapGenerator similarity index 100% rename from plugin/someteam.example.com/v1/ConfigMapGenerator rename to plugin/someteam.example.com/v1/configmapgenerator/ConfigMapGenerator diff --git a/plugin/someteam.example.com/v1/DatePrefixer.go b/plugin/someteam.example.com/v1/dateprefixer/DatePrefixer.go similarity index 55% rename from plugin/someteam.example.com/v1/DatePrefixer.go rename to plugin/someteam.example.com/v1/dateprefixer/DatePrefixer.go index 8f9ba6812..b974a04a3 100644 --- a/plugin/someteam.example.com/v1/DatePrefixer.go +++ b/plugin/someteam.example.com/v1/dateprefixer/DatePrefixer.go @@ -1,20 +1,5 @@ -// +build plugin - -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 package main diff --git a/plugin/someteam.example.com/v1/kvMaker.go b/plugin/someteam.example.com/v1/kvMaker.go deleted file mode 100644 index 401a6e622..000000000 --- a/plugin/someteam.example.com/v1/kvMaker.go +++ /dev/null @@ -1,43 +0,0 @@ -// +build plugin - -/* -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -var database = map[string]string{ - "TREE": "oak", - "ROCKET": "Saturn V", - "FRUIT": "apple", - "VEGETABLE": "carrot", - "SIMPSON": "homer", -} - -type plugin struct{} - -var KVSource plugin - -func (p plugin) Get( - root string, args []string) (map[string]string, error) { - r := make(map[string]string) - for _, k := range args { - v, ok := database[k] - if ok { - r[k] = v - } - } - return r, nil -} diff --git a/plugin/someteam.example.com/v1/kvmaker/kvMaker.go b/plugin/someteam.example.com/v1/kvmaker/kvMaker.go new file mode 100644 index 000000000..3af6d0631 --- /dev/null +++ b/plugin/someteam.example.com/v1/kvmaker/kvMaker.go @@ -0,0 +1,25 @@ +package kvmaker + +var database = map[string]string{ + "TREE": "oak", + "ROCKET": "Saturn V", + "FRUIT": "apple", + "VEGETABLE": "carrot", + "SIMPSON": "homer", +} + +type plugin struct{} + +var KVSource plugin + +func (p plugin) Get( + root string, args []string) (map[string]string, error) { + r := make(map[string]string) + for _, k := range args { + v, ok := database[k] + if ok { + r[k] = v + } + } + return r, nil +} diff --git a/plugin/someteam.example.com/v1/PrintWorkDir b/plugin/someteam.example.com/v1/printworkdir/PrintWorkDir similarity index 100% rename from plugin/someteam.example.com/v1/PrintWorkDir rename to plugin/someteam.example.com/v1/printworkdir/PrintWorkDir diff --git a/plugin/someteam.example.com/v1/SedTransformer b/plugin/someteam.example.com/v1/sedtransformer/SedTransformer similarity index 100% rename from plugin/someteam.example.com/v1/SedTransformer rename to plugin/someteam.example.com/v1/sedtransformer/SedTransformer diff --git a/plugin/someteam.example.com/v1/SedTransformer_test.go b/plugin/someteam.example.com/v1/sedtransformer/SedTransformer_test.go similarity index 95% rename from plugin/someteam.example.com/v1/SedTransformer_test.go rename to plugin/someteam.example.com/v1/sedtransformer/SedTransformer_test.go index 04b2f6529..e60fec4f5 100644 --- a/plugin/someteam.example.com/v1/SedTransformer_test.go +++ b/plugin/someteam.example.com/v1/sedtransformer/SedTransformer_test.go @@ -11,7 +11,7 @@ import ( ) func TestSedTransformer(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildExecPlugin("someteam.example.com", "v1", "SedTransformer") diff --git a/plugin/someteam.example.com/v1/SomeServiceGenerator.go b/plugin/someteam.example.com/v1/someservicegenerator/SomeServiceGenerator.go similarity index 98% rename from plugin/someteam.example.com/v1/SomeServiceGenerator.go rename to plugin/someteam.example.com/v1/someservicegenerator/SomeServiceGenerator.go index d40792aa0..e9adb0487 100644 --- a/plugin/someteam.example.com/v1/SomeServiceGenerator.go +++ b/plugin/someteam.example.com/v1/someservicegenerator/SomeServiceGenerator.go @@ -1,5 +1,3 @@ -// +build plugin - // Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 diff --git a/plugin/someteam.example.com/v1/SomeServiceGenerator_test.go b/plugin/someteam.example.com/v1/someservicegenerator/SomeServiceGenerator_test.go similarity index 94% rename from plugin/someteam.example.com/v1/SomeServiceGenerator_test.go rename to plugin/someteam.example.com/v1/someservicegenerator/SomeServiceGenerator_test.go index c6c10dda6..d68e2fe2f 100644 --- a/plugin/someteam.example.com/v1/SomeServiceGenerator_test.go +++ b/plugin/someteam.example.com/v1/someservicegenerator/SomeServiceGenerator_test.go @@ -11,7 +11,7 @@ import ( ) func TestSomeServiceGeneratorPlugin(t *testing.T) { - tc := plugin.NewPluginTestEnv(t).Set() + tc := plugin.NewEnvForTest(t).Set() defer tc.Reset() tc.BuildGoPlugin( diff --git a/plugin/someteam.example.com/v1/StringPrefixer.go b/plugin/someteam.example.com/v1/stringprefixer/StringPrefixer.go similarity index 97% rename from plugin/someteam.example.com/v1/StringPrefixer.go rename to plugin/someteam.example.com/v1/stringprefixer/StringPrefixer.go index 5cd3adc5b..f7a4ad268 100644 --- a/plugin/someteam.example.com/v1/StringPrefixer.go +++ b/plugin/someteam.example.com/v1/stringprefixer/StringPrefixer.go @@ -1,5 +1,3 @@ -// +build plugin - // Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0