Push namespace transformer code to plugin.

This commit is contained in:
Jeffrey Regan
2019-06-19 12:57:02 -07:00
parent 658ebeaa21
commit 2eccf67b1c
4 changed files with 101 additions and 331 deletions

View File

@@ -19,15 +19,15 @@ import (
// ResMap is an interface describing operations on the
// core kustomize data structure, a list of Resources.
//
// Every Resource has two ResIds: OriginalId and CurId.
//
// A ResId is a tuple of {Namespace, Group, Version, Kind, Name}.
// Every Resource has two ResIds: OrgId and CurId.
//
// In a ResMap, no two resources may have the same CurId,
// but they may have the same OriginalId. The latter can happen
// but they may have the same OrgId. The latter can happen
// when mixing two or more different overlays apply different
// transformations to a common base.
//
// transformations to a common base. When looking for a
// resource to transform, try the OrgId first, and if this
// fails or finds too many, it might make sense to then try
// the CurrId. Depends on the situation.
type ResMap interface {
// Size reports the number of resources.
Size() int

View File

@@ -1,124 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package transformers
import (
"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/config"
)
type namespaceTransformer struct {
namespace string
fieldSpecsToUse []config.FieldSpec
}
var _ Transformer = &namespaceTransformer{}
// NewNamespaceTransformer construct a namespaceTransformer.
func NewNamespaceTransformer(ns string, cf []config.FieldSpec) Transformer {
return &namespaceTransformer{
namespace: ns,
fieldSpecsToUse: cf,
}
}
const metaNamespace = "metadata/namespace"
// Transform adds the namespace.
func (o *namespaceTransformer) Transform(m resmap.ResMap) (err error) {
if len(o.namespace) == 0 {
return nil
}
for _, r := range m.Resources() {
id := r.OrgId()
fs, ok := o.isSelected(id)
if !ok {
continue
}
if len(r.Map()) == 0 {
// Don't mutate empty objects?
continue
}
if doIt(id, fs) {
err = o.changeNamespace(r, fs)
if err != nil {
return err
}
}
}
o.updateClusterRoleBinding(m)
return nil
}
// Special casing metadata.namespace since
// all objects have it, even "ClusterKind" objects
// that don't exist in a namespace (the Namespace
// object itself doesn't live in a namespace).
func doIt(id resid.ResId, fs *config.FieldSpec) bool {
return fs.Path != metaNamespace ||
(fs.Path == metaNamespace && !id.IsClusterKind())
}
func (o *namespaceTransformer) changeNamespace(
r *resource.Resource, fs *config.FieldSpec) error {
return MutateField(
r.Map(), fs.PathSlice(), fs.CreateIfNotPresent,
func(_ interface{}) (interface{}, error) {
return o.namespace, nil
})
}
func (o *namespaceTransformer) isSelected(
id resid.ResId) (*config.FieldSpec, bool) {
for _, fs := range o.fieldSpecsToUse {
if id.IsSelected(&fs.Gvk) {
return &fs, true
}
}
return nil, false
}
func (o *namespaceTransformer) updateClusterRoleBinding(m resmap.ResMap) {
srvAccount := gvk.Gvk{Version: "v1", Kind: "ServiceAccount"}
saMap := map[string]bool{}
for _, id := range m.AllIds() {
if id.Gvk.Equals(srvAccount) {
saMap[id.Name] = true
}
}
for _, res := range m.Resources() {
if res.OrgId().Kind != "ClusterRoleBinding" &&
res.OrgId().Kind != "RoleBinding" {
continue
}
objMap := res.Map()
subjects, ok := objMap["subjects"].([]interface{})
if subjects == nil || !ok {
continue
}
for i := range subjects {
subject := subjects[i].(map[string]interface{})
kind, foundk := subject["kind"]
name, foundn := subject["name"]
if !foundk || !foundn || kind.(string) != srvAccount.Kind {
continue
}
// a ServiceAccount named “default” exists in every active namespace
if name.(string) == "default" || saMap[name.(string)] {
subject := subjects[i].(map[string]interface{})
MutateField(
subject, []string{"namespace"},
true, func(_ interface{}) (interface{}, error) {
return o.namespace, nil
})
subjects[i] = subject
}
}
objMap["subjects"] = subjects
}
}

View File

@@ -1,199 +0,0 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package transformers
import (
"testing"
"sigs.k8s.io/kustomize/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/pkg/resmaptest"
"sigs.k8s.io/kustomize/pkg/resource"
)
func TestNamespaceRun(t *testing.T) {
rf := resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())
m := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm1",
}}).
Add(map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm2",
"namespace": "foo",
}}).
Add(map[string]interface{}{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": map[string]interface{}{
"name": "ns1",
}}).
Add(map[string]interface{}{
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": map[string]interface{}{
"name": "default",
"namespace": "system",
}}).
Add(map[string]interface{}{
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": map[string]interface{}{
"name": "service-account",
"namespace": "system",
}}).
Add(map[string]interface{}{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "ClusterRoleBinding",
"metadata": map[string]interface{}{
"name": "manager-rolebinding",
},
"subjects": []interface{}{
map[string]interface{}{
"kind": "ServiceAccount",
"name": "default",
"namespace": "system",
},
map[string]interface{}{
"kind": "ServiceAccount",
"name": "service-account",
"namespace": "system",
},
map[string]interface{}{
"kind": "ServiceAccount",
"name": "another",
"namespace": "random",
},
}}).
Add(map[string]interface{}{
"apiVersion": "apiextensions.k8s.io/v1beta1",
"kind": "CustomResourceDefinition",
"metadata": map[string]interface{}{
"name": "crd",
}}).ResMap()
expected := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm1",
"namespace": "test",
}}).
Add(map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm2",
"namespace": "test",
}}).
Add(map[string]interface{}{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": map[string]interface{}{
"name": "ns1",
}}).
Add(map[string]interface{}{
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": map[string]interface{}{
"name": "default",
"namespace": "test",
}}).
Add(map[string]interface{}{
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": map[string]interface{}{
"name": "service-account",
"namespace": "test",
}}).
Add(map[string]interface{}{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "ClusterRoleBinding",
"metadata": map[string]interface{}{
"name": "manager-rolebinding",
},
"subjects": []interface{}{
map[string]interface{}{
"kind": "ServiceAccount",
"name": "default",
"namespace": "test",
},
map[string]interface{}{
"kind": "ServiceAccount",
"name": "service-account",
"namespace": "test",
},
map[string]interface{}{
"kind": "ServiceAccount",
"name": "another",
"namespace": "random",
},
}}).
Add(map[string]interface{}{
"apiVersion": "apiextensions.k8s.io/v1beta1",
"kind": "CustomResourceDefinition",
"metadata": map[string]interface{}{
"name": "crd",
}}).ResMap()
nst := NewNamespaceTransformer("test", defaultTransformerConfig.NameSpace)
err := nst.Transform(m)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err = expected.ErrorIfNotEqualLists(m); err != nil {
t.Fatalf("actual doesn't match expected: %v", err)
}
}
func TestNamespaceRunForClusterLevelKind(t *testing.T) {
rf := resource.NewFactory(
kunstruct.NewKunstructuredFactoryImpl())
m := resmaptest_test.NewRmBuilder(t, rf).
Add(map[string]interface{}{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": map[string]interface{}{
"name": "ns1",
}}).
Add(map[string]interface{}{
"kind": "CustomResourceDefinition",
"metadata": map[string]interface{}{
"name": "crd1",
}}).
Add(map[string]interface{}{
"kind": "PersistentVolume",
"metadata": map[string]interface{}{
"name": "pv1",
}}).
Add(map[string]interface{}{
"kind": "ClusterRole",
"metadata": map[string]interface{}{
"name": "cr1",
}}).
Add(map[string]interface{}{
"kind": "ClusterRoleBinding",
"metadata": map[string]interface{}{
"name": "crb1",
},
"subjects": []interface{}{}}).ResMap()
expected := m.DeepCopy()
nst := NewNamespaceTransformer("test", defaultTransformerConfig.NameSpace)
err := nst.Transform(m)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err = expected.ErrorIfNotEqualLists(m); err != nil {
t.Fatalf("actual doesn't match expected: %v", err)
}
}

View File

@@ -5,8 +5,11 @@
package main
import (
"sigs.k8s.io/kustomize/pkg/gvk"
"sigs.k8s.io/kustomize/pkg/ifc"
"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/transformers/config"
"sigs.k8s.io/yaml"
@@ -29,6 +32,96 @@ func (p *plugin) Config(
}
func (p *plugin) Transform(m resmap.ResMap) error {
return transformers.NewNamespaceTransformer(
p.Namespace, p.FieldSpecs).Transform(m)
if len(p.Namespace) == 0 {
return nil
}
for _, r := range m.Resources() {
id := r.OrgId()
fs, ok := p.isSelected(id)
if !ok {
continue
}
if len(r.Map()) == 0 {
// Don't mutate empty objects?
continue
}
if doIt(id, fs) {
if err := p.changeNamespace(r, fs); err != nil {
return err
}
}
}
p.updateClusterRoleBinding(m)
return nil
}
const metaNamespace = "metadata/namespace"
// Special casing metadata.namespace since
// all objects have it, even "ClusterKind" objects
// that don't exist in a namespace (the Namespace
// object itself doesn't live in a namespace).
func doIt(id resid.ResId, fs *config.FieldSpec) bool {
return fs.Path != metaNamespace ||
(fs.Path == metaNamespace && !id.IsClusterKind())
}
func (p *plugin) changeNamespace(
r *resource.Resource, fs *config.FieldSpec) error {
return transformers.MutateField(
r.Map(), fs.PathSlice(), fs.CreateIfNotPresent,
func(_ interface{}) (interface{}, error) {
return p.Namespace, nil
})
}
func (p *plugin) isSelected(
id resid.ResId) (*config.FieldSpec, bool) {
for _, fs := range p.FieldSpecs {
if id.IsSelected(&fs.Gvk) {
return &fs, true
}
}
return nil, false
}
func (p *plugin) updateClusterRoleBinding(m resmap.ResMap) {
srvAccount := gvk.Gvk{Version: "v1", Kind: "ServiceAccount"}
saMap := map[string]bool{}
for _, id := range m.AllIds() {
if id.Gvk.Equals(srvAccount) {
saMap[id.Name] = true
}
}
for _, res := range m.Resources() {
if res.OrgId().Kind != "ClusterRoleBinding" &&
res.OrgId().Kind != "RoleBinding" {
continue
}
objMap := res.Map()
subjects, ok := objMap["subjects"].([]interface{})
if subjects == nil || !ok {
continue
}
for i := range subjects {
subject := subjects[i].(map[string]interface{})
kind, foundK := subject["kind"]
name, foundN := subject["name"]
if !foundK || !foundN || kind.(string) != srvAccount.Kind {
continue
}
// a ServiceAccount named “default” exists in every active namespace
if name.(string) == "default" || saMap[name.(string)] {
subject := subjects[i].(map[string]interface{})
transformers.MutateField(
subject, []string{"namespace"},
true, func(_ interface{}) (interface{}, error) {
return p.Namespace, nil
})
subjects[i] = subject
}
}
objMap["subjects"] = subjects
}
}