generate configmap for pruning

This commit is contained in:
Jingfang Liu
2019-04-05 16:13:15 -07:00
parent 4937b1c75e
commit 826affb8dd
17 changed files with 673 additions and 4 deletions

View File

@@ -20,8 +20,10 @@ package transformer
import (
"sigs.k8s.io/kustomize/k8sdeps/transformer/hash"
"sigs.k8s.io/kustomize/k8sdeps/transformer/patch"
"sigs.k8s.io/kustomize/k8sdeps/transformer/prune"
"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
@@ -41,3 +43,7 @@ func (p *FactoryImpl) MakePatchTransformer(slice []*resource.Resource, rf *resou
func (p *FactoryImpl) MakeHashTransformer() transformers.Transformer {
return hash.NewNameHashTransformer()
}
func (p *FactoryImpl) MakePruneTransformer(arg *types.Prune, namespace string, append bool) transformers.Transformer {
return prune.NewPruneTransformer(arg, namespace, append)
}

View File

@@ -20,6 +20,7 @@ import (
"crypto/sha256"
"encoding/json"
"fmt"
"sort"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -86,6 +87,21 @@ func SecretHash(sec *v1.Secret) (string, error) {
return h, nil
}
// SortArrayAndComputeHash sorts a string array and
// returns a hash for it
func SortArrayAndComputeHash(s []string) (string, error) {
sort.Strings(s)
data, err := json.Marshal(s)
if err != nil {
return "", err
}
h, err := encodeHash(hash(string(data)))
if err != nil {
return "", err
}
return h, nil
}
// encodeConfigMap encodes a ConfigMap.
// Data, Kind, and Name are taken into account.
func encodeConfigMap(cm *v1.ConfigMap) (string, error) {

View File

@@ -90,6 +90,28 @@ func TestSecretHash(t *testing.T) {
}
}
func TestArrayHash(t *testing.T) {
array1 := []string{"a", "b", "c"}
array2 := []string{"c", "b", "a"}
h1, err := SortArrayAndComputeHash(array1)
if err != nil {
t.Errorf("unexpected error %v", err)
}
if h1 == "" {
t.Errorf("failed to hash %v", array1)
}
h2, err := SortArrayAndComputeHash(array2)
if err != nil {
t.Errorf("unexpected error %v", err)
}
if h2 == "" {
t.Errorf("failed to hash %v", array2)
}
if h1 != h2 {
t.Errorf("hash is not consistent with reordered list: %s %s", h1, h2)
}
}
func TestEncodeConfigMap(t *testing.T) {
cases := []struct {
desc string

View File

@@ -0,0 +1,110 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package prune
import (
"fmt"
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/k8sdeps/transformer/hash"
"sigs.k8s.io/kustomize/pkg/gvk"
"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"
)
//const PruneAnnotation = "kustomize.k8s.io/PruneRevision"
const PruneAnnotation = "current"
// pruneTransformer compute the ConfigMap used in prune
type pruneTransformer struct {
append bool
cmName string
cmNamespace string
}
var _ transformers.Transformer = &pruneTransformer{}
// NewPruneTransformer makes a pruneTransformer.
func NewPruneTransformer(p *types.Prune, namespace string, append bool) transformers.Transformer {
if p == nil || p.Type != "alphaConfigMap" || p.AlphaConfigMap.Namespace != namespace {
return transformers.NewNoOpTransformer()
}
return &pruneTransformer{
append: append,
cmName: p.AlphaConfigMap.Name,
cmNamespace: p.AlphaConfigMap.Namespace,
}
}
// Transform generates a prune ConfigMap based on the input ResMap.
// this tranformer 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
func (o *pruneTransformer) Transform(m resmap.ResMap) error {
keys := []string{}
for id, r := range m {
s := id.PruneString()
keys = append(keys, s)
for _, refid := range r.GetRefBy() {
keys = append(keys, s+"---"+refid.PruneString())
}
}
h, err := hash.SortArrayAndComputeHash(keys)
if err != nil {
return err
}
args := &types.ConfigMapArgs{}
args.Name = o.cmName
args.Namespace = o.cmNamespace
for _, key := range keys {
args.LiteralSources = append(args.LiteralSources,
key+"="+h)
}
opts := &types.GeneratorOptions{
Annotations: make(map[string]string),
}
opts.Annotations[PruneAnnotation] = h
kf := kunstruct.NewKunstructuredFactoryImpl()
k, err := kf.MakeConfigMap(nil, opts, args)
if err != nil {
return err
}
if !o.append {
for k := range m {
delete(m, k)
}
}
id := resid.NewResIdWithPrefixNamespace(
gvk.Gvk{
Version: "v1",
Kind: "ConfigMap",
},
o.cmName,
"", o.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

@@ -0,0 +1,171 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package prune
import (
"reflect"
"testing"
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/resid"
"sigs.k8s.io/kustomize/pkg/resmap"
"sigs.k8s.io/kustomize/pkg/resource"
"sigs.k8s.io/kustomize/pkg/types"
)
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{}{
"group": "apps",
"apiVersion": "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 TestPruneTransformer(t *testing.T) {
rf := resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())
// hash is derived based on all keys in the ConfigMap data field.
// It is added to annotations as
// current: hash
// When seeing the same annotation, prune binary assumes no
// clean up is needed
hash := "k777d7h45b"
// 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{}{
"current": hash,
},
},
"data": map[string]interface{}{
"_ConfigMap__cm1": hash,
"_Secret__secret1": hash,
"apps_Deployment__deploy1": hash,
"_ConfigMap__cm1---apps_Deployment__deploy1": hash,
"_Secret__secret1---apps_Deployment__deploy1": hash,
},
})
expected := resmap.ResMap{
resid.NewResIdWithPrefixNamespace(cmap, "pruneCM", "", "default"): pruneMap,
}
p := &types.Prune{
Type: "alphaConfigMap",
AlphaConfigMap: types.NameArgs{
Name: "pruneCM",
Namespace: "default",
},
}
objs := makeResMap()
// include the original resmap; only return the ConfigMap for pruning
tran := NewPruneTransformer(p, "default", false)
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 = NewPruneTransformer(p, "default", true)
tran.Transform(objs)
if !reflect.DeepEqual(objs, expected) {
err := expected.ErrorIfNotEqual(objs)
t.Fatalf("actual doesn't match expected: %v", err)
}
}