Skip to content

Commit 4b6d27e

Browse files
authored
Improve formatKeyName function to handle all invalid ConfigMap key characters (#663)
1 parent 568322d commit 4b6d27e

File tree

2 files changed

+154
-5
lines changed

2 files changed

+154
-5
lines changed

components/odh-notebook-controller/controllers/notebook_runtime.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,11 @@ func (r *OpenshiftNotebookReconciler) syncRuntimeImagesConfigMap(ctx context.Con
105105
// Construct the key name
106106
if displayName != "" {
107107
formattedName := formatKeyName(displayName)
108-
data[formattedName] = metadataParsed
108+
if formattedName != "" {
109+
data[formattedName] = metadataParsed
110+
} else {
111+
log.Error(nil, "Failed to construct ConfigMap key name", "ImageStream", item.GetName(), "Tag", tag)
112+
}
109113
}
110114
}
111115
}
@@ -183,12 +187,21 @@ func extractDisplayName(metadata string) string {
183187
return displayName
184188
}
185189

186-
var multiDash = regexp.MustCompile(`-+`)
190+
var (
191+
invalidChars = regexp.MustCompile(`[^-._a-zA-Z0-9]+`)
192+
multiDash = regexp.MustCompile(`-+`)
193+
)
187194

195+
// formatKeyName sanitizes display names for use as ConfigMap keys.
196+
// Returns empty string if input contains only invalid characters.
188197
func formatKeyName(displayName string) string {
189-
s := strings.NewReplacer(" ", "-", "(", "", ")", "", "|", "").Replace(displayName)
190-
s = multiDash.ReplaceAllString(strings.ToLower(s), "-")
191-
return strings.Trim(s, "-") + ".json"
198+
s := invalidChars.ReplaceAllString(strings.ToLower(displayName), "-")
199+
s = multiDash.ReplaceAllString(s, "-")
200+
s = strings.Trim(s, "-")
201+
if s == "" {
202+
return ""
203+
}
204+
return s + ".json"
192205
}
193206

194207
// parseRuntimeImageMetadata extracts the first object from the JSON array

components/odh-notebook-controller/controllers/notebook_runtime_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,101 @@ var _ = Describe("Runtime images ConfigMap should be mounted", func() {
317317
},
318318
},
319319
},
320+
{
321+
name: "ImageStream with formatKeyName edge cases",
322+
notebookName: "test-notebook-runtime-format-key",
323+
expectedConfigMapData: map[string]string{
324+
"foo-bar.json": `{"display_name":"foo bar","metadata":{"display_name":"foo bar","image_name":"quay.io/opendatahub/test1","pull_policy":"IfNotPresent","tags":["tag1"]},"schema_name":"runtime-image"}`,
325+
"invalid-chars.json": `{"display_name":" !@#$|| invalid chars","metadata":{"display_name":" !@#$|| invalid chars","image_name":"quay.io/opendatahub/test2","pull_policy":"IfNotPresent","tags":["tag2"]},"schema_name":"runtime-image"}`,
326+
"cz.json": `{"display_name":"CZ ěščřžýáíé","metadata":{"display_name":"CZ ěščřžýáíé","image_name":"quay.io/opendatahub/test3","pull_policy":"IfNotPresent","tags":["tag3"]},"schema_name":"runtime-image"}`,
327+
},
328+
imageStream: &imagev1.ImageStream{
329+
TypeMeta: metav1.TypeMeta{
330+
Kind: "ImageStream",
331+
APIVersion: "image.openshift.io/v1",
332+
},
333+
ObjectMeta: metav1.ObjectMeta{
334+
Name: "format-key-test-image",
335+
Namespace: "redhat-ods-applications",
336+
Labels: map[string]string{
337+
"opendatahub.io/runtime-image": "true",
338+
},
339+
},
340+
Spec: imagev1.ImageStreamSpec{
341+
LookupPolicy: imagev1.ImageLookupPolicy{
342+
Local: true,
343+
},
344+
Tags: []imagev1.TagReference{
345+
{
346+
Name: "tag1",
347+
Annotations: map[string]string{
348+
"opendatahub.io/runtime-image-metadata": `
349+
[
350+
{
351+
"display_name": "foo bar",
352+
"metadata": {
353+
"tags": ["tag1"],
354+
"display_name": "foo bar",
355+
"pull_policy": "IfNotPresent"
356+
},
357+
"schema_name": "runtime-image"
358+
}
359+
]
360+
`,
361+
},
362+
From: &corev1.ObjectReference{
363+
Kind: "DockerImage",
364+
Name: "quay.io/opendatahub/test1",
365+
},
366+
},
367+
{
368+
Name: "tag2",
369+
Annotations: map[string]string{
370+
"opendatahub.io/runtime-image-metadata": `
371+
[
372+
{
373+
"display_name": " !@#$|| invalid chars",
374+
"metadata": {
375+
"tags": ["tag2"],
376+
"display_name": " !@#$|| invalid chars",
377+
"pull_policy": "IfNotPresent"
378+
},
379+
"schema_name": "runtime-image"
380+
}
381+
]
382+
`,
383+
},
384+
From: &corev1.ObjectReference{
385+
Kind: "DockerImage",
386+
Name: "quay.io/opendatahub/test2",
387+
},
388+
},
389+
{
390+
Name: "tag3",
391+
Annotations: map[string]string{
392+
"opendatahub.io/runtime-image-metadata": `
393+
[
394+
{
395+
"display_name": "CZ ěščřžýáíé",
396+
"metadata": {
397+
"tags": ["tag3"],
398+
"display_name": "CZ ěščřžýáíé",
399+
"pull_policy": "IfNotPresent"
400+
},
401+
"schema_name": "runtime-image"
402+
}
403+
]
404+
`,
405+
},
406+
From: &corev1.ObjectReference{
407+
Kind: "DockerImage",
408+
Name: "quay.io/opendatahub/test3",
409+
},
410+
},
411+
},
412+
},
413+
},
414+
},
320415
}
321416

322417
for _, testCase := range testCases {
@@ -443,3 +538,44 @@ var _ = Describe("Runtime images ConfigMap should be mounted", func() {
443538
}
444539
})
445540
})
541+
542+
var _ = Describe("FormatKeyName function", func() {
543+
It("should format key names correctly", func() {
544+
var tests = []struct {
545+
input string
546+
expected string
547+
}{
548+
// Cases with valid characters
549+
{"foo", "foo.json"},
550+
{"foo-bar", "foo-bar.json"},
551+
{"foo__bar", "foo__bar.json"},
552+
{"FOO_-BAR-999", "foo_-bar-999.json"},
553+
{"some.name_with-numbers-123", "some.name_with-numbers-123.json"},
554+
{"_leading_underscore", "_leading_underscore.json"},
555+
{"trailing_underscore_", "trailing_underscore_.json"},
556+
{"_-_leading_and_trailing_", "_-_leading_and_trailing_.json"},
557+
558+
// Cases with invalid characters
559+
{"@@@", ""},
560+
{"foo bar", "foo-bar.json"},
561+
{"!@#$%^&*()", ""},
562+
{" leading and trailing spaces ", "leading-and-trailing-spaces.json"},
563+
{" !@#$ invalid chars & valid ones", "invalid-chars-valid-ones.json"},
564+
{"CZ ěščřžýáíé", "cz.json"},
565+
{" --FOO Bar-- ", "foo-bar.json"},
566+
567+
// Edge cases
568+
{"", ""},
569+
{"-", ""},
570+
{"--", ""},
571+
{".", "..json"},
572+
{"_", "_.json"},
573+
{"... ---___", "...-___.json"},
574+
}
575+
576+
for _, testCase := range tests {
577+
result := formatKeyName(testCase.input)
578+
Expect(result).To(Equal(testCase.expected), fmt.Sprintf("input: '%s'", testCase.input))
579+
}
580+
})
581+
})

0 commit comments

Comments
 (0)