mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
Push namespace transformer code to plugin.
This commit is contained in:
@@ -19,15 +19,15 @@ import (
|
|||||||
// ResMap is an interface describing operations on the
|
// ResMap is an interface describing operations on the
|
||||||
// core kustomize data structure, a list of Resources.
|
// core kustomize data structure, a list of Resources.
|
||||||
//
|
//
|
||||||
// Every Resource has two ResIds: OriginalId and CurId.
|
// Every Resource has two ResIds: OrgId and CurId.
|
||||||
//
|
|
||||||
// A ResId is a tuple of {Namespace, Group, Version, Kind, Name}.
|
|
||||||
//
|
//
|
||||||
// In a ResMap, no two resources may have the same 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
|
// 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 {
|
type ResMap interface {
|
||||||
// Size reports the number of resources.
|
// Size reports the number of resources.
|
||||||
Size() int
|
Size() int
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,8 +5,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||||
"sigs.k8s.io/kustomize/pkg/ifc"
|
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resid"
|
||||||
"sigs.k8s.io/kustomize/pkg/resmap"
|
"sigs.k8s.io/kustomize/pkg/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/pkg/resource"
|
||||||
"sigs.k8s.io/kustomize/pkg/transformers"
|
"sigs.k8s.io/kustomize/pkg/transformers"
|
||||||
"sigs.k8s.io/kustomize/pkg/transformers/config"
|
"sigs.k8s.io/kustomize/pkg/transformers/config"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
@@ -29,6 +32,96 @@ func (p *plugin) Config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *plugin) Transform(m resmap.ResMap) error {
|
func (p *plugin) Transform(m resmap.ResMap) error {
|
||||||
return transformers.NewNamespaceTransformer(
|
if len(p.Namespace) == 0 {
|
||||||
p.Namespace, p.FieldSpecs).Transform(m)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user