Skip to content

Commit a668623

Browse files
committed
fix(validators): per-label 63-char check; drop trailing period
Bugbot follow-ups on PR #573. 1. validateHelmReleaseName length error trailed with a period. The ValidationResult contract is 'no trailing period — call sites compose them into sentences', and InstallWizard does exactly that ('Release name {error}.'), producing '...limit..' in the UI when a user typed > 53 chars. 2. validateRFC1123Subdomain only checked total length (≤ 253) and the 'segments' regex, so a single 200-char label passed client-side and was rejected only server-side. K8s' IsDNS1123Subdomain enforces ≤ 63 PER label on top of the 253 total. Add the per-label check and pin it under 'each dot-separated label must be at most 63 characters'. Made-with: Cursor
1 parent 362a6bf commit a668623

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

packages/k8s-ui/src/utils/validators.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,27 @@ describe('validateRFC1123Subdomain', () => {
8181
expect(r.valid).toBe(false)
8282
if (!r.valid) expect(r.error).toMatch(/253/)
8383
})
84+
85+
it('rejects single labels longer than 63 chars even when total is under 253', () => {
86+
// K8s' IsDNS1123Subdomain enforces ≤ 63 chars per dot-
87+
// separated label on top of the 253-char total. Without the
88+
// per-label check, a single 200-char label slipped through
89+
// client-side and was rejected only server-side.
90+
const huge = 'a'.repeat(200)
91+
const r = validateRFC1123Subdomain(huge)
92+
expect(r.valid).toBe(false)
93+
if (!r.valid) expect(r.error).toMatch(/63/)
94+
})
95+
96+
it('accepts a 63-char label at the boundary', () => {
97+
expect(validateRFC1123Subdomain('a'.repeat(63)).valid).toBe(true)
98+
})
99+
100+
it('rejects only the offending label when other labels are fine', () => {
101+
const r = validateRFC1123Subdomain(`ok.${'a'.repeat(64)}.also-ok`)
102+
expect(r.valid).toBe(false)
103+
if (!r.valid) expect(r.error).toMatch(/63/)
104+
})
84105
})
85106

86107
describe('validateHelmReleaseName', () => {
@@ -103,6 +124,17 @@ describe('validateHelmReleaseName', () => {
103124
it('accepts a name exactly 53 characters long (boundary)', () => {
104125
expect(validateHelmReleaseName('a'.repeat(53)).valid).toBe(true)
105126
})
127+
128+
it('error string has no trailing period (call-site composition convention)', () => {
129+
// ValidationResult contract: error strings have no trailing
130+
// period — the caller composes them into sentences and adds
131+
// its own. InstallWizard renders `Release name {error}.` so a
132+
// trailing period in this error string produced "...limit.." in
133+
// the UI when a user exceeded the cap.
134+
const r = validateHelmReleaseName('a'.repeat(54))
135+
if (r.valid) throw new Error('expected invalid')
136+
expect(r.error.endsWith('.')).toBe(false)
137+
})
106138
})
107139

108140
describe('validatePort', () => {

packages/k8s-ui/src/utils/validators.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ export function validateRFC1123Subdomain(input: string): ValidationResult {
9090
error: 'must contain only lowercase letters, numbers, hyphens, and dots, and each segment must start/end with a letter or number',
9191
}
9292
}
93+
// Per-label cap: K8s' IsDNS1123Subdomain enforces ≤ 63 chars
94+
// PER dot-separated label on top of the 253-char total. The
95+
// regex above doesn't catch that; without this check a single
96+
// 200-char label passes here and only fails server-side.
97+
for (const label of input.split('.')) {
98+
if (label.length > RFC1123_LABEL_MAX) {
99+
return {
100+
valid: false,
101+
error: `each dot-separated label must be at most ${RFC1123_LABEL_MAX} characters (got ${label.length} for "${label}")`,
102+
}
103+
}
104+
}
93105
return { valid: true }
94106
}
95107

@@ -105,7 +117,10 @@ export function validateHelmReleaseName(input: string): ValidationResult {
105117
if (input.length > HELM_RELEASE_NAME_MAX) {
106118
return {
107119
valid: false,
108-
error: `must be at most ${HELM_RELEASE_NAME_MAX} characters (got ${input.length}). Helm caps release names so derived resource names fit under K8s' 63-char limit.`,
120+
// No trailing period — call sites compose this into a
121+
// sentence and append their own. Bugbot caught the
122+
// double-period UI in InstallWizard.
123+
error: `must be at most ${HELM_RELEASE_NAME_MAX} characters (got ${input.length}); Helm caps release names so derived resource names fit under K8s' 63-char limit`,
109124
}
110125
}
111126
return validateRFC1123Subdomain(input)

0 commit comments

Comments
 (0)