Skip to content

Commit 70a5d35

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 1a7bbd9 commit 70a5d35

4 files changed

Lines changed: 132 additions & 3 deletions

File tree

internal/controller/console_deployment.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,29 @@ 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+
47+
// consoleLocalesRewriteAwk is the awk script that performs case-preserving
48+
// OpenShift -> OpenStack replacement only in JSON values (after the first `": `).
49+
const consoleLocalesRewriteAwk = `{
50+
idx = index($0, "\": ")
51+
if (idx > 0) {
52+
key_part = substr($0, 1, idx + 2)
53+
val_part = substr($0, idx + 3)
54+
gsub(/OpenShift/, "OpenStack", val_part)
55+
gsub(/openshift/, "openstack", val_part)
56+
gsub(/OPENSHIFT/, "OPENSTACK", val_part)
57+
printf "%s%s\n", key_part, val_part
58+
} else { print }
59+
}`
60+
4161
// buildConsoleDeploymentSpec builds the Deployment spec for the console plugin.
62+
// Includes an init container that rewrites OpenShift references to OpenStack
63+
// in the locales JSON file using an emptyDir volume.
4264
func buildConsoleDeploymentSpec(consoleImage string) appsv1.DeploymentSpec {
4365
replicas := int32(1)
4466
volumeDefaultMode := VolumeDefaultMode
@@ -55,6 +77,27 @@ func buildConsoleDeploymentSpec(consoleImage string) appsv1.DeploymentSpec {
5577
},
5678
Spec: corev1.PodSpec{
5779
ServiceAccountName: ConsoleUIServiceAccountName,
80+
InitContainers: []corev1.Container{
81+
{
82+
Name: "rewrite-locales",
83+
Image: consoleImage,
84+
SecurityContext: &corev1.SecurityContext{
85+
AllowPrivilegeEscalation: toPtr(false),
86+
ReadOnlyRootFilesystem: toPtr(true),
87+
},
88+
Command: []string{
89+
"sh", "-c",
90+
"awk '" + consoleLocalesRewriteAwk + "' " +
91+
consoleLocalesPath + " > /locales-rewrite/" + consoleLocalesFilename,
92+
},
93+
VolumeMounts: []corev1.VolumeMount{
94+
{
95+
Name: "locales-rewrite",
96+
MountPath: "/locales-rewrite",
97+
},
98+
},
99+
},
100+
},
58101
Containers: []corev1.Container{
59102
{
60103
Name: "lightspeed-console-plugin",
@@ -86,6 +129,12 @@ func buildConsoleDeploymentSpec(consoleImage string) appsv1.DeploymentSpec {
86129
Name: "nginx-temp",
87130
MountPath: "/tmp/nginx",
88131
},
132+
{
133+
Name: "locales-rewrite",
134+
MountPath: consoleLocalesPath,
135+
SubPath: consoleLocalesFilename,
136+
ReadOnly: true,
137+
},
89138
},
90139
},
91140
},
@@ -116,6 +165,12 @@ func buildConsoleDeploymentSpec(consoleImage string) appsv1.DeploymentSpec {
116165
EmptyDir: &corev1.EmptyDirVolumeSource{},
117166
},
118167
},
168+
{
169+
Name: "locales-rewrite",
170+
VolumeSource: corev1.VolumeSource{
171+
EmptyDir: &corev1.EmptyDirVolumeSource{},
172+
},
173+
},
119174
},
120175
},
121176
},

internal/controller/console_reconciler_test.go

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

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

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

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

134192
Describe("buildConsolePluginSpec", func() {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,14 @@ kind: Deployment
153153
metadata:
154154
name: lightspeed-console-plugin
155155
namespace: openstack-lightspeed
156+
spec:
157+
template:
158+
spec:
159+
initContainers:
160+
- name: rewrite-locales
161+
volumeMounts:
162+
- name: locales-rewrite
163+
mountPath: /locales-rewrite
156164
---
157165
apiVersion: v1
158166
kind: Service

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ kind: Deployment
103103
metadata:
104104
name: lightspeed-console-plugin
105105
namespace: openstack-lightspeed
106+
spec:
107+
template:
108+
spec:
109+
initContainers:
110+
- name: rewrite-locales
111+
volumeMounts:
112+
- name: locales-rewrite
113+
mountPath: /locales-rewrite
106114
---
107115
apiVersion: v1
108116
kind: Service

0 commit comments

Comments
 (0)