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:
Jerome Brette
2019-06-20 12:33:27 -05:00
parent 762d3143eb
commit 2bba0a6aa3
5 changed files with 216 additions and 20 deletions

View File

@@ -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

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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() {

View File

@@ -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")
}