diff --git a/go.mod b/go.mod index 89e7a5174..bcffc8073 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 85a9911b2..7fc814bb9 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/k8sdeps/transformer/factory.go b/k8sdeps/transformer/factory.go index 75020282c..0491db864 100644 --- a/k8sdeps/transformer/factory.go +++ b/k8sdeps/transformer/factory.go @@ -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) -} diff --git a/k8sdeps/transformer/inventory/transformer.go b/k8sdeps/transformer/inventory/transformer.go deleted file mode 100644 index 5b3009831..000000000 --- a/k8sdeps/transformer/inventory/transformer.go +++ /dev/null @@ -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 -} diff --git a/k8sdeps/transformer/inventory/transformer_test.go b/k8sdeps/transformer/inventory/transformer_test.go deleted file mode 100644 index 0c503d396..000000000 --- a/k8sdeps/transformer/inventory/transformer_test.go +++ /dev/null @@ -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) - } -} diff --git a/pkg/accumulator/resaccumulator.go b/pkg/accumulator/resaccumulator.go index 5f26bf862..48b9526eb 100644 --- a/pkg/accumulator/resaccumulator.go +++ b/pkg/accumulator/resaccumulator.go @@ -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 diff --git a/pkg/ifc/transformer/factory.go b/pkg/ifc/transformer/factory.go index f04d58f55..da8a640f5 100644 --- a/pkg/ifc/transformer/factory.go +++ b/pkg/ifc/transformer/factory.go @@ -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) } diff --git a/pkg/target/kusttarget.go b/pkg/target/kusttarget.go index 0cd069547..595938b02 100644 --- a/pkg/target/kusttarget.go +++ b/pkg/target/kusttarget.go @@ -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 { diff --git a/pkg/types/garbagepolicy_string.go b/pkg/types/garbagepolicy_string.go new file mode 100644 index 000000000..b6ff7d291 --- /dev/null +++ b/pkg/types/garbagepolicy_string.go @@ -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]] +} diff --git a/pkg/types/kustomization.go b/pkg/types/kustomization.go index eb6fc2af2..fb889c50e 100644 --- a/pkg/types/kustomization.go +++ b/pkg/types/kustomization.go @@ -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 ) diff --git a/plugin/builtin/HashTransformer.go b/plugin/builtin/HashTransformer.go index 5376898aa..ab1374e39 100644 --- a/plugin/builtin/HashTransformer.go +++ b/plugin/builtin/HashTransformer.go @@ -3,6 +3,7 @@ package builtin import ( "fmt" + "sigs.k8s.io/kustomize/pkg/ifc" "sigs.k8s.io/kustomize/pkg/resmap" ) diff --git a/plugin/builtin/InventoryTransformer.go b/plugin/builtin/InventoryTransformer.go new file mode 100644 index 000000000..8b17c4b28 --- /dev/null +++ b/plugin/builtin/InventoryTransformer.go @@ -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 +} diff --git a/plugin/builtin/inventorytransformer/InventoryTransformer.go b/plugin/builtin/inventorytransformer/InventoryTransformer.go new file mode 100644 index 000000000..593650491 --- /dev/null +++ b/plugin/builtin/inventorytransformer/InventoryTransformer.go @@ -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 +} diff --git a/plugin/builtin/inventorytransformer/InventoryTransformer_test.go b/plugin/builtin/inventorytransformer/InventoryTransformer_test.go new file mode 100644 index 000000000..0e6bf1961 --- /dev/null +++ b/plugin/builtin/inventorytransformer/InventoryTransformer_test.go @@ -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) +} +