mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-10 08:20:59 +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(
|
||||
fSys fs.FileSystem, folderPath string, m resmap.ResMap) error {
|
||||
for _, res := range m.Resources() {
|
||||
filename := filepath.Join(
|
||||
folderPath,
|
||||
fmt.Sprintf(
|
||||
|
||||
byNamespace := m.GroupedByNamespace()
|
||||
nsNeeded := len(byNamespace) > 1
|
||||
for namespace, nresources := range byNamespace {
|
||||
for _, res := range nresources {
|
||||
basename := fmt.Sprintf(
|
||||
"%s_%s.yaml",
|
||||
strings.ToLower(res.GetGvk().String()),
|
||||
strings.ToLower(res.GetName()),
|
||||
),
|
||||
)
|
||||
out, err := yaml.Marshal(res.Map())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = fSys.WriteFile(filename, out)
|
||||
if err != nil {
|
||||
return err
|
||||
)
|
||||
|
||||
// Preserve backward compatibility with kustomize 2.1.0.
|
||||
// No need to cluter filename with namespace if all the objects
|
||||
// are in the same namespace.
|
||||
if (nsNeeded) && (namespace != "cluster-wide") {
|
||||
basename = fmt.Sprintf(
|
||||
"%s_%s",
|
||||
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
|
||||
|
||||
@@ -180,3 +180,40 @@ func (x Gvk) IsClusterKind() bool {
|
||||
}
|
||||
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
|
||||
// namespace/Group/Version/Kind/name.
|
||||
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.
|
||||
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() []resid.ResId
|
||||
|
||||
@@ -343,6 +350,29 @@ func (m *resWrangler) GetById(id resid.ResId) (*resource.Resource, error) {
|
||||
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.
|
||||
func (m *resWrangler) AsYaml() ([]byte, error) {
|
||||
firstObj := true
|
||||
@@ -504,7 +534,7 @@ func (m *resWrangler) appendReplaceOrMerge(
|
||||
res *resource.Resource) error {
|
||||
id := res.CurId()
|
||||
// Maybe also try by current id if nothing matches?
|
||||
matches := m.GetMatchingResourcesByOriginalId(id.GvknEquals)
|
||||
matches := m.GetMatchingResourcesByOriginalId(id.Equals)
|
||||
switch len(matches) {
|
||||
case 0:
|
||||
switch res.Behavior() {
|
||||
|
||||
@@ -486,18 +486,108 @@ func TestGeneratingIntoNamespaces(t *testing.T) {
|
||||
th.WriteK("/app", `
|
||||
configMapGenerator:
|
||||
- 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:
|
||||
- key=value
|
||||
- name: test
|
||||
namespace: kube-system
|
||||
literals:
|
||||
- key=value
|
||||
`)
|
||||
_, err := th.MakeKustTarget().MakeCustomizedResMap()
|
||||
// Document #1155
|
||||
// This actually should be nil; it should work, and
|
||||
// have some expected output.
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
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 {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user