mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-18 06:25:20 +00:00
Define a plugin compiler.
This commit is contained in:
@@ -42,7 +42,7 @@ func NewDefaultCommand() *cobra.Command {
|
||||
stdOut := os.Stdout
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: pgmconfig.PgmName,
|
||||
Use: pgmconfig.ProgramName,
|
||||
Short: "Manages declarative configuration of Kubernetes",
|
||||
Long: `
|
||||
Manages declarative configuration of Kubernetes.
|
||||
|
||||
@@ -34,12 +34,12 @@ func ConfigRoot() string {
|
||||
dir := os.Getenv(XDG_CONFIG_HOME)
|
||||
if len(dir) == 0 {
|
||||
dir = filepath.Join(
|
||||
homeDir(), defaultConfigSubdir)
|
||||
HomeDir(), defaultConfigSubdir)
|
||||
}
|
||||
return filepath.Join(dir, PgmName)
|
||||
return filepath.Join(dir, ProgramName)
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
func HomeDir() string {
|
||||
home := os.Getenv(homeEnv())
|
||||
if len(home) > 0 {
|
||||
return home
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestConfigDirNoXdg(t *testing.T) {
|
||||
}
|
||||
if !strings.HasSuffix(
|
||||
s,
|
||||
rootedPath(defaultConfigSubdir, PgmName)) {
|
||||
rootedPath(defaultConfigSubdir, ProgramName)) {
|
||||
t.Fatalf("unexpected config dir: %s", s)
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func TestConfigDirWithXdg(t *testing.T) {
|
||||
if isSet {
|
||||
os.Setenv(XDG_CONFIG_HOME, xdg)
|
||||
}
|
||||
if s != rootedPath("blah", PgmName) {
|
||||
if s != rootedPath("blah", ProgramName) {
|
||||
t.Fatalf("unexpected config dir: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ var KustomizationFileNames = []string{
|
||||
}
|
||||
|
||||
const (
|
||||
PgmName = "kustomize"
|
||||
Repo = "sigs.k8s.io"
|
||||
// Program name, for help, finding the XDG_CONFIG_DIR, etc.
|
||||
ProgramName = "kustomize"
|
||||
// Domain from which kustomize code is imported, for locating
|
||||
// plugin source code under $GOPATH.
|
||||
DomainName = "sigs.k8s.io"
|
||||
)
|
||||
|
||||
146
pkg/plugins/compiler.go
Normal file
146
pkg/plugins/compiler.go
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
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"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv/plugin"
|
||||
"sigs.k8s.io/kustomize/pkg/pgmconfig"
|
||||
)
|
||||
|
||||
// Compiler creates Go plugin object files.
|
||||
//
|
||||
// Source code is read from
|
||||
// ${srcRoot}/${g}/${v}/${k}.go
|
||||
//
|
||||
// Object code is written to
|
||||
// ${objRoot}/${g}/${v}/${k}.so
|
||||
type Compiler struct {
|
||||
srcRoot string
|
||||
objRoot string
|
||||
}
|
||||
|
||||
// DefaultSrcRoot guesses where the user
|
||||
// has her ${g}/${v}/${k}.go files.
|
||||
func DefaultSrcRoot() (string, error) {
|
||||
var nope []string
|
||||
var root string
|
||||
|
||||
root = filepath.Join(
|
||||
os.Getenv("GOPATH"), "src",
|
||||
pgmconfig.DomainName,
|
||||
pgmconfig.ProgramName, plugin.PluginRoot)
|
||||
if FileExists(root) {
|
||||
return root, nil
|
||||
}
|
||||
nope = append(nope, root)
|
||||
|
||||
root = filepath.Join(
|
||||
pgmconfig.ConfigRoot(), plugin.PluginRoot)
|
||||
if FileExists(root) {
|
||||
return root, nil
|
||||
}
|
||||
nope = append(nope, root)
|
||||
|
||||
root = filepath.Join(
|
||||
pgmconfig.HomeDir(),
|
||||
pgmconfig.ProgramName, plugin.PluginRoot)
|
||||
if FileExists(root) {
|
||||
return root, nil
|
||||
}
|
||||
nope = append(nope, root)
|
||||
|
||||
return "", fmt.Errorf(
|
||||
"no default src root; tried %v", nope)
|
||||
}
|
||||
|
||||
// NewCompiler returns a new compiler instance.
|
||||
func NewCompiler(srcRoot, objRoot string) *Compiler {
|
||||
return &Compiler{srcRoot: srcRoot, objRoot: objRoot}
|
||||
}
|
||||
|
||||
// ObjRoot is root of compilation target tree.
|
||||
func (b *Compiler) ObjRoot() string {
|
||||
return b.objRoot
|
||||
}
|
||||
|
||||
func goBin() string {
|
||||
return filepath.Join(os.Getenv("GOROOT"), "bin", "go")
|
||||
}
|
||||
|
||||
// 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)
|
||||
objFile := filepath.Join(objDir, k) + ".so"
|
||||
if RecentFileExists(objFile) {
|
||||
// Skip rebuilding it.
|
||||
return nil
|
||||
}
|
||||
err := os.MkdirAll(objDir, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srcFile := filepath.Join(b.srcRoot, g, v, k) + ".go"
|
||||
if !FileExists(srcFile) {
|
||||
return fmt.Errorf(
|
||||
"cannot find source %s", srcFile)
|
||||
}
|
||||
commands := []string{
|
||||
"build",
|
||||
"-buildmode",
|
||||
"plugin",
|
||||
"-tags=plugin",
|
||||
"-o", objFile, srcFile,
|
||||
}
|
||||
goBin := goBin()
|
||||
if !FileExists(goBin) {
|
||||
return fmt.Errorf(
|
||||
"cannot find go compiler %s", goBin)
|
||||
}
|
||||
cmd := exec.Command(goBin, commands...)
|
||||
cmd.Env = os.Environ()
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf(
|
||||
"compiler error building %s: %v", srcFile, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// True if file less than 3 minutes old, i.e. not
|
||||
// accidentally left over from some earlier build.
|
||||
func RecentFileExists(path string) bool {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
age := time.Now().Sub(fi.ModTime())
|
||||
return age.Minutes() < 3
|
||||
}
|
||||
|
||||
func FileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
60
pkg/plugins/compiler_test.go
Normal file
60
pkg/plugins/compiler_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/pkg/plugins"
|
||||
)
|
||||
|
||||
// Regression coverage over compiler behavior.
|
||||
func TestCompiler(t *testing.T) {
|
||||
configRoot, err := ioutil.TempDir("", "kustomize-compiler-test")
|
||||
if err != nil {
|
||||
t.Errorf("failed to make temp dir: %v", err)
|
||||
}
|
||||
srcRoot, err := DefaultSrcRoot()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
c := NewCompiler(srcRoot, configRoot)
|
||||
if configRoot != c.ObjRoot() {
|
||||
t.Errorf("unexpected objRoot %s", c.ObjRoot())
|
||||
}
|
||||
expectObj := filepath.Join(
|
||||
c.ObjRoot(),
|
||||
"someteam.example.com", "v1", "DatePrefixer.so")
|
||||
if FileExists(expectObj) {
|
||||
t.Errorf("obj file should not exist yet: %s", expectObj)
|
||||
}
|
||||
err = c.Compile("someteam.example.com", "v1", "DatePrefixer")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !RecentFileExists(expectObj) {
|
||||
t.Errorf("didn't find expected obj file %s", expectObj)
|
||||
}
|
||||
err = os.RemoveAll(c.ObjRoot())
|
||||
if err != nil {
|
||||
t.Errorf(
|
||||
"removing temp dir: %s %v", c.ObjRoot(), err)
|
||||
}
|
||||
if FileExists(expectObj) {
|
||||
t.Errorf("cleanup failed; still see: %s", expectObj)
|
||||
}
|
||||
}
|
||||
@@ -77,12 +77,13 @@ func loadAndConfigurePlugin(
|
||||
fileName string, res *resource.Resource) (Configurable, error) {
|
||||
goPlugin, err := plugin.Open(fileName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("plugin %s file not opened", fileName)
|
||||
return nil, errors.Wrapf(err, "plugin %s fails to load", fileName)
|
||||
}
|
||||
symbol, err := goPlugin.Lookup(kplugin.PluginSymbol)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"plugin %s doesn't have symbol %s", fileName, kplugin.PluginSymbol)
|
||||
return nil, errors.Wrapf(
|
||||
err, "plugin %s doesn't have symbol %s",
|
||||
fileName, kplugin.PluginSymbol)
|
||||
}
|
||||
c, ok := symbol.(Configurable)
|
||||
if !ok {
|
||||
|
||||
@@ -233,6 +233,7 @@ func (kt *KustTarget) generateFromPlugins(
|
||||
generators, err := kt.loadGeneratorPlugins()
|
||||
if err != nil {
|
||||
errs.Append(err)
|
||||
return
|
||||
}
|
||||
for _, g := range generators {
|
||||
resMap, err := g.Generate()
|
||||
|
||||
@@ -16,22 +16,22 @@ package target_test
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"sigs.k8s.io/kustomize/k8sdeps/kv/plugin"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/pgmconfig"
|
||||
"sigs.k8s.io/kustomize/pkg/plugins"
|
||||
)
|
||||
|
||||
// TestEnvController manages the KustTarget test environment.
|
||||
// It sets/resets XDG_CONFIG_HOME, makes/removes a temp objRoot.
|
||||
type TestEnvController struct {
|
||||
t *testing.T
|
||||
xdgConfigHome string
|
||||
oldXdg string
|
||||
wasSet bool
|
||||
t *testing.T
|
||||
compiler *plugins.Compiler
|
||||
workDir string
|
||||
oldXdg string
|
||||
wasSet bool
|
||||
}
|
||||
|
||||
func NewTestEnvController(t *testing.T) *TestEnvController {
|
||||
@@ -39,121 +39,62 @@ func NewTestEnvController(t *testing.T) *TestEnvController {
|
||||
}
|
||||
|
||||
func (x *TestEnvController) Set() *TestEnvController {
|
||||
x.makeTmpConfigHomeDir()
|
||||
x.makeObjectRootDir()
|
||||
x.createWorkDir()
|
||||
x.compiler = x.makeCompiler()
|
||||
x.setEnv()
|
||||
return x
|
||||
}
|
||||
|
||||
func (x *TestEnvController) Reset() {
|
||||
x.resetEnv()
|
||||
x.removeTmpConfigHomeDir()
|
||||
x.removeWorkDir()
|
||||
}
|
||||
|
||||
func (x *TestEnvController) fileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (x *TestEnvController) recentFileExists(path string) bool {
|
||||
fi, err := os.Stat(path)
|
||||
func (x *TestEnvController) BuildGoPlugin(g, v, k string) {
|
||||
err := x.compiler.Compile(g, v, k)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
age := time.Now().Sub(fi.ModTime())
|
||||
return age.Minutes() < 1
|
||||
}
|
||||
|
||||
func (x *TestEnvController) BuildGoPlugin(plugin ...string) {
|
||||
obj := filepath.Join(
|
||||
append([]string{x.ObjectRoot()}, plugin...)...) + ".so"
|
||||
if x.recentFileExists(obj) {
|
||||
// Skip rebuilding it.
|
||||
return
|
||||
}
|
||||
src := filepath.Join(
|
||||
append([]string{x.SrcRoot()}, plugin...)...) + ".go"
|
||||
if !x.fileExists(src) {
|
||||
x.t.Errorf("cannot find go plugin source %s", src)
|
||||
}
|
||||
commands := []string{
|
||||
"build",
|
||||
"-buildmode",
|
||||
"plugin",
|
||||
"-tags=plugin",
|
||||
"-o", obj, src,
|
||||
}
|
||||
goBin := filepath.Join(os.Getenv("GOROOT"), "bin", "go")
|
||||
if !x.fileExists(src) {
|
||||
x.t.Errorf("cannot find go compiler %s", goBin)
|
||||
}
|
||||
cmd := exec.Command(goBin, commands...)
|
||||
cmd.Env = os.Environ()
|
||||
// cmd.Dir = filepath.Join(dir, "kustomize", "plugins")
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
x.t.Errorf("compiler error building %s: %v", src, err)
|
||||
x.t.Errorf("compile failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ObjectRoot is the objRoot dir for plugin object files.
|
||||
func (x *TestEnvController) ObjectRoot() string {
|
||||
return filepath.Join(
|
||||
x.xdgConfigHome, pgmconfig.PgmName, plugin.PluginRoot)
|
||||
}
|
||||
|
||||
// SrcRoot is a objRoot directory for plugin source code
|
||||
// used by tests.
|
||||
//
|
||||
// Plugin object code files have to be in a particular
|
||||
// location to be found and loaded for security reasons,
|
||||
// but placement of plugin source code is up to the user.
|
||||
//
|
||||
// This function returns a location for storing example
|
||||
// plugins for tests. And maybe builtins at some point.
|
||||
func (x *TestEnvController) SrcRoot() string {
|
||||
dir := filepath.Join(
|
||||
os.Getenv("GOPATH"), "src",
|
||||
pgmconfig.Repo, pgmconfig.PgmName, plugin.PluginRoot)
|
||||
if _, err := os.Stat(dir); err != nil {
|
||||
x.t.Errorf("plugin source objRoot '%s' not found", dir)
|
||||
func (x *TestEnvController) makeCompiler() *plugins.Compiler {
|
||||
// The plugin loader wants to find object code under
|
||||
// $XDG_CONFIG_HOME/kustomize/plugins
|
||||
// and the compiler writes object code to
|
||||
// $objRoot
|
||||
// so set things up accordingly.
|
||||
objRoot := filepath.Join(
|
||||
x.workDir, pgmconfig.ProgramName, plugin.PluginRoot)
|
||||
err := os.MkdirAll(objRoot, os.ModePerm)
|
||||
if err != nil {
|
||||
x.t.Error(err)
|
||||
}
|
||||
return dir
|
||||
srcRoot, err := plugins.DefaultSrcRoot()
|
||||
if err != nil {
|
||||
x.t.Error(err)
|
||||
}
|
||||
return plugins.NewCompiler(srcRoot, objRoot)
|
||||
}
|
||||
|
||||
func (x *TestEnvController) makeTmpConfigHomeDir() {
|
||||
func (x *TestEnvController) createWorkDir() {
|
||||
var err error
|
||||
x.xdgConfigHome, err = ioutil.TempDir("", "kustomizetests")
|
||||
x.workDir, err = ioutil.TempDir("", "kustomize-plugin-tests")
|
||||
if err != nil {
|
||||
x.t.Errorf("failed to make temp objRoot: %v", err)
|
||||
x.t.Errorf("failed to make work dir: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *TestEnvController) makeObjectRootDir() {
|
||||
err := os.MkdirAll(x.ObjectRoot(), os.ModePerm)
|
||||
func (x *TestEnvController) removeWorkDir() {
|
||||
err := os.RemoveAll(x.workDir)
|
||||
if err != nil {
|
||||
x.t.Errorf(
|
||||
"making temp object objRoot %s: %v", x.ObjectRoot(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *TestEnvController) removeTmpConfigHomeDir() {
|
||||
err := os.RemoveAll(x.xdgConfigHome)
|
||||
if err != nil {
|
||||
x.t.Errorf(
|
||||
"removing temp object objRoot: %s %v", x.xdgConfigHome, err)
|
||||
"removing work dir: %s %v", x.workDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *TestEnvController) setEnv() {
|
||||
x.oldXdg, x.wasSet = os.LookupEnv(pgmconfig.XDG_CONFIG_HOME)
|
||||
os.Setenv(pgmconfig.XDG_CONFIG_HOME, x.xdgConfigHome)
|
||||
os.Setenv(pgmconfig.XDG_CONFIG_HOME, x.workDir)
|
||||
}
|
||||
|
||||
func (x *TestEnvController) resetEnv() {
|
||||
|
||||
Reference in New Issue
Block a user