Convert inventory transformer to plugin, reduce k8sdeps.

This commit is contained in:
Jeffrey Regan
2019-05-31 13:46:35 -07:00
committed by jregan
parent 115a0bc560
commit 81c98c855f
14 changed files with 491 additions and 328 deletions

3
go.mod
View File

@@ -27,8 +27,7 @@ require (
github.com/spf13/cobra v0.0.2
github.com/spf13/pflag v1.0.1
github.com/stretchr/testify v1.3.0 // indirect
golang.org/x/net v0.0.0-20190225153610-fe579d43d832 // indirect
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 // indirect
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.1
k8s.io/api v0.0.0-20180510062335-53d615ae3f44

7
go.sum
View File

@@ -58,16 +58,23 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190225153610-fe579d43d832 h1:2IdId8zoI92l1bUzjAOygcAOkmCe13HY1j0rqPPPzB8=
golang.org/x/net v0.0.0-20190225153610-fe579d43d832/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d h1:bt+R27hbE7uVf7PY9S6wpNg9Xo2WRe/XQT0uGq9RQQw=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=

View File

@@ -5,12 +5,9 @@
package transformer
import (
"sigs.k8s.io/kustomize/k8sdeps/transformer/inventory"
"sigs.k8s.io/kustomize/k8sdeps/transformer/patch"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/transformers"
"sigs.k8s.io/kustomize/pkg/types"
)
// FactoryImpl makes patch transformer and name hash transformer
@@ -27,11 +24,3 @@ func (p *FactoryImpl) MakePatchTransformer(
rf *resource.Factory) (transformers.Transformer, error) {
return patch.NewTransformer(slice, rf)
}
func (p *FactoryImpl) MakeInventoryTransformer(
arg *types.Inventory,
ldr ifc.Loader,
namespace string,
gp types.GarbagePolicy) transformers.Transformer {
return inventory.NewTransformer(arg, ldr, namespace, gp)
}

View File

@@ -1,113 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package inventory
import (
"fmt"
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/hasher"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/inventory"
"sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/transformers"
"sigs.k8s.io/kustomize/pkg/types"
)
// transformer compute the inventory object used in prune
type transformer struct {
garbagePolicy types.GarbagePolicy
ldr ifc.Loader
cmName string
cmNamespace string
}
var _ transformers.Transformer = &transformer{}
// NewTransformer makes a new inventory transformer.
func NewTransformer(
p *types.Inventory,
ldr ifc.Loader,
namespace string,
gp types.GarbagePolicy) transformers.Transformer {
if p == nil || p.Type != "ConfigMap" || p.ConfigMap.Namespace != namespace {
return transformers.NewNoOpTransformer()
}
return &transformer{
garbagePolicy: gp,
ldr: ldr,
cmName: p.ConfigMap.Name,
cmNamespace: p.ConfigMap.Namespace,
}
}
// Transform generates an inventory object based on the input ResMap.
// this transformer doesn't change existing resources -
// it just visits resources and accumulates information to make a new ConfigMap.
// The prune ConfigMap is used to support the pruning command in the client side tool,
// which is proposed in https://github.com/kubernetes/enhancements/pull/810
// The inventory data is written to annotation since
// 1. The key in data field is constrained and couldn't include arbitrary letters
// 2. The annotation can be put into any kind of objects
func (tf *transformer) Transform(m resmap.ResMap) error {
invty := inventory.NewInventory()
var keys []string
for _, r := range m {
ns, _ := r.GetFieldValue("metadata.namespace")
item := resid.NewItemId(r.GetGvk(), ns, r.GetName())
var refs []resid.ItemId
for _, refid := range r.GetRefBy() {
ref := m[refid]
ns, _ := ref.GetFieldValue("metadata.namespace")
refs = append(refs, resid.NewItemId(ref.GetGvk(), ns, ref.GetName()))
}
invty.Current[item] = refs
keys = append(keys, item.String())
}
h, err := hasher.SortArrayAndComputeHash(keys)
if err != nil {
return err
}
args := &types.ConfigMapArgs{}
args.Name = tf.cmName
args.Namespace = tf.cmNamespace
opts := &types.GeneratorOptions{
Annotations: make(map[string]string),
}
opts.Annotations[inventory.HashAnnotation] = h
err = invty.UpdateAnnotations(opts.Annotations)
if err != nil {
return err
}
kf := kunstruct.NewKunstructuredFactoryImpl()
k, err := kf.MakeConfigMap(tf.ldr, opts, args)
if err != nil {
return err
}
if tf.garbagePolicy == types.GarbageCollect {
for k := range m {
delete(m, k)
}
}
id := resid.NewResIdWithPrefixNamespace(
gvk.Gvk{
Version: "v1",
Kind: "ConfigMap",
},
tf.cmName,
"", tf.cmNamespace)
if _, ok := m[id]; ok {
return fmt.Errorf("id %v is already used, please use a different name in the prune field", id)
}
m[id] = resource.NewFactory(kf).FromKunstructured(k)
return nil
}

View File

@@ -1,161 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package inventory
import (
"reflect"
"testing"
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/loader"
"sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/types"
"sigs.k8s.io/kustomize/pkg/validators"
)
var secret = gvk.Gvk{Version: "v1", Kind: "Secret"}
var cmap = gvk.Gvk{Version: "v1", Kind: "ConfigMap"}
var deploy = gvk.Gvk{Group: "apps", Version: "v1", Kind: "Deployment"}
func makeResMap() resmap.ResMap {
rf := resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())
objs := resmap.ResMap{
resid.NewResId(cmap, "cm1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm1",
},
}),
resid.NewResId(secret, "secret1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "Secret",
"metadata": map[string]interface{}{
"name": "secret1",
},
}),
resid.NewResId(deploy, "deploy1"): rf.FromMap(
map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:1.7.9",
"env": []interface{}{
map[string]interface{}{
"name": "CM_FOO",
"valueFrom": map[string]interface{}{
"configMapKeyRef": map[string]interface{}{
"name": "cm1",
"key": "somekey",
},
},
},
},
"envFrom": []interface{}{
map[string]interface{}{
"configMapRef": map[string]interface{}{
"name": "cm1",
"key": "somekey",
},
},
map[string]interface{}{
"secretRef": map[string]interface{}{
"name": "secret1",
"key": "somekey",
},
},
},
},
},
},
},
},
}),
}
objs[resid.NewResId(cmap, "cm1")].AppendRefBy(resid.NewResId(deploy, "deploy1"))
objs[resid.NewResId(secret, "secret1")].AppendRefBy(resid.NewResId(deploy, "deploy1"))
return objs
}
func TestInventoryTransformer(t *testing.T) {
rf := resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())
ldr := loader.NewFileLoaderAtCwd(validators.MakeFakeValidator(), fs.MakeFakeFS())
// hash is derived based on all keys in the Inventory
// It is added to annotations as
// kustomize.config.k8s.io/InventoryHash: hash
// When seeing the same annotation, prune binary assumes no
// clean up is needed
hash := "h44788gt7g"
// inventory is the derived json string for an Inventory object
// It is added to annotations as
// kustomize.config.k8s.io/Inventory: inventory
inventory := "{\"current\":{\"apps_v1_Deployment|~X|deploy1\":null,\"~G_v1_ConfigMap|~X|cm1\":[{\"group\":\"apps\",\"version\":\"v1\",\"kind\":\"Deployment\",\"name\":\"deploy1\"}],\"~G_v1_Secret|~X|secret1\":[{\"group\":\"apps\",\"version\":\"v1\",\"kind\":\"Deployment\",\"name\":\"deploy1\"}]}}" // nolint
// This is the root or inventory object which tracks all
// the applied resources - this is the thing we expect the transformer to create.
pruneMap := rf.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "pruneCM",
"namespace": "default",
"annotations": map[string]interface{}{
"kustomize.config.k8s.io/Inventory": inventory,
"kustomize.config.k8s.io/InventoryHash": hash,
},
},
})
expected := resmap.ResMap{
resid.NewResIdWithPrefixNamespace(cmap, "pruneCM", "", "default"): pruneMap,
}
p := &types.Inventory{
Type: "ConfigMap",
ConfigMap: types.NameArgs{
Name: "pruneCM",
Namespace: "default",
},
}
objs := makeResMap()
// include the original resmap; only return the ConfigMap for pruning
tran := NewTransformer(p, ldr, "default", types.GarbageCollect)
tran.Transform(objs)
if !reflect.DeepEqual(objs, expected) {
err := expected.ErrorIfNotEqual(objs)
t.Fatalf("actual doesn't match expected: %v", err)
}
objs = makeResMap()
expected = objs.DeepCopy(rf)
expected[resid.NewResIdWithPrefixNamespace(cmap, "pruneCM", "", "default")] = pruneMap
// append the ConfigMap for pruning to the original resmap
tran = NewTransformer(p, ldr, "default", types.GarbageIgnore)
tran.Transform(objs)
if !reflect.DeepEqual(objs, expected) {
err := expected.ErrorIfNotEqual(objs)
t.Fatalf("actual doesn't match expected: %v", err)
}
}

View File

@@ -1,18 +1,5 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package accumulator
@@ -29,7 +16,8 @@ import (
)
// ResAccumulator accumulates resources and the rules
// used to customize those resources.
// used to customize those resources. It's a ResMap
// plus stuff needed to modify the ResMap.
type ResAccumulator struct {
resMap resmap.ResMap
tConfig *config.TransformerConfig

View File

@@ -5,18 +5,13 @@
package transformer
import (
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/transformers"
"sigs.k8s.io/kustomize/pkg/types"
)
// Factory makes transformers
// Factory makes transformers that require k8sdeps.
type Factory interface {
MakePatchTransformer(slice []*resource.Resource, rf *resource.Factory) (transformers.Transformer, error)
MakeInventoryTransformer(
p *types.Inventory,
ldr ifc.Loader,
namespace string,
gp types.GarbagePolicy) transformers.Transformer
MakePatchTransformer(
slice []*resource.Resource,
rf *resource.Factory) (transformers.Transformer, error)
}

View File

@@ -127,14 +127,11 @@ func (kt *KustTarget) makeCustomizedResMap(
if err != nil {
return nil, err
}
// This must be done last, and not as part of
// The following steps must be done last, not as part of
// the recursion implicit in AccumulateTarget.
p := builtin.NewHashTransformerPlugin()
err = kt.configureBuiltinPlugin(p, nil, "hash")
if err != nil {
return nil, err
}
err = ra.Transform(p)
err = kt.addHashesToNames(ra)
if err != nil {
return nil, err
}
@@ -145,22 +142,60 @@ func (kt *KustTarget) makeCustomizedResMap(
if err != nil {
return nil, err
}
// With all the back references fixed, it's OK to resolve Vars.
err = ra.ResolveVars()
if err != nil {
return nil, err
}
rm := ra.ResMap()
pt := kt.tFactory.MakeInventoryTransformer(
kt.kustomization.Inventory, kt.ldr,
kt.kustomization.Namespace,
garbagePolicy)
err = pt.Transform(rm)
err = kt.computeInventory(ra, garbagePolicy)
if err != nil {
return nil, err
}
return rm, nil
return ra.ResMap(), nil
}
func (kt *KustTarget) addHashesToNames(
ra *accumulator.ResAccumulator) error {
p := builtin.NewHashTransformerPlugin()
err := kt.configureBuiltinPlugin(p, nil, "hash")
if err != nil {
return err
}
return ra.Transform(p)
}
func (kt *KustTarget) computeInventory(
ra *accumulator.ResAccumulator, garbagePolicy types.GarbagePolicy) error {
inv := kt.kustomization.Inventory
if inv == nil {
return nil
}
if inv.Type != "ConfigMap" {
return fmt.Errorf("don't know how to do that")
}
if inv.ConfigMap.Namespace != kt.kustomization.Namespace {
return fmt.Errorf("namespace mismatch")
}
p := builtin.NewInventoryTransformerPlugin()
var c struct {
Policy string
Name string
Namespace string
}
c.Name = inv.ConfigMap.Name
c.Namespace = inv.ConfigMap.Namespace
c.Policy = garbagePolicy.String()
err := kt.configureBuiltinPlugin(p, c, "inventory")
if err != nil {
return err
}
return ra.Transform(p)
}
func (kt *KustTarget) shouldAddHashSuffixesToGeneratedResources() bool {

View File

@@ -0,0 +1,25 @@
// Code generated by "stringer -type=GarbagePolicy"; DO NOT EDIT.
package types
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[GarbageIgnore-1]
_ = x[GarbageCollect-2]
}
const _GarbagePolicy_name = "GarbageIgnoreGarbageCollect"
var _GarbagePolicy_index = [...]uint8{0, 13, 27}
func (i GarbagePolicy) String() string {
i -= 1
if i < 0 || i >= GarbagePolicy(len(_GarbagePolicy_index)-1) {
return "GarbagePolicy(" + strconv.FormatInt(int64(i+1), 10) + ")"
}
return _GarbagePolicy_name[_GarbagePolicy_index[i]:_GarbagePolicy_index[i+1]]
}

View File

@@ -146,11 +146,11 @@ type Kustomization struct {
Inventory *Inventory `json:"inventory,omitempty" yaml:"inventory:omitempty"`
}
//go:generate stringer -type=GarbagePolicy
type GarbagePolicy int
const (
GarbageUnknown GarbagePolicy = iota
GarbageIgnore
GarbageIgnore GarbagePolicy = iota + 1
GarbageCollect
)

View File

@@ -3,6 +3,7 @@ package builtin
import (
"fmt"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/resmap"
)

View File

@@ -0,0 +1,136 @@
// Code generated by pluginator on InventoryTransformer; DO NOT EDIT.
package builtin
import (
"fmt"
"strings"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/hasher"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/inventory"
"sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/types"
"sigs.k8s.io/yaml"
)
type InventoryTransformerPlugin struct {
ldr ifc.Loader
rf *resmap.Factory
Policy string `json:"policy,omitempty" yaml:"policy,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
}
func NewInventoryTransformerPlugin() *InventoryTransformerPlugin {
return &InventoryTransformerPlugin{}
}
func (p *InventoryTransformerPlugin) Config(
ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) {
p.ldr = ldr
p.rf = rf
err = yaml.Unmarshal(c, p)
if err != nil {
return err
}
if p.Policy == "" {
p.Policy = types.GarbageIgnore.String()
}
if p.Policy != types.GarbageCollect.String() &&
p.Policy != types.GarbageIgnore.String() {
return fmt.Errorf(
"unrecognized garbagePolicy '%s'", p.Policy)
}
return nil
}
// Transform generates an inventory object from the input ResMap.
// This ConfigMap supports the pruning command in
// the client side tool proposed here:
// https://github.com/kubernetes/enhancements/pull/810
//
// The inventory data is written to the ConfigMap's
// annotations, rather than to the key-value pairs in
// the ConfigMap's data field, since
// 1. Keys in a ConfigMap's data field are too
// constrained for this purpose.
// 2. Using annotations allow any object to be used,
// not just a ConfigMap, should some other object
// (e.g. some App object) become more desirable
// for this purpose.
func (p *InventoryTransformerPlugin) Transform(m resmap.ResMap) error {
inv, h, err := makeInventory(m)
if err != nil {
return err
}
args := types.ConfigMapArgs{}
args.Name = p.Name
args.Namespace = p.Namespace
opts := &types.GeneratorOptions{
Annotations: make(map[string]string),
}
opts.Annotations[inventory.HashAnnotation] = h
err = inv.UpdateAnnotations(opts.Annotations)
if err != nil {
return err
}
cm, err := p.rf.RF().MakeConfigMap(p.ldr, opts, &args)
if err != nil {
return err
}
if p.Policy == types.GarbageCollect.String() {
for byeBye := range m {
delete(m, byeBye)
}
}
id := cm.Id()
if _, ok := m[id]; ok {
return fmt.Errorf(
"id '%v' already used; use a different name", id)
}
m[id] = cm
return nil
}
func makeInventory(m resmap.ResMap) (
inv *inventory.Inventory, hash string, err error) {
inv = inventory.NewInventory()
var keys []string
for _, r := range m {
ns := getNamespace(r)
item := resid.NewItemId(r.GetGvk(), ns, r.GetName())
if _, ok := inv.Current[item]; ok {
return nil, "", fmt.Errorf(
"item '%v' already in inventory", item)
}
inv.Current[item] = computeRefs(r, m)
keys = append(keys, item.String())
}
h, err := hasher.SortArrayAndComputeHash(keys)
return inv, h, err
}
func getNamespace(r *resource.Resource) string {
ns, err := r.GetFieldValue("metadata.namespace")
if err != nil && !strings.Contains(err.Error(), "no field named") {
panic(err)
}
return ns
}
func computeRefs(r *resource.Resource, m resmap.ResMap) (refs []resid.ItemId) {
for _, refid := range r.GetRefBy() {
ref := m[refid]
ns := getNamespace(ref)
refs = append(refs, resid.NewItemId(ref.GetGvk(), ns, ref.GetName()))
}
return
}

View File

@@ -0,0 +1,137 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
//go:generate go run sigs.k8s.io/kustomize/plugin/pluginator
package main
import (
"fmt"
"strings"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/hasher"
"sigs.k8s.io/kustomize/pkg/ifc"
"sigs.k8s.io/kustomize/pkg/inventory"
"sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/types"
"sigs.k8s.io/yaml"
)
type plugin struct {
ldr ifc.Loader
rf *resmap.Factory
Policy string `json:"policy,omitempty" yaml:"policy,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
}
var KustomizePlugin plugin
func (p *plugin) Config(
ldr ifc.Loader, rf *resmap.Factory, c []byte) (err error) {
p.ldr = ldr
p.rf = rf
err = yaml.Unmarshal(c, p)
if err != nil {
return err
}
if p.Policy == "" {
p.Policy = types.GarbageIgnore.String()
}
if p.Policy != types.GarbageCollect.String() &&
p.Policy != types.GarbageIgnore.String() {
return fmt.Errorf(
"unrecognized garbagePolicy '%s'", p.Policy)
}
return nil
}
// Transform generates an inventory object from the input ResMap.
// This ConfigMap supports the pruning command in
// the client side tool proposed here:
// https://github.com/kubernetes/enhancements/pull/810
//
// The inventory data is written to the ConfigMap's
// annotations, rather than to the key-value pairs in
// the ConfigMap's data field, since
// 1. Keys in a ConfigMap's data field are too
// constrained for this purpose.
// 2. Using annotations allow any object to be used,
// not just a ConfigMap, should some other object
// (e.g. some App object) become more desirable
// for this purpose.
func (p *plugin) Transform(m resmap.ResMap) error {
inv, h, err := makeInventory(m)
if err != nil {
return err
}
args := types.ConfigMapArgs{}
args.Name = p.Name
args.Namespace = p.Namespace
opts := &types.GeneratorOptions{
Annotations: make(map[string]string),
}
opts.Annotations[inventory.HashAnnotation] = h
err = inv.UpdateAnnotations(opts.Annotations)
if err != nil {
return err
}
cm, err := p.rf.RF().MakeConfigMap(p.ldr, opts, &args)
if err != nil {
return err
}
if p.Policy == types.GarbageCollect.String() {
for byeBye := range m {
delete(m, byeBye)
}
}
id := cm.Id()
if _, ok := m[id]; ok {
return fmt.Errorf(
"id '%v' already used; use a different name", id)
}
m[id] = cm
return nil
}
func makeInventory(m resmap.ResMap) (
inv *inventory.Inventory, hash string, err error) {
inv = inventory.NewInventory()
var keys []string
for _, r := range m {
ns := getNamespace(r)
item := resid.NewItemId(r.GetGvk(), ns, r.GetName())
if _, ok := inv.Current[item]; ok {
return nil, "", fmt.Errorf(
"item '%v' already in inventory", item)
}
inv.Current[item] = computeRefs(r, m)
keys = append(keys, item.String())
}
h, err := hasher.SortArrayAndComputeHash(keys)
return inv, h, err
}
func getNamespace(r *resource.Resource) string {
ns, err := r.GetFieldValue("metadata.namespace")
if err != nil && !strings.Contains(err.Error(), "no field named") {
panic(err)
}
return ns
}
func computeRefs(r *resource.Resource, m resmap.ResMap) (refs []resid.ItemId) {
for _, refid := range r.GetRefBy() {
ref := m[refid]
ns := getNamespace(ref)
refs = append(refs, resid.NewItemId(ref.GetGvk(), ns, ref.GetName()))
}
return
}

View File

@@ -0,0 +1,125 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package main_test
import (
"testing"
"sigs.k8s.io/kustomize/pkg/kusttest"
"sigs.k8s.io/kustomize/plugin"
)
const (
content = `
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---
apiVersion: v1
kind: Secret
metadata:
name: secret1
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy1
spec:
template:
spec:
containers:
- env:
name: CM_FOO
valueFrom:
configMapKeyRef:
key: someKey
name: cm1
envFrom:
configMapRef:
key: someKey
name: cm1
secretRef:
key: someKey
name: secret1
image: nginx:1.7.9
name: nginx
`
inv = `
apiVersion: v1
kind: ConfigMap
metadata:
annotations:
kustomize.config.k8s.io/Inventory: '{"current":{"apps_v1_Deployment|~X|deploy1":null,"~G_v1_ConfigMap|~X|cm1":null,"~G_v1_Secret|~X|secret1":null}}'
kustomize.config.k8s.io/InventoryHash: h44788gt7g
name: pruneCM
namespace: default
`
)
func TestInventoryTransformerCollect(t *testing.T) {
tc := plugin.NewEnvForTest(t).Set()
defer tc.Reset()
tc.BuildGoPlugin(
"builtin", "", "InventoryTransformer")
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: InventoryTransformer
metadata:
name: notImportantHere
policy: GarbageCollect
name: pruneCM
namespace: default
`, content)
th.AssertActualEqualsExpected(rm, inv)
}
func TestInventoryTransformerIgnore(t *testing.T) {
tc := plugin.NewEnvForTest(t).Set()
defer tc.Reset()
tc.BuildGoPlugin(
"builtin", "", "InventoryTransformer")
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: InventoryTransformer
metadata:
name: notImportantHere
policy: GarbageIgnore
name: pruneCM
namespace: default
`, content)
th.AssertActualEqualsExpected(rm, inv+"---"+content)
}
func TestInventoryTransformerDefaultPolicy(t *testing.T) {
tc := plugin.NewEnvForTest(t).Set()
defer tc.Reset()
tc.BuildGoPlugin(
"builtin", "", "InventoryTransformer")
th := kusttest_test.NewKustTestPluginHarness(t, "/app")
rm := th.LoadAndRunTransformer(`
apiVersion: builtin
kind: InventoryTransformer
metadata:
name: notImportantHere
name: pruneCM
namespace: default
`, content)
th.AssertActualEqualsExpected(rm, inv+"---"+content)
}