Skip to content

Commit

Permalink
generate configmap for pruning
Browse files Browse the repository at this point in the history
  • Loading branch information
Liujingfang1 committed Apr 8, 2019
1 parent 4937b1c commit 826affb
Show file tree
Hide file tree
Showing 17 changed files with 673 additions and 4 deletions.
6 changes: 6 additions & 0 deletions k8sdeps/transformer/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
16 changes: 16 additions & 0 deletions k8sdeps/transformer/hash/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"crypto/sha256"
"encoding/json"
"fmt"
"sort"

"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand Down Expand Up @@ -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) {
Expand Down
22 changes: 22 additions & 0 deletions k8sdeps/transformer/hash/hash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
110 changes: 110 additions & 0 deletions k8sdeps/transformer/prune/prune.go
Original file line number Diff line number Diff line change
@@ -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
}
171 changes: 171 additions & 0 deletions k8sdeps/transformer/prune/prune_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading

0 comments on commit 826affb

Please sign in to comment.