feat: implement config validation and retry logic (Phase 1.3)
Add comprehensive input validation and exponential backoff retry logic to improve user experience and system reliability. ## Changes ### Validators Module (src/lib/validators.ts) - NEW - Add type guards for SealedSecret runtime validation - Add Kubernetes DNS-1123 name/key validators - Add PEM certificate format validator - Add detailed validation functions with error messages - Validate secret names, keys, and values - Validate plugin configuration ### Retry Logic (src/lib/retry.ts) - NEW - Implement exponential backoff with jitter - Add configurable retry options (max attempts, delays, backoff multiplier) - Add helper predicates for network/HTTP errors - Aggregate errors across retry attempts - Default: 3 attempts, 1s initial delay, 10s max delay ### Controller API (src/lib/controller.ts) - Add retry logic to fetchPublicCertificate - Split into fetchPublicCertificateOnce (internal) and public version - Automatic retry with 3 attempts on network errors ### UI Components (src/components/EncryptDialog.tsx) - Add input validation before encryption - Validate secret name (Kubernetes format) - Validate each key name (alphanumeric + hyphens/dots/underscores) - Validate each value (non-empty, < 1MB) - Show specific error messages for validation failures ## Validation Rules - **Secret Names:** DNS-1123 subdomain (lowercase, alphanumeric, hyphens, dots) - **Secret Keys:** Alphanumeric, hyphens, underscores, dots (1-253 chars) - **Secret Values:** Non-empty, < 1MB - **PEM Certificates:** Valid BEGIN/END CERTIFICATE markers ## Retry Strategy - **Exponential Backoff:** delay = initialDelay * (2 ^ attempt) - **Jitter:** ±25% random variation prevents thundering herd - **Max Delay:** Capped at 10 seconds - **Error Aggregation:** Collects all error messages for debugging ## Benefits - Clear, actionable error messages - Prevents invalid Kubernetes resources - Automatic recovery from transient failures - Kubernetes-compliant validation - Better user experience ## Verification - TypeScript: 0 errors - Linting: 0 errors - Build: Success (342.57 kB, 94.15 kB gzipped) - Build time: 3.87s Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -27,6 +27,7 @@ import React from 'react';
|
||||
import { fetchPublicCertificate, getPluginConfig } from '../lib/controller';
|
||||
import { encryptKeyValues, parsePublicKeyFromCert } from '../lib/crypto';
|
||||
import { SealedSecret } from '../lib/SealedSecretCRD';
|
||||
import { validateSecretKey, validateSecretName, validateSecretValue } from '../lib/validators';
|
||||
import { PlaintextValue, SealedSecretScope, SecretKeyValue } from '../types';
|
||||
|
||||
interface EncryptDialogProps {
|
||||
@@ -76,13 +77,37 @@ export function EncryptDialog({ open, onClose }: EncryptDialogProps) {
|
||||
};
|
||||
|
||||
const handleCreate = async () => {
|
||||
// Validate inputs
|
||||
if (!name) {
|
||||
enqueueSnackbar('Secret name is required', { variant: 'error' });
|
||||
// Validate secret name
|
||||
const nameValidation = validateSecretName(name);
|
||||
if (!nameValidation.valid) {
|
||||
enqueueSnackbar(nameValidation.error, { variant: 'error' });
|
||||
return;
|
||||
}
|
||||
|
||||
const validKeyValues = keyValues.filter(kv => kv.key && kv.value);
|
||||
// Validate key-value pairs
|
||||
const validKeyValues: Array<{ key: string; value: string }> = [];
|
||||
for (const kv of keyValues) {
|
||||
if (!kv.key && !kv.value) {
|
||||
continue; // Skip empty rows
|
||||
}
|
||||
|
||||
const keyValidation = validateSecretKey(kv.key);
|
||||
if (!keyValidation.valid) {
|
||||
enqueueSnackbar(`Invalid key "${kv.key}": ${keyValidation.error}`, { variant: 'error' });
|
||||
return;
|
||||
}
|
||||
|
||||
const valueValidation = validateSecretValue(kv.value);
|
||||
if (!valueValidation.valid) {
|
||||
enqueueSnackbar(`Invalid value for key "${kv.key}": ${valueValidation.error}`, {
|
||||
variant: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
validKeyValues.push({ key: kv.key, value: kv.value });
|
||||
}
|
||||
|
||||
if (validKeyValues.length === 0) {
|
||||
enqueueSnackbar('At least one key-value pair is required', { variant: 'error' });
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user