Allow importing kustomize API's without relying on plugins (#5525)

* Allow importing kustomize API's without relying on `plugins`

Introduce `kustomize_disable_go_plugin_support` go build tag to decouple the kustomize
API from the `plugins` package dependency. This is advantageous for applications
embedding the kusstomize API without the need for dynamic Go plugins, mitigating an
increase in binary size associated with the inclusion of the plugins dependency
and the population of ELF sections like `.dynsym` and `.dynstr`.

The flag provides applications with the flexibility to exclude the import, catering to
scenarios where dynamic Go plugin support is unnecessary.

Signed-off-by: Tiago Silva <tiago.silva@goteleport.com>

* fix golint by disabling some lint checks

* handle code review suggestions

---------

Signed-off-by: Tiago Silva <tiago.silva@goteleport.com>
This commit is contained in:
Tiago Silva
2024-02-27 11:22:51 -05:00
committed by GitHub
parent 28ef9da0d9
commit 33caee50cb
3 changed files with 86 additions and 47 deletions

View File

@@ -0,0 +1,62 @@
// Copyright 2024 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//go:build !kustomize_disable_go_plugin_support
package loader
import (
"fmt"
"log"
"plugin"
"reflect"
"sigs.k8s.io/kustomize/api/internal/plugins/utils"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/resid"
)
// registry is a means to avoid trying to load the same .so file
// into memory more than once, which results in an error.
// Each test makes its own loader, and tries to load its own plugins,
// but the loaded .so files are in shared memory, so one will get
// "this plugin already loaded" errors if the registry is maintained
// as a Loader instance variable. So make it a package variable.
var registry = make(map[string]resmap.Configurable) //nolint:gochecknoglobals
func copyPlugin(c resmap.Configurable) resmap.Configurable {
indirect := reflect.Indirect(reflect.ValueOf(c))
newIndirect := reflect.New(indirect.Type())
newIndirect.Elem().Set(reflect.ValueOf(indirect.Interface()))
newNamed := newIndirect.Interface()
return newNamed.(resmap.Configurable) //nolint:forcetypeassert
}
func (l *Loader) loadGoPlugin(id resid.ResId, absPath string) (resmap.Configurable, error) {
regId := relativePluginPath(id)
if c, ok := registry[regId]; ok {
return copyPlugin(c), nil
}
if !utils.FileExists(absPath) {
return nil, fmt.Errorf(
"expected file with Go object code at: %s", absPath)
}
log.Printf("Attempting plugin load from %q", absPath)
p, err := plugin.Open(absPath)
if err != nil {
return nil, errors.WrapPrefixf(err, "plugin %s fails to load", absPath)
}
symbol, err := p.Lookup(konfig.PluginSymbol)
if err != nil {
return nil, errors.WrapPrefixf(
err, "plugin %s doesn't have symbol %s",
regId, konfig.PluginSymbol)
}
c, ok := symbol.(resmap.Configurable)
if !ok {
return nil, fmt.Errorf("plugin %q not configurable", regId)
}
registry[regId] = c
return copyPlugin(c), nil
}

View File

@@ -0,0 +1,24 @@
// Copyright 2024 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// The build tag "kustomize_disable_go_plugin_support" is used to deactivate the
// kustomize API's dependency on the "plugins" package. This is beneficial for
// applications that need to embed it but do not have requirements for dynamic
// Go plugins.
// Including plugins as a dependency can lead to an increase in binary size due
// to the population of ELF's sections such as .dynsym and .dynstr.
// By utilizing this flag, applications have the flexibility to exclude the
// import if they do not require support for dynamic Go plugins.
//go:build kustomize_disable_go_plugin_support
package loader
import (
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/resid"
)
func (l *Loader) loadGoPlugin(_ resid.ResId, _ string) (resmap.Configurable, error) {
return nil, errors.New("plugin load is disabled")
}

View File

@@ -5,18 +5,14 @@ package loader
import (
"fmt"
"log"
"os"
"path/filepath"
"plugin"
"reflect"
"strings"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers"
"sigs.k8s.io/kustomize/api/internal/plugins/execplugin"
"sigs.k8s.io/kustomize/api/internal/plugins/fnplugin"
"sigs.k8s.io/kustomize/api/internal/plugins/utils"
"sigs.k8s.io/kustomize/api/konfig"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
@@ -287,46 +283,3 @@ func (l *Loader) loadExecOrGoPlugin(resId resid.ResId) (resmap.Configurable, err
return c, nil
}
// registry is a means to avoid trying to load the same .so file
// into memory more than once, which results in an error.
// Each test makes its own loader, and tries to load its own plugins,
// but the loaded .so files are in shared memory, so one will get
// "this plugin already loaded" errors if the registry is maintained
// as a Loader instance variable. So make it a package variable.
var registry = make(map[string]resmap.Configurable)
func (l *Loader) loadGoPlugin(id resid.ResId, absPath string) (resmap.Configurable, error) {
regId := relativePluginPath(id)
if c, ok := registry[regId]; ok {
return copyPlugin(c), nil
}
if !utils.FileExists(absPath) {
return nil, fmt.Errorf(
"expected file with Go object code at: %s", absPath)
}
log.Printf("Attempting plugin load from '%s'", absPath)
p, err := plugin.Open(absPath)
if err != nil {
return nil, errors.WrapPrefixf(err, "plugin %s fails to load", absPath)
}
symbol, err := p.Lookup(konfig.PluginSymbol)
if err != nil {
return nil, errors.WrapPrefixf(
err, "plugin %s doesn't have symbol %s",
regId, konfig.PluginSymbol)
}
c, ok := symbol.(resmap.Configurable)
if !ok {
return nil, fmt.Errorf("plugin '%s' not configurable", regId)
}
registry[regId] = c
return copyPlugin(c), nil
}
func copyPlugin(c resmap.Configurable) resmap.Configurable {
indirect := reflect.Indirect(reflect.ValueOf(c))
newIndirect := reflect.New(indirect.Type())
newIndirect.Elem().Set(reflect.ValueOf(indirect.Interface()))
newNamed := newIndirect.Interface()
return newNamed.(resmap.Configurable)
}