Skip to content

Commit fa93621

Browse files
Akrogclaude
andcommitted
Console: Add OpenShift->OpenStack text replacement
To avoid having the console saying it's OpenShift Lightspeed we will be doing a replacement of the OpenShift word in the internationalization file. This mechanism is a bit hacky, but it's a patch that's easy to revert if we decide to. Add an init container that rewrites "OpenShift" references to "OpenStack" in the locales JSON file using awk. The awk script splits each line at the key-value boundary and only replaces in values, preserving i18n lookup keys. Using this mechanism should not be a problem for non English locales (at least according to Claude), since the i18n fallback mechanism works like this: 1. This plugin doesn't initialize i18next itself. It relies on the parent OpenShift Console (via ConsoleRemotePlugin in webpack.config.ts) to set up i18next globally. The plugin just uses useTranslation('plugin__lightspeed-console-plugin') to access its namespace. 2. When a non-English locale is requested, i18next looks for locales/<locale>/plugin__lightspeed-console-plugin.json. Since only locales/en/ exists, i18next falls back to English — this is standard i18next behavior where missing locale files cause a fallback to the default language. 3. Additionally, the translation keys in locales/en/plugin__lightspeed-console-plugin.json are the English strings themselves (e.g., "Close": "Close"). So even if the fallback mechanism somehow failed entirely, i18next's last resort is to display the key as-is — which in this case is already readable English text. As long as OLS console doesn't have a bug in their code that doesn't use the internationalization with a string and that string has OpenShift in the wording we should be fine. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 358df4b commit fa93621

6 files changed

Lines changed: 140 additions & 3 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
idx = index($0, "\": ")
3+
if (idx > 0) {
4+
key_part = substr($0, 1, idx + 2)
5+
val_part = substr($0, idx + 3)
6+
gsub(/OpenShift/, "OpenStack", val_part)
7+
gsub(/openshift/, "openstack", val_part)
8+
gsub(/OPENSHIFT/, "OPENSTACK", val_part)
9+
printf "%s%s\n", key_part, val_part
10+
} else { print }
11+
}

internal/controller/console_deployment.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,15 @@ func generateConsoleSelectorLabels() map[string]string {
3838
}
3939
}
4040

41+
// consoleLocalesFilename is the filename of the locales JSON file.
42+
const consoleLocalesFilename = "plugin__lightspeed-console-plugin.json"
43+
44+
// consoleLocalesPath is the path to the locales JSON file inside the console image.
45+
const consoleLocalesPath = "/usr/share/nginx/html/locales/en/" + consoleLocalesFilename
46+
4147
// buildConsoleDeploymentSpec builds the Deployment spec for the console plugin.
48+
// Includes an init container that rewrites OpenShift references to OpenStack
49+
// in the locales JSON file using an emptyDir volume.
4250
func buildConsoleDeploymentSpec(consoleImage string) appsv1.DeploymentSpec {
4351
replicas := int32(1)
4452
volumeDefaultMode := VolumeDefaultMode
@@ -61,6 +69,30 @@ func buildConsoleDeploymentSpec(consoleImage string) appsv1.DeploymentSpec {
6169
},
6270
},
6371
ServiceAccountName: ConsoleUIServiceAccountName,
72+
InitContainers: []corev1.Container{
73+
{
74+
Name: "rewrite-locales",
75+
Image: consoleImage,
76+
SecurityContext: &corev1.SecurityContext{
77+
AllowPrivilegeEscalation: toPtr(false),
78+
ReadOnlyRootFilesystem: toPtr(true),
79+
Capabilities: &corev1.Capabilities{
80+
Drop: []corev1.Capability{"ALL"},
81+
},
82+
},
83+
Command: []string{
84+
"sh", "-c",
85+
"awk '" + consoleLocalesRewriteAwk + "' " +
86+
consoleLocalesPath + " > /locales-rewrite/" + consoleLocalesFilename,
87+
},
88+
VolumeMounts: []corev1.VolumeMount{
89+
{
90+
Name: "locales-rewrite",
91+
MountPath: "/locales-rewrite",
92+
},
93+
},
94+
},
95+
},
6496
Containers: []corev1.Container{
6597
{
6698
Name: "lightspeed-console-plugin",
@@ -92,6 +124,12 @@ func buildConsoleDeploymentSpec(consoleImage string) appsv1.DeploymentSpec {
92124
Name: "nginx-temp",
93125
MountPath: "/tmp/nginx",
94126
},
127+
{
128+
Name: "locales-rewrite",
129+
MountPath: consoleLocalesPath,
130+
SubPath: consoleLocalesFilename,
131+
ReadOnly: true,
132+
},
95133
},
96134
},
97135
},
@@ -122,6 +160,12 @@ func buildConsoleDeploymentSpec(consoleImage string) appsv1.DeploymentSpec {
122160
EmptyDir: &corev1.EmptyDirVolumeSource{},
123161
},
124162
},
163+
{
164+
Name: "locales-rewrite",
165+
VolumeSource: corev1.VolumeSource{
166+
EmptyDir: &corev1.EmptyDirVolumeSource{},
167+
},
168+
},
125169
},
126170
},
127171
},

internal/controller/console_reconciler_test.go

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,29 @@ var _ = Describe("Console Plugin", func() {
7575
Expect(ports[0].Protocol).To(Equal(corev1.ProtocolTCP))
7676
})
7777

78-
It("should have TLS cert, nginx config, and nginx temp volume mounts", func() {
78+
It("should have TLS cert, nginx config, nginx temp, and locales-rewrite volume mounts", func() {
7979
mounts := spec.Template.Spec.Containers[0].VolumeMounts
80-
Expect(mounts).To(HaveLen(3))
80+
Expect(mounts).To(HaveLen(4))
8181

8282
var names []string
8383
for _, m := range mounts {
8484
names = append(names, m.Name)
8585
}
86-
Expect(names).To(ContainElements("lightspeed-console-plugin-cert", "nginx-config", "nginx-temp"))
86+
Expect(names).To(ContainElements("lightspeed-console-plugin-cert", "nginx-config", "nginx-temp", "locales-rewrite"))
87+
})
88+
89+
It("should mount locales-rewrite with SubPath at the locales file path", func() {
90+
mounts := spec.Template.Spec.Containers[0].VolumeMounts
91+
var found bool
92+
for _, m := range mounts {
93+
if m.Name == "locales-rewrite" {
94+
found = true
95+
Expect(m.MountPath).To(Equal(consoleLocalesPath))
96+
Expect(m.SubPath).To(Equal(consoleLocalesFilename))
97+
Expect(m.ReadOnly).To(BeTrue())
98+
}
99+
}
100+
Expect(found).To(BeTrue())
87101
})
88102

89103
It("should have TLS cert volume from secret", func() {
@@ -127,6 +141,50 @@ var _ = Describe("Console Plugin", func() {
127141
It("should use the console service account", func() {
128142
Expect(spec.Template.Spec.ServiceAccountName).To(Equal(ConsoleUIServiceAccountName))
129143
})
144+
145+
It("should have a locales-rewrite emptyDir volume", func() {
146+
volumes := spec.Template.Spec.Volumes
147+
var found bool
148+
for _, v := range volumes {
149+
if v.Name == "locales-rewrite" {
150+
found = true
151+
Expect(v.VolumeSource.EmptyDir).NotTo(BeNil())
152+
}
153+
}
154+
Expect(found).To(BeTrue())
155+
})
156+
157+
It("should have one init container for rewriting locales", func() {
158+
initContainers := spec.Template.Spec.InitContainers
159+
Expect(initContainers).To(HaveLen(1))
160+
Expect(initContainers[0].Name).To(Equal("rewrite-locales"))
161+
})
162+
163+
It("should use the same console image for the init container", func() {
164+
initContainer := spec.Template.Spec.InitContainers[0]
165+
mainContainer := spec.Template.Spec.Containers[0]
166+
Expect(initContainer.Image).To(Equal(mainContainer.Image))
167+
})
168+
169+
It("should have the init container command with awk for text replacement", func() {
170+
initContainer := spec.Template.Spec.InitContainers[0]
171+
Expect(initContainer.Command).To(HaveLen(3))
172+
Expect(initContainer.Command[0]).To(Equal("sh"))
173+
Expect(initContainer.Command[1]).To(Equal("-c"))
174+
cmd := initContainer.Command[2]
175+
Expect(cmd).To(ContainSubstring("awk"))
176+
Expect(cmd).To(ContainSubstring("OpenShift"))
177+
Expect(cmd).To(ContainSubstring("OpenStack"))
178+
Expect(cmd).To(ContainSubstring(consoleLocalesPath))
179+
Expect(cmd).To(ContainSubstring("/locales-rewrite/" + consoleLocalesFilename))
180+
})
181+
182+
It("should mount locales-rewrite volume in the init container", func() {
183+
initContainer := spec.Template.Spec.InitContainers[0]
184+
Expect(initContainer.VolumeMounts).To(HaveLen(1))
185+
Expect(initContainer.VolumeMounts[0].Name).To(Equal("locales-rewrite"))
186+
Expect(initContainer.VolumeMounts[0].MountPath).To(Equal("/locales-rewrite"))
187+
})
130188
})
131189

132190
Describe("buildConsolePluginSpec", func() {

internal/controller/constants.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,9 @@ var vectorDatabaseBuildScript string
224224

225225
//go:embed assets/console_nginx.conf.tmpl
226226
var consoleNginxConfigTemplate string
227+
228+
// consoleLocalesRewriteAwk is the awk script that performs case-preserving
229+
// OpenShift -> OpenStack replacement only in JSON values (after the first `": `).
230+
//
231+
//go:embed assets/console_locales_rewrite.awk
232+
var consoleLocalesRewriteAwk string

test/kuttl/common/openstack-lightspeed-instance/assert-openstack-lightspeed-instance.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,16 @@ spec:
189189
runAsNonRoot: true
190190
seccompProfile:
191191
type: RuntimeDefault
192+
initContainers:
193+
- name: rewrite-locales
194+
securityContext:
195+
allowPrivilegeEscalation: false
196+
readOnlyRootFilesystem: true
197+
capabilities:
198+
drop: ["ALL"]
199+
volumeMounts:
200+
- name: locales-rewrite
201+
mountPath: /locales-rewrite
192202
---
193203
apiVersion: v1
194204
kind: Service

test/kuttl/tests/update-openstacklightspeed/08-assert-openstacklightspeed-update.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ kind: Deployment
117117
metadata:
118118
name: lightspeed-console-plugin
119119
namespace: openstack-lightspeed
120+
spec:
121+
template:
122+
spec:
123+
initContainers:
124+
- name: rewrite-locales
125+
volumeMounts:
126+
- name: locales-rewrite
127+
mountPath: /locales-rewrite
120128
---
121129
apiVersion: v1
122130
kind: Service

0 commit comments

Comments
 (0)