diff --git a/pkg/commands/build/build.go b/pkg/commands/build/build.go index 1176f25b1..cf4945d32 100644 --- a/pkg/commands/build/build.go +++ b/pkg/commands/build/build.go @@ -62,7 +62,8 @@ url examples: func NewCmdBuild( out io.Writer, fs fs.FileSystem, rf *resmap.Factory, - ptf transformer.Factory) *cobra.Command { + ptf transformer.Factory, + b bool) *cobra.Command { var o Options cmd := &cobra.Command{ @@ -75,7 +76,7 @@ func NewCmdBuild( if err != nil { return err } - return o.RunBuild(out, fs, rf, ptf) + return o.RunBuild(out, fs, rf, ptf, b) }, } cmd.Flags().StringVarP( @@ -102,13 +103,14 @@ func (o *Options) Validate(args []string) error { // RunBuild runs build command. func (o *Options) RunBuild( out io.Writer, fSys fs.FileSystem, - rf *resmap.Factory, ptf transformer.Factory) error { + rf *resmap.Factory, ptf transformer.Factory, + b bool) error { ldr, err := loader.NewLoader(o.kustomizationPath, fSys) if err != nil { return err } defer ldr.Cleanup() - kt, err := target.NewKustTarget(ldr, rf, ptf) + kt, err := target.NewKustTarget(ldr, rf, ptf, b) if err != nil { return err } diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go index e2109249e..d327bd230 100644 --- a/pkg/commands/commands.go +++ b/pkg/commands/commands.go @@ -68,7 +68,7 @@ See https://sigs.k8s.io/kustomize build.NewCmdBuild( stdOut, fSys, resmap.NewFactory(resource.NewFactory(uf)), - transformer.NewFactoryImpl()), + transformer.NewFactoryImpl(), genMetaArgs.PluginConfig.GoEnabled), edit.NewCmdEdit(fSys, validator.NewKustValidator(), uf), misc.NewCmdConfig(fSys), misc.NewCmdVersion(stdOut), diff --git a/pkg/pgmconfig/constants.go b/pkg/pgmconfig/constants.go index 008b69da9..8418a75ce 100644 --- a/pkg/pgmconfig/constants.go +++ b/pkg/pgmconfig/constants.go @@ -28,5 +28,6 @@ var KustomizationFileNames = []string{ } const ( - PgmName = "kustomize" + PgmName = "kustomize" + PluginsDir = "plugins" ) diff --git a/pkg/plugins/transformers.go b/pkg/plugins/transformers.go new file mode 100644 index 000000000..aae6895be --- /dev/null +++ b/pkg/plugins/transformers.go @@ -0,0 +1,95 @@ +/* +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 plugins + +import ( + "fmt" + "path/filepath" + "plugin" + + "github.com/pkg/errors" + "sigs.k8s.io/kustomize/pkg/ifc" + "sigs.k8s.io/kustomize/pkg/pgmconfig" + "sigs.k8s.io/kustomize/pkg/resid" + "sigs.k8s.io/kustomize/pkg/resmap" + "sigs.k8s.io/kustomize/pkg/resource" + "sigs.k8s.io/kustomize/pkg/transformers" +) + +const transformerSymbol = "Transformer" + +type Configurable interface { + Config(k ifc.Kunstructured) error +} + +type transformerLoader struct { + pluginDir string + enabled bool +} + +func NewTransformerLoader(b bool) transformerLoader { + return transformerLoader{ + pluginDir: filepath.Join(pgmconfig.ConfigRoot(), pgmconfig.PluginsDir), + enabled: b, + } +} + +func (l transformerLoader) Load(rm resmap.ResMap) ([]transformers.Transformer, error) { + if len(rm) == 0 { + return nil, nil + } + if !l.enabled { + return nil, fmt.Errorf("plugin is not enabled") + } + var result []transformers.Transformer + for id, res := range rm { + t, err := l.load(id, res) + if err != nil { + return nil, err + } + result = append(result, t) + } + return result, nil +} + +func (l transformerLoader) load(id resid.ResId, res *resource.Resource) (transformers.Transformer, error) { + fileName := filepath.Join(l.pluginDir, id.Gvk().Kind+".so") + goPlugin, err := plugin.Open(fileName) + if err != nil { + return nil, fmt.Errorf("plugin %s file not opened", fileName) + } + + symbol, err := goPlugin.Lookup(transformerSymbol) + if err != nil { + return nil, fmt.Errorf("plugin %s fails lookup", fileName) + } + + c, ok := symbol.(Configurable) + if !ok { + return nil, fmt.Errorf("plugin %s not configurable", fileName) + } + err = c.Config(res) + if err != nil { + return nil, errors.Wrapf(err, "plugin %s fails configuration", fileName) + } + + t, ok := c.(transformers.Transformer) + if !ok { + return nil, fmt.Errorf("plugin %s not a transformer", fileName) + } + return t, nil +} diff --git a/pkg/target/kusttarget.go b/pkg/target/kusttarget.go index e476cecdd..3dfb02adb 100644 --- a/pkg/target/kusttarget.go +++ b/pkg/target/kusttarget.go @@ -21,9 +21,6 @@ import ( "bytes" "encoding/json" "fmt" - "os" - "path/filepath" - "plugin" "strings" "github.com/ghodss/yaml" @@ -34,6 +31,7 @@ import ( interror "sigs.k8s.io/kustomize/pkg/internal/error" patchtransformer "sigs.k8s.io/kustomize/pkg/patch/transformer" "sigs.k8s.io/kustomize/pkg/pgmconfig" + "sigs.k8s.io/kustomize/pkg/plugins" "sigs.k8s.io/kustomize/pkg/resmap" "sigs.k8s.io/kustomize/pkg/resource" "sigs.k8s.io/kustomize/pkg/transformers" @@ -43,17 +41,19 @@ import ( // KustTarget encapsulates the entirety of a kustomization build. type KustTarget struct { - kustomization *types.Kustomization - ldr ifc.Loader - rFactory *resmap.Factory - tFactory transformer.Factory + kustomization *types.Kustomization + ldr ifc.Loader + rFactory *resmap.Factory + tFactory transformer.Factory + goPluginEnabled bool } // NewKustTarget returns a new instance of KustTarget primed with a Loader. func NewKustTarget( ldr ifc.Loader, rFactory *resmap.Factory, - tFactory transformer.Factory) (*KustTarget, error) { + tFactory transformer.Factory, + b bool) (*KustTarget, error) { content, err := loadKustFile(ldr) if err != nil { return nil, err @@ -71,10 +71,11 @@ func NewKustTarget( strings.Join(errs, "\n"), ldr.Root()) } return &KustTarget{ - kustomization: &k, - ldr: ldr, - rFactory: rFactory, - tFactory: tFactory, + kustomization: &k, + ldr: ldr, + rFactory: rFactory, + tFactory: tFactory, + goPluginEnabled: b, }, nil } @@ -255,7 +256,7 @@ func (kt *KustTarget) accumulateBases() ( continue } subKt, err := NewKustTarget( - ldr, kt.rFactory, kt.tFactory) + ldr, kt.rFactory, kt.tFactory, kt.goPluginEnabled) if err != nil { errs.Append(errors.Wrap(err, "couldn't make target for "+path)) ldr.Cleanup() @@ -331,48 +332,11 @@ func (kt *KustTarget) newTransformer( } func (kt *KustTarget) loadTransformerPlugins() ([]transformers.Transformer, error) { - var result []transformers.Transformer - - pc := types.PluginConfig{ - DirectoryPath: os.Getenv("GOPATH"), - GoEnabled: true} - - root := filepath.Join( - pc.DirectoryPath, "src", "sigs.k8s.io", "kustomize", "plugins") - - if !pc.GoEnabled { - return nil, fmt.Errorf("plugins not enabled") - } - transformerPluginConfigs, err := kt.rFactory.FromFiles( kt.ldr, kt.kustomization.Transformers) - - for id, res := range transformerPluginConfigs { - fileName := filepath.Join(root, id.Gvk().Kind+".so") - goPlugin, err := plugin.Open(fileName) - if err != nil { - return nil, fmt.Errorf("plugin %s file not opened", fileName) - } - symbol, err := goPlugin.Lookup("Transformer") - if err != nil { - return nil, fmt.Errorf("plugin %s fails lookup", fileName) - } - c, ok := symbol.(transformers.Configurable) - if !ok { - return nil, fmt.Errorf("plugin %s not configurable", fileName) - } - err = c.Config(res) - if err != nil { - return nil, errors.Wrapf(err, "plugin %s fails configuration", fileName) - } - t, ok := c.(transformers.Transformer) - if !ok { - return nil, fmt.Errorf("plugin %s not a transformer", fileName) - } - - result = append(result, t) - fmt.Printf("Added plugin %s\n", fileName) - + if err != nil { + return nil, err } - return result, err + tl := plugins.NewTransformerLoader(kt.goPluginEnabled) + return tl.Load(transformerPluginConfigs) } diff --git a/pkg/target/kusttarget_test.go b/pkg/target/kusttarget_test.go index e57fe0f37..cde95b544 100644 --- a/pkg/target/kusttarget_test.go +++ b/pkg/target/kusttarget_test.go @@ -204,7 +204,7 @@ func TestResources(t *testing.T) { } func TestKustomizationNotFound(t *testing.T) { - _, err := NewKustTarget(loadertest.NewFakeLoader("/foo"), nil, nil) + _, err := NewKustTarget(loadertest.NewFakeLoader("/foo"), nil, nil, false) if err == nil { t.Fatalf("expected an error") } diff --git a/pkg/target/kusttestharness_test.go b/pkg/target/kusttestharness_test.go index ea3b712bd..70f997862 100644 --- a/pkg/target/kusttestharness_test.go +++ b/pkg/target/kusttestharness_test.go @@ -40,6 +40,7 @@ type KustTestHarness struct { t *testing.T rf *resmap.Factory ldr loadertest.FakeLoader + b bool } func NewKustTestHarness(t *testing.T, path string) *KustTestHarness { @@ -55,12 +56,13 @@ func NewKustTestHarnessWithPluginConfig( rf: resmap.NewFactory(resource.NewFactory( kunstruct.NewKunstructuredFactoryWithGeneratorArgs( &types.GeneratorMetaArgs{PluginConfig: config}))), - ldr: loadertest.NewFakeLoader(path)} + ldr: loadertest.NewFakeLoader(path), + b: config.GoEnabled} } func (th *KustTestHarness) makeKustTarget() *KustTarget { kt, err := NewKustTarget( - th.ldr, th.rf, transformer.NewFactoryImpl()) + th.ldr, th.rf, transformer.NewFactoryImpl(), th.b) if err != nil { th.t.Fatalf("Unexpected construction error %v", err) } diff --git a/pkg/target/transformerplugin_test.go b/pkg/target/transformerplugin_test.go index 9c050b0cf..1345d9cdf 100644 --- a/pkg/target/transformerplugin_test.go +++ b/pkg/target/transformerplugin_test.go @@ -14,8 +14,14 @@ limitations under the License. package target_test import ( - "sigs.k8s.io/kustomize/pkg/types" + "os" + "os/exec" + "path/filepath" "testing" + + "fmt" + "sigs.k8s.io/kustomize/pkg/pgmconfig" + "sigs.k8s.io/kustomize/pkg/types" ) func writeDeployment(th *KustTestHarness, path string) { @@ -55,7 +61,45 @@ metadata: `) } +func buildGoPlugins(dir, filename string) error { + commands := []string{ + "build", + "-buildmode", + "plugin", + "-tags=plugin", + "-o", + filename + ".so", + filename + ".go", + } + goBin := filepath.Join(os.Getenv("GOROOT"), "bin", "go") + if _, err := os.Stat(goBin); err != nil { + return fmt.Errorf("go binary not found %s", goBin) + } + cmd := exec.Command(goBin, commands...) + cmd.Env = os.Environ() + cmd.Dir = filepath.Join(dir, "kustomize", "plugins") + + return cmd.Run() +} + func TestOrderedTransformers(t *testing.T) { + dir, err := filepath.Abs("../../..") + if err != nil { + t.Errorf("unexpected error %v", err) + } + os.Setenv(pgmconfig.XDG_CONFIG_HOME, dir) + defer os.Unsetenv(pgmconfig.XDG_CONFIG_HOME) + + err = buildGoPlugins(dir, "StringPrefixer") + if err != nil { + t.Errorf("unexpected error %v", err) + } + + err = buildGoPlugins(dir, "DatePrefixer") + if err != nil { + t.Errorf("unexpected error %v", err) + } + th := NewKustTestHarnessWithPluginConfig( t, "/app", types.PluginConfig{GoEnabled: true}) th.writeK("/app", ` diff --git a/pkg/transformers/transformer.go b/pkg/transformers/transformer.go index 93288646c..fc0803fce 100644 --- a/pkg/transformers/transformer.go +++ b/pkg/transformers/transformer.go @@ -18,7 +18,6 @@ limitations under the License. package transformers import ( - "sigs.k8s.io/kustomize/pkg/ifc" "sigs.k8s.io/kustomize/pkg/resmap" ) @@ -27,7 +26,3 @@ type Transformer interface { // Transform modifies data in the argument, e.g. adding labels to resources that can be labelled. Transform(m resmap.ResMap) error } - -type Configurable interface { - Config(k ifc.Kunstructured) error -}