mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-10 08:20:59 +00:00
Remove remaining plugins packages from external API.
This commit is contained in:
10
api/internal/plugins/builtinconfig/doc.go
Normal file
10
api/internal/plugins/builtinconfig/doc.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package builtinconfig provides legacy methods for
|
||||
// configuring builtin plugins from a common config file.
|
||||
// As a user, its best to configure plugins individually
|
||||
// with plugin config files specified in the `transformers:`
|
||||
// or `generators:` field, than to use this legacy
|
||||
// configuration technique.
|
||||
package builtinconfig
|
||||
42
api/internal/plugins/builtinconfig/loaddefaultconfig.go
Normal file
42
api/internal/plugins/builtinconfig/loaddefaultconfig.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package builtinconfig
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// loadDefaultConfig returns a TranformerConfig
|
||||
// object from a list of files.
|
||||
func loadDefaultConfig(
|
||||
ldr ifc.Loader, paths []string) (*TransformerConfig, error) {
|
||||
result := &TransformerConfig{}
|
||||
for _, path := range paths {
|
||||
data, err := ldr.Load(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, err := makeTransformerConfigFromBytes(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err = result.Merge(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// makeTransformerConfigFromBytes returns a TransformerConfig object from bytes
|
||||
func makeTransformerConfigFromBytes(data []byte) (*TransformerConfig, error) {
|
||||
var t TransformerConfig
|
||||
err := yaml.Unmarshal(data, &t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.sortFields()
|
||||
return &t, nil
|
||||
}
|
||||
37
api/internal/plugins/builtinconfig/loaddefaultconfig_test.go
Normal file
37
api/internal/plugins/builtinconfig/loaddefaultconfig_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package builtinconfig
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/loadertest"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func TestLoadDefaultConfigsFromFiles(t *testing.T) {
|
||||
ldr := loadertest.NewFakeLoader("/app")
|
||||
ldr.AddFile("/app/config.yaml", []byte(`
|
||||
namePrefix:
|
||||
- path: nameprefix/path
|
||||
kind: SomeKind
|
||||
`))
|
||||
tcfg, err := loadDefaultConfig(ldr, []string{"/app/config.yaml"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
expected := &TransformerConfig{
|
||||
NamePrefix: []types.FieldSpec{
|
||||
{
|
||||
Gvk: resid.Gvk{Kind: "SomeKind"},
|
||||
Path: "nameprefix/path",
|
||||
},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(tcfg, expected) {
|
||||
t.Fatalf("expected %v\n but go6t %v\n", expected, tcfg)
|
||||
}
|
||||
}
|
||||
93
api/internal/plugins/builtinconfig/namebackreferences.go
Normal file
93
api/internal/plugins/builtinconfig/namebackreferences.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package builtinconfig
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
// NameBackReferences is an association between a gvk.GVK and a list
|
||||
// of FieldSpec instances that could refer to it.
|
||||
//
|
||||
// It is used to handle name changes, and can be thought of as a
|
||||
// a contact list. If you change your own contact info (name,
|
||||
// phone number, etc.), you must tell your contacts or they won't
|
||||
// know about the change.
|
||||
//
|
||||
// For example, ConfigMaps can be used by Pods and everything that
|
||||
// contains a Pod; Deployment, Job, StatefulSet, etc. To change
|
||||
// the name of a ConfigMap instance from 'alice' to 'bob', one
|
||||
// must visit all objects that could refer to the ConfigMap, see if
|
||||
// they mention 'alice', and if so, change the reference to 'bob'.
|
||||
//
|
||||
// The NameBackReferences instance to aid in this could look like
|
||||
// {
|
||||
// kind: ConfigMap
|
||||
// version: v1
|
||||
// FieldSpecs:
|
||||
// - kind: Pod
|
||||
// version: v1
|
||||
// path: spec/volumes/configMap/name
|
||||
// - kind: Deployment
|
||||
// path: spec/template/spec/volumes/configMap/name
|
||||
// - kind: Job
|
||||
// path: spec/template/spec/volumes/configMap/name
|
||||
// (etc.)
|
||||
// }
|
||||
type NameBackReferences struct {
|
||||
resid.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
|
||||
FieldSpecs types.FsSlice `json:"FieldSpecs,omitempty" yaml:"FieldSpecs,omitempty"`
|
||||
}
|
||||
|
||||
func (n NameBackReferences) String() string {
|
||||
var r []string
|
||||
for _, f := range n.FieldSpecs {
|
||||
r = append(r, f.String())
|
||||
}
|
||||
return n.Gvk.String() + ": (\n" +
|
||||
strings.Join(r, "\n") + "\n)"
|
||||
}
|
||||
|
||||
type nbrSlice []NameBackReferences
|
||||
|
||||
func (s nbrSlice) Len() int { return len(s) }
|
||||
func (s nbrSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s nbrSlice) Less(i, j int) bool {
|
||||
return s[i].Gvk.IsLessThan(s[j].Gvk)
|
||||
}
|
||||
|
||||
func (s nbrSlice) mergeAll(o nbrSlice) (result nbrSlice, err error) {
|
||||
result = s
|
||||
for _, r := range o {
|
||||
result, err = result.mergeOne(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s nbrSlice) mergeOne(other NameBackReferences) (nbrSlice, error) {
|
||||
var result nbrSlice
|
||||
var err error
|
||||
found := false
|
||||
for _, c := range s {
|
||||
if c.Gvk.Equals(other.Gvk) {
|
||||
c.FieldSpecs, err = c.FieldSpecs.MergeAll(other.FieldSpecs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
found = true
|
||||
}
|
||||
result = append(result, c)
|
||||
}
|
||||
|
||||
if !found {
|
||||
result = append(result, other)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package builtinconfig
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func TestMergeAll(t *testing.T) {
|
||||
fsSlice1 := []types.FieldSpec{
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "Pod",
|
||||
},
|
||||
Path: "path/to/a/name",
|
||||
CreateIfNotPresent: false,
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "Deployment",
|
||||
},
|
||||
Path: "another/path/to/some/name",
|
||||
CreateIfNotPresent: false,
|
||||
},
|
||||
}
|
||||
fsSlice2 := []types.FieldSpec{
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "Job",
|
||||
},
|
||||
Path: "morepath/to/name",
|
||||
CreateIfNotPresent: false,
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "StatefulSet",
|
||||
},
|
||||
Path: "yet/another/path/to/a/name",
|
||||
CreateIfNotPresent: false,
|
||||
},
|
||||
}
|
||||
|
||||
nbrsSlice1 := nbrSlice{
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
FieldSpecs: fsSlice1,
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "Secret",
|
||||
},
|
||||
FieldSpecs: fsSlice2,
|
||||
},
|
||||
}
|
||||
nbrsSlice2 := nbrSlice{
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
FieldSpecs: fsSlice1,
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "Secret",
|
||||
},
|
||||
FieldSpecs: fsSlice2,
|
||||
},
|
||||
}
|
||||
expected := nbrSlice{
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
FieldSpecs: fsSlice1,
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "Secret",
|
||||
},
|
||||
FieldSpecs: fsSlice2,
|
||||
},
|
||||
}
|
||||
actual, err := nbrsSlice1.mergeAll(nbrsSlice2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("expected\n %v\n but got\n %v\n", expected, actual)
|
||||
}
|
||||
}
|
||||
148
api/internal/plugins/builtinconfig/transformerconfig.go
Normal file
148
api/internal/plugins/builtinconfig/transformerconfig.go
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package builtinconfig
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sort"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/konfig/builtinpluginconsts"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
// TransformerConfig holds the data needed to perform transformations.
|
||||
type TransformerConfig struct {
|
||||
NamePrefix types.FsSlice `json:"namePrefix,omitempty" yaml:"namePrefix,omitempty"`
|
||||
NameSuffix types.FsSlice `json:"nameSuffix,omitempty" yaml:"nameSuffix,omitempty"`
|
||||
NameSpace types.FsSlice `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
CommonLabels types.FsSlice `json:"commonLabels,omitempty" yaml:"commonLabels,omitempty"`
|
||||
CommonAnnotations types.FsSlice `json:"commonAnnotations,omitempty" yaml:"commonAnnotations,omitempty"`
|
||||
NameReference nbrSlice `json:"nameReference,omitempty" yaml:"nameReference,omitempty"`
|
||||
VarReference types.FsSlice `json:"varReference,omitempty" yaml:"varReference,omitempty"`
|
||||
Images types.FsSlice `json:"images,omitempty" yaml:"images,omitempty"`
|
||||
Replicas types.FsSlice `json:"replicas,omitempty" yaml:"replicas,omitempty"`
|
||||
}
|
||||
|
||||
// MakeEmptyConfig returns an empty TransformerConfig object
|
||||
func MakeEmptyConfig() *TransformerConfig {
|
||||
return &TransformerConfig{}
|
||||
}
|
||||
|
||||
// MakeDefaultConfig returns a default TransformerConfig.
|
||||
func MakeDefaultConfig() *TransformerConfig {
|
||||
c, err := makeTransformerConfigFromBytes(
|
||||
builtinpluginconsts.GetDefaultFieldSpecs())
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to make default transformconfig: %v", err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// MakeTransformerConfig returns a merger of custom config,
|
||||
// if any, with default config.
|
||||
func MakeTransformerConfig(
|
||||
ldr ifc.Loader, paths []string) (*TransformerConfig, error) {
|
||||
t1 := MakeDefaultConfig()
|
||||
if len(paths) == 0 {
|
||||
return t1, nil
|
||||
}
|
||||
t2, err := loadDefaultConfig(ldr, paths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t1.Merge(t2)
|
||||
}
|
||||
|
||||
// sortFields provides determinism in logging, tests, etc.
|
||||
func (t *TransformerConfig) sortFields() {
|
||||
sort.Sort(t.NamePrefix)
|
||||
sort.Sort(t.NameSpace)
|
||||
sort.Sort(t.CommonLabels)
|
||||
sort.Sort(t.CommonAnnotations)
|
||||
sort.Sort(t.NameReference)
|
||||
sort.Sort(t.VarReference)
|
||||
sort.Sort(t.Images)
|
||||
sort.Sort(t.Replicas)
|
||||
}
|
||||
|
||||
// AddPrefixFieldSpec adds a FieldSpec to NamePrefix
|
||||
func (t *TransformerConfig) AddPrefixFieldSpec(fs types.FieldSpec) (err error) {
|
||||
t.NamePrefix, err = t.NamePrefix.MergeOne(fs)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddSuffixFieldSpec adds a FieldSpec to NameSuffix
|
||||
func (t *TransformerConfig) AddSuffixFieldSpec(fs types.FieldSpec) (err error) {
|
||||
t.NameSuffix, err = t.NameSuffix.MergeOne(fs)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddLabelFieldSpec adds a FieldSpec to CommonLabels
|
||||
func (t *TransformerConfig) AddLabelFieldSpec(fs types.FieldSpec) (err error) {
|
||||
t.CommonLabels, err = t.CommonLabels.MergeOne(fs)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddAnnotationFieldSpec adds a FieldSpec to CommonAnnotations
|
||||
func (t *TransformerConfig) AddAnnotationFieldSpec(fs types.FieldSpec) (err error) {
|
||||
t.CommonAnnotations, err = t.CommonAnnotations.MergeOne(fs)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddNamereferenceFieldSpec adds a NameBackReferences to NameReference
|
||||
func (t *TransformerConfig) AddNamereferenceFieldSpec(
|
||||
nbrs NameBackReferences) (err error) {
|
||||
t.NameReference, err = t.NameReference.mergeOne(nbrs)
|
||||
return err
|
||||
}
|
||||
|
||||
// Merge merges two TransformerConfigs objects into
|
||||
// a new TransformerConfig object
|
||||
func (t *TransformerConfig) Merge(input *TransformerConfig) (
|
||||
merged *TransformerConfig, err error) {
|
||||
if input == nil {
|
||||
return t, nil
|
||||
}
|
||||
merged = &TransformerConfig{}
|
||||
merged.NamePrefix, err = t.NamePrefix.MergeAll(input.NamePrefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
merged.NameSuffix, err = t.NameSuffix.MergeAll(input.NameSuffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
merged.NameSpace, err = t.NameSpace.MergeAll(input.NameSpace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
merged.CommonAnnotations, err = t.CommonAnnotations.MergeAll(
|
||||
input.CommonAnnotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
merged.CommonLabels, err = t.CommonLabels.MergeAll(input.CommonLabels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
merged.VarReference, err = t.VarReference.MergeAll(input.VarReference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
merged.NameReference, err = t.NameReference.mergeAll(input.NameReference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
merged.Images, err = t.Images.MergeAll(input.Images)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
merged.Replicas, err = t.Replicas.MergeAll(input.Replicas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
merged.sortFields()
|
||||
return merged, nil
|
||||
}
|
||||
175
api/internal/plugins/builtinconfig/transformerconfig_test.go
Normal file
175
api/internal/plugins/builtinconfig/transformerconfig_test.go
Normal file
@@ -0,0 +1,175 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package builtinconfig_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func TestMakeDefaultConfig(t *testing.T) {
|
||||
// Confirm default can be made without fatal error inside call.
|
||||
_ = MakeDefaultConfig()
|
||||
}
|
||||
|
||||
func TestAddNamereferenceFieldSpec(t *testing.T) {
|
||||
cfg := &TransformerConfig{}
|
||||
|
||||
nbrs := NameBackReferences{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "KindA",
|
||||
},
|
||||
FieldSpecs: []types.FieldSpec{
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "KindB",
|
||||
},
|
||||
Path: "path/to/a/field",
|
||||
CreateIfNotPresent: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := cfg.AddNamereferenceFieldSpec(nbrs)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if len(cfg.NameReference) != 1 {
|
||||
t.Fatal("failed to add namereference FieldSpec")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddFieldSpecs(t *testing.T) {
|
||||
cfg := &TransformerConfig{}
|
||||
|
||||
fieldSpec := types.FieldSpec{
|
||||
Gvk: resid.Gvk{Group: "GroupA", Kind: "KindB"},
|
||||
Path: "path/to/a/field",
|
||||
CreateIfNotPresent: true,
|
||||
}
|
||||
|
||||
err := cfg.AddPrefixFieldSpec(fieldSpec)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if len(cfg.NamePrefix) != 1 {
|
||||
t.Fatalf("failed to add nameprefix FieldSpec")
|
||||
}
|
||||
err = cfg.AddSuffixFieldSpec(fieldSpec)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if len(cfg.NameSuffix) != 1 {
|
||||
t.Fatalf("failed to add namesuffix FieldSpec")
|
||||
}
|
||||
err = cfg.AddLabelFieldSpec(fieldSpec)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if len(cfg.CommonLabels) != 1 {
|
||||
t.Fatalf("failed to add nameprefix FieldSpec")
|
||||
}
|
||||
err = cfg.AddAnnotationFieldSpec(fieldSpec)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if len(cfg.CommonAnnotations) != 1 {
|
||||
t.Fatalf("failed to add nameprefix FieldSpec")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
nameReference := []NameBackReferences{
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "KindA",
|
||||
},
|
||||
FieldSpecs: []types.FieldSpec{
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "KindB",
|
||||
},
|
||||
Path: "path/to/a/field",
|
||||
CreateIfNotPresent: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "KindA",
|
||||
},
|
||||
FieldSpecs: []types.FieldSpec{
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "KindC",
|
||||
},
|
||||
Path: "path/to/a/field",
|
||||
CreateIfNotPresent: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
fieldSpecs := []types.FieldSpec{
|
||||
{
|
||||
Gvk: resid.Gvk{Group: "GroupA", Kind: "KindB"},
|
||||
Path: "path/to/a/field",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{Group: "GroupA", Kind: "KindC"},
|
||||
Path: "path/to/a/field",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
}
|
||||
cfga := &TransformerConfig{}
|
||||
cfga.AddNamereferenceFieldSpec(nameReference[0])
|
||||
cfga.AddPrefixFieldSpec(fieldSpecs[0])
|
||||
cfga.AddSuffixFieldSpec(fieldSpecs[0])
|
||||
|
||||
cfgb := &TransformerConfig{}
|
||||
cfgb.AddNamereferenceFieldSpec(nameReference[1])
|
||||
cfgb.AddPrefixFieldSpec(fieldSpecs[1])
|
||||
cfga.AddSuffixFieldSpec(fieldSpecs[1])
|
||||
|
||||
actual, err := cfga.Merge(cfgb)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
if len(actual.NamePrefix) != 2 {
|
||||
t.Fatal("merge failed for namePrefix FieldSpec")
|
||||
}
|
||||
|
||||
if len(actual.NameSuffix) != 2 {
|
||||
t.Fatal("merge failed for nameSuffix FieldSpec")
|
||||
}
|
||||
|
||||
if len(actual.NameReference) != 1 {
|
||||
t.Fatal("merge failed for namereference FieldSpec")
|
||||
}
|
||||
|
||||
expected := &TransformerConfig{}
|
||||
expected.AddNamereferenceFieldSpec(nameReference[0])
|
||||
expected.AddNamereferenceFieldSpec(nameReference[1])
|
||||
expected.AddPrefixFieldSpec(fieldSpecs[0])
|
||||
expected.AddPrefixFieldSpec(fieldSpecs[1])
|
||||
expected.AddSuffixFieldSpec(fieldSpecs[0])
|
||||
expected.AddSuffixFieldSpec(fieldSpecs[1])
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("expected: %v\n but got: %v\n", expected, actual)
|
||||
}
|
||||
|
||||
actual, err = cfga.Merge(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, cfga) {
|
||||
t.Fatalf("expected: %v\n but got: %v\n", cfga, actual)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Code generated by "stringer -type=BuiltinPluginType"; DO NOT EDIT.
|
||||
|
||||
package builtinhelpers
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[Unknown-0]
|
||||
_ = x[AnnotationsTransformer-1]
|
||||
_ = x[ConfigMapGenerator-2]
|
||||
_ = x[HashTransformer-3]
|
||||
_ = x[ImageTagTransformer-4]
|
||||
_ = x[InventoryTransformer-5]
|
||||
_ = x[LabelTransformer-6]
|
||||
_ = x[LegacyOrderTransformer-7]
|
||||
_ = x[NamespaceTransformer-8]
|
||||
_ = x[PatchJson6902Transformer-9]
|
||||
_ = x[PatchStrategicMergeTransformer-10]
|
||||
_ = x[PatchTransformer-11]
|
||||
_ = x[PrefixSuffixTransformer-12]
|
||||
_ = x[ReplicaCountTransformer-13]
|
||||
_ = x[SecretGenerator-14]
|
||||
}
|
||||
|
||||
const _BuiltinPluginType_name = "UnknownAnnotationsTransformerConfigMapGeneratorHashTransformerImageTagTransformerInventoryTransformerLabelTransformerLegacyOrderTransformerNamespaceTransformerPatchJson6902TransformerPatchStrategicMergeTransformerPatchTransformerPrefixSuffixTransformerReplicaCountTransformerSecretGenerator"
|
||||
|
||||
var _BuiltinPluginType_index = [...]uint16{0, 7, 29, 47, 62, 81, 101, 117, 139, 159, 183, 213, 229, 252, 275, 290}
|
||||
|
||||
func (i BuiltinPluginType) String() string {
|
||||
if i < 0 || i >= BuiltinPluginType(len(_BuiltinPluginType_index)-1) {
|
||||
return "BuiltinPluginType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _BuiltinPluginType_name[_BuiltinPluginType_index[i]:_BuiltinPluginType_index[i+1]]
|
||||
}
|
||||
75
api/internal/plugins/builtinhelpers/builtins.go
Normal file
75
api/internal/plugins/builtinhelpers/builtins.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package builtinhelpers
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/builtins"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=BuiltinPluginType
|
||||
type BuiltinPluginType int
|
||||
|
||||
const (
|
||||
Unknown BuiltinPluginType = iota
|
||||
AnnotationsTransformer
|
||||
ConfigMapGenerator
|
||||
HashTransformer
|
||||
ImageTagTransformer
|
||||
InventoryTransformer
|
||||
LabelTransformer
|
||||
LegacyOrderTransformer
|
||||
NamespaceTransformer
|
||||
PatchJson6902Transformer
|
||||
PatchStrategicMergeTransformer
|
||||
PatchTransformer
|
||||
PrefixSuffixTransformer
|
||||
ReplicaCountTransformer
|
||||
SecretGenerator
|
||||
)
|
||||
|
||||
var stringToBuiltinPluginTypeMap map[string]BuiltinPluginType
|
||||
|
||||
func init() {
|
||||
stringToBuiltinPluginTypeMap = makeStringToBuiltinPluginTypeMap()
|
||||
}
|
||||
|
||||
func makeStringToBuiltinPluginTypeMap() (result map[string]BuiltinPluginType) {
|
||||
result = make(map[string]BuiltinPluginType, 23)
|
||||
for k := range GeneratorFactories {
|
||||
result[k.String()] = k
|
||||
}
|
||||
for k := range TransformerFactories {
|
||||
result[k.String()] = k
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetBuiltinPluginType(n string) BuiltinPluginType {
|
||||
result, ok := stringToBuiltinPluginTypeMap[n]
|
||||
if ok {
|
||||
return result
|
||||
}
|
||||
return Unknown
|
||||
}
|
||||
|
||||
var GeneratorFactories = map[BuiltinPluginType]func() resmap.GeneratorPlugin{
|
||||
ConfigMapGenerator: builtins.NewConfigMapGeneratorPlugin,
|
||||
SecretGenerator: builtins.NewSecretGeneratorPlugin,
|
||||
}
|
||||
|
||||
var TransformerFactories = map[BuiltinPluginType]func() resmap.TransformerPlugin{
|
||||
AnnotationsTransformer: builtins.NewAnnotationsTransformerPlugin,
|
||||
HashTransformer: builtins.NewHashTransformerPlugin,
|
||||
ImageTagTransformer: builtins.NewImageTagTransformerPlugin,
|
||||
InventoryTransformer: builtins.NewInventoryTransformerPlugin,
|
||||
LabelTransformer: builtins.NewLabelTransformerPlugin,
|
||||
LegacyOrderTransformer: builtins.NewLegacyOrderTransformerPlugin,
|
||||
NamespaceTransformer: builtins.NewNamespaceTransformerPlugin,
|
||||
PatchJson6902Transformer: builtins.NewPatchJson6902TransformerPlugin,
|
||||
PatchStrategicMergeTransformer: builtins.NewPatchStrategicMergeTransformerPlugin,
|
||||
PatchTransformer: builtins.NewPatchTransformerPlugin,
|
||||
PrefixSuffixTransformer: builtins.NewPrefixSuffixTransformerPlugin,
|
||||
ReplicaCountTransformer: builtins.NewReplicaCountTransformerPlugin,
|
||||
}
|
||||
38
api/internal/plugins/builtinhelpers/generateBuiltins.sh
Executable file
38
api/internal/plugins/builtinhelpers/generateBuiltins.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Generate the Go code for the generator and
|
||||
# transformer factory functions in
|
||||
#
|
||||
# sigs.k8s.io/kustomize/api/builtins
|
||||
#
|
||||
# from the raw plugin directories found under
|
||||
#
|
||||
# sigs.k8s.io/kustomize/plugin/builtin
|
||||
|
||||
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
|
||||
|
||||
for goMod in $(find ./plugin/builtin -name 'go.mod'); do
|
||||
dir=$(dirname "${goMod}")
|
||||
(cd $dir; GOPATH=$myGoPath go generate ./...)
|
||||
echo "Formatting $dir"
|
||||
(cd $dir; GOPATH=$myGoPath go fmt ./...)
|
||||
done
|
||||
|
||||
echo All done.
|
||||
153
api/internal/plugins/compiler/compiler.go
Normal file
153
api/internal/plugins/compiler/compiler.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
)
|
||||
|
||||
// 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}/$lower(${k})/${k}.go files.
|
||||
func DefaultSrcRoot(fSys filesys.FileSystem) (string, error) {
|
||||
return konfig.FirstDirThatExistsElseError(
|
||||
"source directory", fSys, []konfig.NotedFunc{
|
||||
{
|
||||
Note: "old style $GOPATH",
|
||||
F: func() string {
|
||||
return filepath.Join(
|
||||
os.Getenv("GOPATH"),
|
||||
"src", konfig.DomainName,
|
||||
konfig.ProgramName, konfig.RelPluginHome)
|
||||
},
|
||||
},
|
||||
{
|
||||
Note: "HOME with literal 'gopath'",
|
||||
F: func() string {
|
||||
return filepath.Join(
|
||||
konfig.HomeDir(), "gopath",
|
||||
"src", konfig.DomainName,
|
||||
konfig.ProgramName, konfig.RelPluginHome)
|
||||
},
|
||||
},
|
||||
{
|
||||
Note: "home directory",
|
||||
F: func() string {
|
||||
return filepath.Join(
|
||||
konfig.HomeDir(), konfig.DomainName,
|
||||
konfig.ProgramName, konfig.RelPluginHome)
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// SrcRoot is where to find src.
|
||||
func (b *Compiler) SrcRoot() string {
|
||||
return b.srcRoot
|
||||
}
|
||||
|
||||
func goBin() string {
|
||||
return filepath.Join(runtime.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 {
|
||||
lowK := strings.ToLower(k)
|
||||
objDir := filepath.Join(b.objRoot, g, v, lowK)
|
||||
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, lowK, k) + ".go"
|
||||
if !FileExists(srcFile) {
|
||||
// Handy for tests of lone plugins.
|
||||
s := k + ".go"
|
||||
if !FileExists(s) {
|
||||
return fmt.Errorf(
|
||||
"cannot find source at '%s' or '%s'", srcFile, s)
|
||||
|
||||
}
|
||||
srcFile = s
|
||||
}
|
||||
commands := []string{
|
||||
"build",
|
||||
"-buildmode",
|
||||
"plugin",
|
||||
"-o", objFile, srcFile,
|
||||
}
|
||||
goBin := goBin()
|
||||
if !FileExists(goBin) {
|
||||
return fmt.Errorf(
|
||||
"cannot find go compiler %s", goBin)
|
||||
}
|
||||
cmd := exec.Command(goBin, commands...)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Env = os.Environ()
|
||||
if err := cmd.Run(); err != nil {
|
||||
return errors.Wrapf(
|
||||
err, "cannot compile %s:\nSTDERR\n%s\n", srcFile, stderr.String())
|
||||
}
|
||||
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
|
||||
}
|
||||
67
api/internal/plugins/compiler/compiler_test.go
Normal file
67
api/internal/plugins/compiler/compiler_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package compiler_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filesys"
|
||||
. "sigs.k8s.io/kustomize/api/internal/plugins/compiler"
|
||||
)
|
||||
|
||||
// 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(filesys.MakeFsOnDisk())
|
||||
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", "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)
|
||||
}
|
||||
|
||||
expectObj = filepath.Join(
|
||||
c.ObjRoot(),
|
||||
"builtin", "", "secretgenerator", "SecretGenerator.so")
|
||||
if FileExists(expectObj) {
|
||||
t.Errorf("obj file should not exist yet: %s", expectObj)
|
||||
}
|
||||
err = c.Compile("builtin", "", "SecretGenerator")
|
||||
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)
|
||||
}
|
||||
}
|
||||
118
api/internal/plugins/doc.go
Normal file
118
api/internal/plugins/doc.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/*
|
||||
|
||||
Read docs/plugins.md first for an overview of kustomize plugins.
|
||||
|
||||
|
||||
BUILTIN PLUGIN CONFIGURATION
|
||||
|
||||
There are two kinds of plugins, Go plugins (shared
|
||||
object library) and exec plugins (independent binary).
|
||||
For performance and standardized testing reasons, all
|
||||
builtin 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.
|
||||
|
||||
The source for a builtin plugin must be at:
|
||||
|
||||
repo=$GOPATH/src/sigs.k8s.io/kustomize
|
||||
${repo}/plugin/builtin/LOWERCASE(${kind})/${kind}
|
||||
|
||||
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).
|
||||
|
||||
|
||||
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 building
|
||||
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 ./...
|
||||
|
||||
See travis/pre-commit.sh for canonical way
|
||||
to execute the above.
|
||||
|
||||
This creates
|
||||
|
||||
$repo/api/plugins/builtins/SecretGenerator.go
|
||||
|
||||
etc.
|
||||
|
||||
Generated plugins are used in kustomize via
|
||||
|
||||
package whatever
|
||||
import sigs.k8s.io/kustomize/api/plugins/builtins
|
||||
...
|
||||
g := builtin.NewSecretGenerator()
|
||||
g.Config(h, k)
|
||||
resources, err := g.Generate()
|
||||
err = g.Transform(resources)
|
||||
// Eventually emit resources.
|
||||
|
||||
*/
|
||||
package plugins
|
||||
270
api/internal/plugins/execplugin/execplugin.go
Normal file
270
api/internal/plugins/execplugin/execplugin.go
Normal file
@@ -0,0 +1,270 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package execplugin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
const (
|
||||
idAnnotation = "kustomize.config.k8s.io/id"
|
||||
HashAnnotation = "kustomize.config.k8s.io/needs-hash"
|
||||
BehaviorAnnotation = "kustomize.config.k8s.io/behavior"
|
||||
tmpConfigFilePrefix = "kust-plugin-config-"
|
||||
)
|
||||
|
||||
// ExecPlugin record the name and args of an executable
|
||||
// It triggers the executable generator and transformer
|
||||
type ExecPlugin struct {
|
||||
// absolute path of the executable
|
||||
path string
|
||||
|
||||
// Optional command line arguments to the executable
|
||||
// pulled from specially named fields in cfg.
|
||||
// This is for executables that don't want to parse YAML.
|
||||
args []string
|
||||
|
||||
// Plugin configuration data.
|
||||
cfg []byte
|
||||
|
||||
// PluginHelpers
|
||||
h *resmap.PluginHelpers
|
||||
}
|
||||
|
||||
func NewExecPlugin(p string) *ExecPlugin {
|
||||
return &ExecPlugin{path: p}
|
||||
}
|
||||
|
||||
func (p *ExecPlugin) ErrIfNotExecutable() error {
|
||||
f, err := os.Stat(p.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f.Mode()&0111 == 0000 {
|
||||
return fmt.Errorf("unexecutable plugin at: %s", p.path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ExecPlugin) Path() string {
|
||||
return p.path
|
||||
}
|
||||
|
||||
func (p *ExecPlugin) Args() []string {
|
||||
return p.args
|
||||
}
|
||||
|
||||
func (p *ExecPlugin) Cfg() []byte {
|
||||
return p.cfg
|
||||
}
|
||||
|
||||
func (p *ExecPlugin) Config(h *resmap.PluginHelpers, config []byte) error {
|
||||
p.h = h
|
||||
p.cfg = config
|
||||
return p.processOptionalArgsFields()
|
||||
}
|
||||
|
||||
type argsConfig struct {
|
||||
ArgsOneLiner string `json:"argsOneLiner,omitempty" yaml:"argsOneLiner,omitempty"`
|
||||
ArgsFromFile string `json:"argsFromFile,omitempty" yaml:"argsFromFile,omitempty"`
|
||||
}
|
||||
|
||||
func (p *ExecPlugin) processOptionalArgsFields() error {
|
||||
var c argsConfig
|
||||
yaml.Unmarshal(p.cfg, &c)
|
||||
if c.ArgsOneLiner != "" {
|
||||
p.args = strings.Split(c.ArgsOneLiner, " ")
|
||||
}
|
||||
if c.ArgsFromFile != "" {
|
||||
content, err := p.h.Loader().Load(c.ArgsFromFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, x := range strings.Split(string(content), "\n") {
|
||||
x := strings.TrimLeft(x, " ")
|
||||
if x != "" {
|
||||
p.args = append(p.args, x)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ExecPlugin) Generate() (resmap.ResMap, error) {
|
||||
output, err := p.invokePlugin(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rm, err := p.h.ResmapFactory().NewResMapFromBytes(output)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.UpdateResourceOptions(rm)
|
||||
}
|
||||
|
||||
func (p *ExecPlugin) Transform(rm resmap.ResMap) error {
|
||||
// add ResIds as annotations to all objects so that we can add them back
|
||||
inputRM, err := p.getResMapWithIdAnnotation(rm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// encode the ResMap so it can be fed to the plugin
|
||||
resources, err := inputRM.AsYaml()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// invoke the plugin with resources as the input
|
||||
output, err := p.invokePlugin(resources)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v %s", err, string(output))
|
||||
}
|
||||
|
||||
// update the original ResMap based on the output
|
||||
return p.updateResMapValues(output, rm)
|
||||
}
|
||||
|
||||
// invokePlugin writes plugin config to a temp file, then
|
||||
// passes the full temp file path as the first arg to a process
|
||||
// running the plugin binary. Process output is returned.
|
||||
func (p *ExecPlugin) invokePlugin(input []byte) ([]byte, error) {
|
||||
f, err := ioutil.TempFile("", tmpConfigFilePrefix)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(
|
||||
err, "creating tmp plugin config file")
|
||||
}
|
||||
_, err = f.Write(p.cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(
|
||||
err, "writing plugin config to "+f.Name())
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(
|
||||
err, "closing plugin config file "+f.Name())
|
||||
}
|
||||
cmd := exec.Command(
|
||||
p.path, append([]string{f.Name()}, p.args...)...)
|
||||
cmd.Env = p.getEnv()
|
||||
cmd.Stdin = bytes.NewReader(input)
|
||||
cmd.Stderr = os.Stderr
|
||||
if _, err := os.Stat(p.h.Loader().Root()); err == nil {
|
||||
cmd.Dir = p.h.Loader().Root()
|
||||
}
|
||||
result, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(
|
||||
err, "failure in plugin configured via %s; %v",
|
||||
f.Name(), err.Error())
|
||||
}
|
||||
return result, os.Remove(f.Name())
|
||||
}
|
||||
|
||||
func (p *ExecPlugin) getEnv() []string {
|
||||
env := os.Environ()
|
||||
env = append(env,
|
||||
"KUSTOMIZE_PLUGIN_CONFIG_STRING="+string(p.cfg),
|
||||
"KUSTOMIZE_PLUGIN_CONFIG_ROOT="+p.h.Loader().Root())
|
||||
return env
|
||||
}
|
||||
|
||||
// Returns a new copy of the given ResMap with the ResIds annotated in each Resource
|
||||
func (p *ExecPlugin) getResMapWithIdAnnotation(rm resmap.ResMap) (resmap.ResMap, error) {
|
||||
inputRM := rm.DeepCopy()
|
||||
for _, r := range inputRM.Resources() {
|
||||
idString, err := yaml.Marshal(r.CurId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
annotations := r.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
annotations[idAnnotation] = string(idString)
|
||||
r.SetAnnotations(annotations)
|
||||
}
|
||||
return inputRM, nil
|
||||
}
|
||||
|
||||
// updateResMapValues updates the Resource value in the given ResMap
|
||||
// with the emitted Resource values in output.
|
||||
func (p *ExecPlugin) updateResMapValues(output []byte, rm resmap.ResMap) error {
|
||||
outputRM, err := p.h.ResmapFactory().NewResMapFromBytes(output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range outputRM.Resources() {
|
||||
// for each emitted Resource, find the matching Resource in the original ResMap
|
||||
// using its id
|
||||
annotations := r.GetAnnotations()
|
||||
idString, ok := annotations[idAnnotation]
|
||||
if !ok {
|
||||
return fmt.Errorf("the transformer %s should not remove annotation %s",
|
||||
p.path, idAnnotation)
|
||||
}
|
||||
id := resid.ResId{}
|
||||
err := yaml.Unmarshal([]byte(idString), &id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := rm.GetByCurrentId(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to find unique match to %s", id.String())
|
||||
}
|
||||
// remove the annotation set by Kustomize to track the resource
|
||||
delete(annotations, idAnnotation)
|
||||
if len(annotations) == 0 {
|
||||
annotations = nil
|
||||
}
|
||||
r.SetAnnotations(annotations)
|
||||
|
||||
// update the ResMap resource value with the transformed object
|
||||
res.Kunstructured = r.Kunstructured
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateResourceOptions updates the generator options for each resource in the
|
||||
// given ResMap based on plugin provided annotations.
|
||||
func (p *ExecPlugin) UpdateResourceOptions(rm resmap.ResMap) (resmap.ResMap, error) {
|
||||
for _, r := range rm.Resources() {
|
||||
// Disable name hashing by default and require plugin to explicitly
|
||||
// request it for each resource.
|
||||
annotations := r.GetAnnotations()
|
||||
behavior := annotations[BehaviorAnnotation]
|
||||
var needsHash bool
|
||||
if val, ok := annotations[HashAnnotation]; ok {
|
||||
b, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"the annotation %q contains an invalid value (%q)",
|
||||
HashAnnotation, val)
|
||||
}
|
||||
needsHash = b
|
||||
}
|
||||
delete(annotations, HashAnnotation)
|
||||
delete(annotations, BehaviorAnnotation)
|
||||
if len(annotations) == 0 {
|
||||
annotations = nil
|
||||
}
|
||||
r.SetAnnotations(annotations)
|
||||
r.SetOptions(types.NewGenArgs(
|
||||
&types.GeneratorArgs{Behavior: behavior},
|
||||
&types.GeneratorOptions{DisableNameSuffixHash: !needsHash}))
|
||||
}
|
||||
return rm, nil
|
||||
}
|
||||
190
api/internal/plugins/execplugin/execplugin_test.go
Normal file
190
api/internal/plugins/execplugin/execplugin_test.go
Normal file
@@ -0,0 +1,190 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package execplugin_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/loadertest"
|
||||
. "sigs.k8s.io/kustomize/api/internal/plugins/execplugin"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
valtest_test "sigs.k8s.io/kustomize/api/testutils/valtest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func TestExecPluginConfig(t *testing.T) {
|
||||
path := "/app"
|
||||
rf := resmap.NewFactory(
|
||||
resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl()), nil)
|
||||
ldr := loadertest.NewFakeLoader(path)
|
||||
v := valtest_test.MakeFakeValidator()
|
||||
pluginConfig := rf.RF().FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "someteam.example.com/v1",
|
||||
"kind": "SedTransformer",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "some-random-name",
|
||||
},
|
||||
"argsOneLiner": "one two",
|
||||
"argsFromFile": "sed-input.txt",
|
||||
})
|
||||
|
||||
ldr.AddFile("/app/sed-input.txt", []byte(`
|
||||
s/$FOO/foo/g
|
||||
s/$BAR/bar/g
|
||||
\ \ \
|
||||
`))
|
||||
|
||||
p := NewExecPlugin(
|
||||
loader.AbsolutePluginPath(
|
||||
konfig.DisabledPluginConfig(),
|
||||
pluginConfig.OrgId()))
|
||||
// Not checking to see if the plugin is executable,
|
||||
// because this test does not run it.
|
||||
// This tests only covers sending configuration
|
||||
// to the plugin wrapper object and confirming
|
||||
// that it's properly prepared for execution.
|
||||
|
||||
yaml, err := pluginConfig.AsYAML()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
p.Config(resmap.NewPluginHelpers(ldr, v, rf), yaml)
|
||||
|
||||
expected := "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
|
||||
argsFromFile: sed-input.txt
|
||||
argsOneLiner: one two
|
||||
kind: SedTransformer
|
||||
metadata:
|
||||
name: some-random-name
|
||||
`
|
||||
if expected != string(p.Cfg()) {
|
||||
t.Fatalf("expected cfg '%s', got '%s'", expected, string(p.Cfg()))
|
||||
|
||||
}
|
||||
if len(p.Args()) != 5 {
|
||||
t.Fatalf("unexpected arg len %d, %v", len(p.Args()), p.Args())
|
||||
}
|
||||
if p.Args()[0] != "one" ||
|
||||
p.Args()[1] != "two" ||
|
||||
p.Args()[2] != "s/$FOO/foo/g" ||
|
||||
p.Args()[3] != "s/$BAR/bar/g" ||
|
||||
p.Args()[4] != "\\ \\ \\ " {
|
||||
t.Fatalf("unexpected arg array: %v", p.Args())
|
||||
}
|
||||
}
|
||||
|
||||
func makeConfigMap(rf *resource.Factory, name, behavior string, hashValue *string) *resource.Resource {
|
||||
r := rf.FromMap(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{"name": name},
|
||||
})
|
||||
annotations := map[string]string{}
|
||||
if behavior != "" {
|
||||
annotations[BehaviorAnnotation] = behavior
|
||||
}
|
||||
if hashValue != nil {
|
||||
annotations[HashAnnotation] = *hashValue
|
||||
}
|
||||
if len(annotations) > 0 {
|
||||
r.SetAnnotations(annotations)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func makeConfigMapOptions(rf *resource.Factory, name, behavior string, disableHash bool) *resource.Resource {
|
||||
return rf.FromMapAndOption(map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{"name": name},
|
||||
}, &types.GeneratorArgs{Behavior: behavior}, &types.GeneratorOptions{DisableNameSuffixHash: disableHash})
|
||||
}
|
||||
|
||||
func strptr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func TestUpdateResourceOptions(t *testing.T) {
|
||||
p := NewExecPlugin("")
|
||||
if err := p.ErrIfNotExecutable(); err == nil {
|
||||
t.Fatalf("expected unexecutable error")
|
||||
}
|
||||
rf := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())
|
||||
in := resmap.New()
|
||||
expected := resmap.New()
|
||||
cases := []struct {
|
||||
behavior string
|
||||
needsHash bool
|
||||
hashValue *string
|
||||
}{
|
||||
{hashValue: strptr("false")},
|
||||
{hashValue: strptr("true"), needsHash: true},
|
||||
{behavior: "replace"},
|
||||
{behavior: "merge"},
|
||||
{behavior: "create"},
|
||||
{behavior: "nonsense"},
|
||||
{behavior: "merge", hashValue: strptr("false")},
|
||||
{behavior: "merge", hashValue: strptr("true"), needsHash: true},
|
||||
}
|
||||
for i, c := range cases {
|
||||
name := fmt.Sprintf("test%d", i)
|
||||
in.Append(makeConfigMap(rf, name, c.behavior, c.hashValue))
|
||||
expected.Append(makeConfigMapOptions(rf, name, c.behavior, !c.needsHash))
|
||||
}
|
||||
actual, err := p.UpdateResourceOptions(in)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err.Error())
|
||||
}
|
||||
for i, a := range expected.Resources() {
|
||||
b := actual.GetByIndex(i)
|
||||
if b == nil {
|
||||
t.Fatalf("resource %d missing from processed map", i)
|
||||
}
|
||||
if !a.Equals(b) {
|
||||
t.Errorf("expected %v got %v", a, b)
|
||||
}
|
||||
if a.NeedHashSuffix() != b.NeedHashSuffix() {
|
||||
t.Errorf("")
|
||||
}
|
||||
if a.Behavior() != b.Behavior() {
|
||||
t.Errorf("expected %v got %v", a.Behavior(), b.Behavior())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateResourceOptionsWithInvalidHashAnnotationValues(t *testing.T) {
|
||||
p := NewExecPlugin("")
|
||||
if err := p.ErrIfNotExecutable(); err == nil {
|
||||
t.Fatalf("expected unexecutable error")
|
||||
}
|
||||
rf := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl())
|
||||
cases := []string{
|
||||
"",
|
||||
"FaLsE",
|
||||
"TrUe",
|
||||
"potato",
|
||||
}
|
||||
for i, c := range cases {
|
||||
name := fmt.Sprintf("test%d", i)
|
||||
in := resmap.New()
|
||||
in.Append(makeConfigMap(rf, name, "", &c))
|
||||
_, err := p.UpdateResourceOptions(in)
|
||||
if err == nil {
|
||||
t.Errorf("expected error from value %q", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
211
api/internal/plugins/loader/loader.go
Normal file
211
api/internal/plugins/loader/loader.go
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"plugin"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"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/konfig"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
type Loader struct {
|
||||
pc *types.PluginConfig
|
||||
rf *resmap.Factory
|
||||
}
|
||||
|
||||
func NewLoader(
|
||||
pc *types.PluginConfig, rf *resmap.Factory) *Loader {
|
||||
return &Loader{pc: pc, rf: rf}
|
||||
}
|
||||
|
||||
func (l *Loader) LoadGenerators(
|
||||
ldr ifc.Loader, v ifc.Validator, rm resmap.ResMap) ([]resmap.Generator, error) {
|
||||
var result []resmap.Generator
|
||||
for _, res := range rm.Resources() {
|
||||
g, err := l.LoadGenerator(ldr, v, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, g)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (l *Loader) LoadGenerator(
|
||||
ldr ifc.Loader, v ifc.Validator, res *resource.Resource) (resmap.Generator, error) {
|
||||
c, err := l.loadAndConfigurePlugin(ldr, v, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g, ok := c.(resmap.Generator)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("plugin %s not a generator", res.OrgId())
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (l *Loader) LoadTransformers(
|
||||
ldr ifc.Loader, v ifc.Validator, rm resmap.ResMap) ([]resmap.Transformer, error) {
|
||||
var result []resmap.Transformer
|
||||
for _, res := range rm.Resources() {
|
||||
t, err := l.LoadTransformer(ldr, v, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, t)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (l *Loader) LoadTransformer(
|
||||
ldr ifc.Loader, v ifc.Validator, res *resource.Resource) (resmap.Transformer, error) {
|
||||
c, err := l.loadAndConfigurePlugin(ldr, v, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t, ok := c.(resmap.Transformer)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("plugin %s not a transformer", res.OrgId())
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func relativePluginPath(id resid.ResId) string {
|
||||
return filepath.Join(
|
||||
id.Group,
|
||||
id.Version,
|
||||
strings.ToLower(id.Kind))
|
||||
}
|
||||
|
||||
func AbsolutePluginPath(pc *types.PluginConfig, id resid.ResId) string {
|
||||
return filepath.Join(
|
||||
pc.AbsPluginHome, relativePluginPath(id), id.Kind)
|
||||
}
|
||||
|
||||
func (l *Loader) absolutePluginPath(id resid.ResId) string {
|
||||
return AbsolutePluginPath(l.pc, id)
|
||||
}
|
||||
|
||||
func isBuiltinPlugin(res *resource.Resource) bool {
|
||||
// TODO: the special string should appear in Group, not Version.
|
||||
return res.GetGvk().Group == "" &&
|
||||
res.GetGvk().Version == konfig.BuiltinPluginApiVersion
|
||||
}
|
||||
|
||||
func (l *Loader) loadAndConfigurePlugin(
|
||||
ldr ifc.Loader, v ifc.Validator, res *resource.Resource) (c resmap.Configurable, err error) {
|
||||
if isBuiltinPlugin(res) {
|
||||
// Instead of looking for and loading a .so file, just
|
||||
// instantiate the plugin from a generated factory
|
||||
// function (see "pluginator"). Being able to do this
|
||||
// is what makes a plugin "builtin".
|
||||
c, err = l.makeBuiltinPlugin(res.GetGvk())
|
||||
} else if l.pc.PluginRestrictions == types.PluginRestrictionsNone {
|
||||
c, err = l.loadPlugin(res.OrgId())
|
||||
} else {
|
||||
err = types.NewErrOnlyBuiltinPluginsAllowed(res.OrgId().Kind)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
yaml, err := res.AsYAML()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "marshalling yaml from res %s", res.OrgId())
|
||||
}
|
||||
err = c.Config(resmap.NewPluginHelpers(ldr, v, l.rf), yaml)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(
|
||||
err, "plugin %s fails configuration", res.OrgId())
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (l *Loader) makeBuiltinPlugin(r resid.Gvk) (resmap.Configurable, error) {
|
||||
bpt := builtinhelpers.GetBuiltinPluginType(r.Kind)
|
||||
if f, ok := builtinhelpers.GeneratorFactories[bpt]; ok {
|
||||
return f(), nil
|
||||
}
|
||||
if f, ok := builtinhelpers.TransformerFactories[bpt]; ok {
|
||||
return f(), nil
|
||||
}
|
||||
return nil, errors.Errorf("unable to load builtin %s", r)
|
||||
}
|
||||
|
||||
func (l *Loader) loadPlugin(resId resid.ResId) (resmap.Configurable, error) {
|
||||
// First try to load the plugin as an executable.
|
||||
p := execplugin.NewExecPlugin(l.absolutePluginPath(resId))
|
||||
err := p.ErrIfNotExecutable()
|
||||
if err == nil {
|
||||
return p, nil
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
// The file exists, but something else is wrong,
|
||||
// likely it's not executable.
|
||||
// Assume the user forgot to set the exec bit,
|
||||
// and return an error, rather than adding ".so"
|
||||
// to the name and attempting to load it as a Go
|
||||
// plugin, which will likely fail and result
|
||||
// in an obscure message.
|
||||
return nil, err
|
||||
}
|
||||
// Failing the above, try loading it as a Go plugin.
|
||||
c, err := l.loadGoPlugin(resId)
|
||||
if err != nil {
|
||||
return nil, 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) (resmap.Configurable, error) {
|
||||
regId := relativePluginPath(id)
|
||||
if c, ok := registry[regId]; ok {
|
||||
return copyPlugin(c), nil
|
||||
}
|
||||
absPath := l.absolutePluginPath(id)
|
||||
p, err := plugin.Open(absPath + ".so")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "plugin %s fails to load", absPath)
|
||||
}
|
||||
symbol, err := p.Lookup(konfig.PluginSymbol)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(
|
||||
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)
|
||||
}
|
||||
80
api/internal/plugins/loader/loader_test.go
Normal file
80
api/internal/plugins/loader/loader_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package loader_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/loadertest"
|
||||
. "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/konfig"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||
valtest_test "sigs.k8s.io/kustomize/api/testutils/valtest"
|
||||
)
|
||||
|
||||
const (
|
||||
secretGenerator = `
|
||||
apiVersion: builtin
|
||||
kind: SecretGenerator
|
||||
metadata:
|
||||
name: secretGenerator
|
||||
name: mySecret
|
||||
behavior: merge
|
||||
envFiles:
|
||||
- a.env
|
||||
- b.env
|
||||
valueFiles:
|
||||
- longsecret.txt
|
||||
literals:
|
||||
- FRUIT=apple
|
||||
- VEGETABLE=carrot
|
||||
`
|
||||
someServiceGenerator = `
|
||||
apiVersion: someteam.example.com/v1
|
||||
kind: SomeServiceGenerator
|
||||
metadata:
|
||||
name: myServiceGenerator
|
||||
service: my-service
|
||||
port: "12345"
|
||||
`
|
||||
)
|
||||
|
||||
func TestLoader(t *testing.T) {
|
||||
tc := kusttest_test.NewPluginTestEnv(t).Set()
|
||||
defer tc.Reset()
|
||||
|
||||
tc.BuildGoPlugin(
|
||||
"builtin", "", "SecretGenerator")
|
||||
tc.BuildGoPlugin(
|
||||
"someteam.example.com", "v1", "SomeServiceGenerator")
|
||||
|
||||
rmF := resmap.NewFactory(resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl()), nil)
|
||||
|
||||
ldr := loadertest.NewFakeLoader("/foo")
|
||||
|
||||
c, err := konfig.EnabledPluginConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pLdr := NewLoader(c, rmF)
|
||||
if pLdr == nil {
|
||||
t.Fatal("expect non-nil loader")
|
||||
}
|
||||
|
||||
m, err := rmF.NewResMapFromBytes([]byte(
|
||||
someServiceGenerator + "---\n" + secretGenerator))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = pLdr.LoadGenerators(ldr, valtest_test.MakeFakeValidator(), m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user