mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
Support for ConfigMap generator with identical names in different namespaces.
- Attempt to account, at build time, for subsequent kubectl apply behavior. (empty or no namespace means default). - Account for the fact that not all objects have a namespace. - Add new Equal method to ResId address merge name conflict - Add GroupByName to resources by namespaces to resolve filenames conflict - Added corresponding unit tests. - Change the fail test for issue #1155
This commit is contained in:
@@ -202,22 +202,41 @@ func NewCmdBuildPrune(
|
|||||||
|
|
||||||
func writeIndividualFiles(
|
func writeIndividualFiles(
|
||||||
fSys fs.FileSystem, folderPath string, m resmap.ResMap) error {
|
fSys fs.FileSystem, folderPath string, m resmap.ResMap) error {
|
||||||
for _, res := range m.Resources() {
|
|
||||||
filename := filepath.Join(
|
byNamespace := m.GroupedByNamespace()
|
||||||
folderPath,
|
nsNeeded := len(byNamespace) > 1
|
||||||
fmt.Sprintf(
|
for namespace, nresources := range byNamespace {
|
||||||
|
for _, res := range nresources {
|
||||||
|
basename := fmt.Sprintf(
|
||||||
"%s_%s.yaml",
|
"%s_%s.yaml",
|
||||||
strings.ToLower(res.GetGvk().String()),
|
strings.ToLower(res.GetGvk().String()),
|
||||||
strings.ToLower(res.GetName()),
|
strings.ToLower(res.GetName()),
|
||||||
),
|
)
|
||||||
)
|
|
||||||
out, err := yaml.Marshal(res.Map())
|
// Preserve backward compatibility with kustomize 2.1.0.
|
||||||
if err != nil {
|
// No need to cluter filename with namespace if all the objects
|
||||||
return err
|
// are in the same namespace.
|
||||||
}
|
if (nsNeeded) && (namespace != "cluster-wide") {
|
||||||
err = fSys.WriteFile(filename, out)
|
basename = fmt.Sprintf(
|
||||||
if err != nil {
|
"%s_%s",
|
||||||
return err
|
strings.ToLower(namespace),
|
||||||
|
strings.ToLower(basename),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(
|
||||||
|
folderPath,
|
||||||
|
basename,
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := yaml.Marshal(res.Map())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = fSys.WriteFile(filename, out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -180,3 +180,40 @@ func (x Gvk) IsClusterKind() bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var notNamespaceableKinds = []string{
|
||||||
|
"APIService",
|
||||||
|
"CSIDriver",
|
||||||
|
"CSINode",
|
||||||
|
"CertificateSigningRequest",
|
||||||
|
"ClusterRole",
|
||||||
|
"ClusterRoleBinding",
|
||||||
|
"ComponentStatus",
|
||||||
|
"CustomResourceDefinition",
|
||||||
|
"MutatingWebhookConfiguration",
|
||||||
|
"Namespace",
|
||||||
|
"Node",
|
||||||
|
"PersistentVolume",
|
||||||
|
"PodSecurityPolicy",
|
||||||
|
"PodSecurityPolicy",
|
||||||
|
"PriorityClass",
|
||||||
|
"RuntimeClass",
|
||||||
|
"SelfSubjectAccessReview",
|
||||||
|
"SelfSubjectRulesReview",
|
||||||
|
"StorageClass",
|
||||||
|
"SubjectAccessReview",
|
||||||
|
"TokenReview",
|
||||||
|
"ValidatingWebhookConfiguration",
|
||||||
|
"VolumeAttachment",
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNamespaceableKind returns true if x is a namespable Gvk
|
||||||
|
// Implements https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#not-all-objects-are-in-a-namespace
|
||||||
|
func (x Gvk) IsNamespaceableKind() bool {
|
||||||
|
for _, k := range notNamespaceableKinds {
|
||||||
|
if k == x.Kind {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -93,5 +93,25 @@ func (id ResId) GvknEquals(o ResId) bool {
|
|||||||
// Equals returns true if the other id matches
|
// Equals returns true if the other id matches
|
||||||
// namespace/Group/Version/Kind/name.
|
// namespace/Group/Version/Kind/name.
|
||||||
func (id ResId) Equals(o ResId) bool {
|
func (id ResId) Equals(o ResId) bool {
|
||||||
return id.Namespace == o.Namespace && id.GvknEquals(o)
|
return id.IsNsEquals(o) && id.GvknEquals(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNsEquals returns true if the other id matches namespace
|
||||||
|
// or both are in the default namespace
|
||||||
|
// or both are not namespaceable id.
|
||||||
|
func (id ResId) IsNsEquals(o ResId) bool {
|
||||||
|
return id.Namespace == o.Namespace ||
|
||||||
|
(id.IsInDefaultNs() && o.IsInDefaultNs()) ||
|
||||||
|
(!id.IsNamespaceable() && !o.IsNamespaceable())
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInDefaultNs returns true if id is a namespable ResId and the Namespace
|
||||||
|
// is either not set or set to "default"
|
||||||
|
func (id ResId) IsInDefaultNs() bool {
|
||||||
|
return id.IsNamespaceable() && (id.Namespace == "" || id.Namespace == "default")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNamespaceable returns true if id is a namespable ResId
|
||||||
|
func (id ResId) IsNamespaceable() bool {
|
||||||
|
return id.IsNamespaceableKind()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,13 @@ type ResMap interface {
|
|||||||
// Same as GetByOriginalId.
|
// Same as GetByOriginalId.
|
||||||
GetById(resid.ResId) (*resource.Resource, error)
|
GetById(resid.ResId) (*resource.Resource, error)
|
||||||
|
|
||||||
|
// GroupedByNamespace provides map of discardable slice
|
||||||
|
// of resource pointer
|
||||||
|
// The not namespaceable resources are added to the "cluster-wide" key.
|
||||||
|
// The resources in the default namespace are added to the "default" key.
|
||||||
|
// The rest of the resources are grouped in their respectiv namespace.
|
||||||
|
GroupedByNamespace() map[string][]*resource.Resource
|
||||||
|
|
||||||
// AllIds returns all CurrentIds.
|
// AllIds returns all CurrentIds.
|
||||||
AllIds() []resid.ResId
|
AllIds() []resid.ResId
|
||||||
|
|
||||||
@@ -343,6 +350,29 @@ func (m *resWrangler) GetById(id resid.ResId) (*resource.Resource, error) {
|
|||||||
return m.GetByCurrentId(id)
|
return m.GetByCurrentId(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GroupedByNamespace implements ResMap.GroupByNamespace
|
||||||
|
func (m *resWrangler) GroupedByNamespace() map[string][]*resource.Resource {
|
||||||
|
byNamespace := make(map[string][]*resource.Resource)
|
||||||
|
for _, res := range m.rList {
|
||||||
|
namespace := "cluster-wide"
|
||||||
|
|
||||||
|
if res.OrgId().IsNamespaceable() {
|
||||||
|
namespace = res.OrgId().Namespace
|
||||||
|
if res.OrgId().IsInDefaultNs() {
|
||||||
|
namespace = "default"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found := byNamespace[namespace]; !found {
|
||||||
|
byNamespace[namespace] = make([]*resource.Resource, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
byNamespace[namespace] = append(byNamespace[namespace], res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return byNamespace
|
||||||
|
}
|
||||||
|
|
||||||
// AsYaml implements ResMap.
|
// AsYaml implements ResMap.
|
||||||
func (m *resWrangler) AsYaml() ([]byte, error) {
|
func (m *resWrangler) AsYaml() ([]byte, error) {
|
||||||
firstObj := true
|
firstObj := true
|
||||||
@@ -504,7 +534,7 @@ func (m *resWrangler) appendReplaceOrMerge(
|
|||||||
res *resource.Resource) error {
|
res *resource.Resource) error {
|
||||||
id := res.CurId()
|
id := res.CurId()
|
||||||
// Maybe also try by current id if nothing matches?
|
// Maybe also try by current id if nothing matches?
|
||||||
matches := m.GetMatchingResourcesByOriginalId(id.GvknEquals)
|
matches := m.GetMatchingResourcesByOriginalId(id.Equals)
|
||||||
switch len(matches) {
|
switch len(matches) {
|
||||||
case 0:
|
case 0:
|
||||||
switch res.Behavior() {
|
switch res.Behavior() {
|
||||||
|
|||||||
@@ -486,18 +486,108 @@ func TestGeneratingIntoNamespaces(t *testing.T) {
|
|||||||
th.WriteK("/app", `
|
th.WriteK("/app", `
|
||||||
configMapGenerator:
|
configMapGenerator:
|
||||||
- name: test
|
- name: test
|
||||||
namespace: bob
|
namespace: default
|
||||||
|
literals:
|
||||||
|
- key=value
|
||||||
|
- name: test
|
||||||
|
namespace: kube-system
|
||||||
|
literals:
|
||||||
|
- key=value
|
||||||
|
secretGenerator:
|
||||||
|
- name: test
|
||||||
|
namespace: default
|
||||||
|
literals:
|
||||||
|
- username=admin
|
||||||
|
- password=somepw
|
||||||
|
- name: test
|
||||||
|
namespace: kube-system
|
||||||
|
literals:
|
||||||
|
- username=admin
|
||||||
|
- password=somepw
|
||||||
|
`)
|
||||||
|
m, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Err: %v", err)
|
||||||
|
}
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
key: value
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: test-t5t4md8fdm
|
||||||
|
namespace: default
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
key: value
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: test-t5t4md8fdm
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
password: c29tZXB3
|
||||||
|
username: YWRtaW4=
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: test-h65t9hg6kc
|
||||||
|
namespace: default
|
||||||
|
type: Opaque
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
data:
|
||||||
|
password: c29tZXB3
|
||||||
|
username: YWRtaW4=
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: test-h65t9hg6kc
|
||||||
|
namespace: kube-system
|
||||||
|
type: Opaque
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid that conflict is detected is the name are identical
|
||||||
|
// and namespace left to default
|
||||||
|
func TestConfigMapGeneratingIntoSameNamespace(t *testing.T) {
|
||||||
|
th := kusttest_test.NewKustTestHarness(t, "/app")
|
||||||
|
th.WriteK("/app", `
|
||||||
|
configMapGenerator:
|
||||||
|
- name: test
|
||||||
|
namespace: default
|
||||||
literals:
|
literals:
|
||||||
- key=value
|
- key=value
|
||||||
- name: test
|
- name: test
|
||||||
namespace: kube-system
|
|
||||||
literals:
|
literals:
|
||||||
- key=value
|
- key=value
|
||||||
`)
|
`)
|
||||||
_, err := th.MakeKustTarget().MakeCustomizedResMap()
|
_, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||||
// Document #1155
|
if err == nil {
|
||||||
// This actually should be nil; it should work, and
|
t.Fatalf("expected error")
|
||||||
// have some expected output.
|
}
|
||||||
|
if !strings.Contains(err.Error(), "must merge or replace") {
|
||||||
|
t.Fatalf("unexpected error %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid that conflict is detected is the name are identical
|
||||||
|
// and namespace left to default
|
||||||
|
func TestSecretGeneratingIntoSameNamespace(t *testing.T) {
|
||||||
|
th := kusttest_test.NewKustTestHarness(t, "/app")
|
||||||
|
th.WriteK("/app", `
|
||||||
|
secretGenerator:
|
||||||
|
- name: test
|
||||||
|
namespace: default
|
||||||
|
literals:
|
||||||
|
- username=admin
|
||||||
|
- password=somepw
|
||||||
|
- name: test
|
||||||
|
literals:
|
||||||
|
- username=admin
|
||||||
|
- password=somepw
|
||||||
|
`)
|
||||||
|
_, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expected error")
|
t.Fatalf("expected error")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user