feat: implement API version detection and compatibility (Phase 2.4)

Add automatic detection of SealedSecrets CRD API version from cluster.
The plugin now adapts to installed versions (v1alpha1, v1, etc.) and
provides warnings when CRD is missing or non-default versions are used.

Changes:
- Add detectApiVersion() to SealedSecretCRD class
  - Queries CRD definition from Kubernetes API
  - Uses storage version (canonical version for etcd)
  - Caches result to avoid repeated API calls
  - Falls back to v1alpha1 if detection fails

- Create VersionWarning component
  - Auto-detects version on mount
  - Shows error alert for missing CRD (with install instructions)
  - Shows info alert for non-default versions
  - Provides retry button for failed detections
  - Configurable detail level (showDetails prop)

- Integrate version warnings into UI
  - SealedSecretList: minimal warnings (errors only)
  - SettingsPage: detailed version info always shown

- Add version management methods
  - getApiEndpoint(): auto-versioned endpoint
  - getDetectedVersion(): get cached version
  - clearVersionCache(): force re-detection

Benefits:
- Future-proof: automatically supports new API versions
- Better UX: clear error messages with installation help
- Performance: version detected once and cached
- Version awareness: users see which API version is active

Build: 351.34 kB (96.75 kB gzipped), +2.88 kB (+0.8%)
Phase 2.4 complete. 7 of 14 phases done (50% milestone).

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:
2026-02-11 21:57:43 -05:00
parent 839fdd4819
commit 55aba7417c
6 changed files with 677 additions and 1 deletions
@@ -17,6 +17,7 @@ import { usePermission } from '../hooks/usePermissions';
import { SealedSecret } from '../lib/SealedSecretCRD';
import { SealedSecretScope } from '../types';
import { EncryptDialog } from './EncryptDialog';
import { VersionWarning } from './VersionWarning';
/**
* Format scope for display
@@ -75,6 +76,7 @@ export function SealedSecretList() {
<SectionBox
title="Sealed Secrets"
>
<VersionWarning autoDetect showDetails={false} />
<SectionFilterHeader
title=""
noNamespaceFilter={false}
@@ -11,6 +11,7 @@ import React from 'react';
import { getPluginConfig, savePluginConfig } from '../lib/controller';
import { PluginConfig } from '../types';
import { ControllerStatus } from './ControllerStatus';
import { VersionWarning } from './VersionWarning';
/**
* Settings page component
@@ -43,6 +44,9 @@ export function SettingsPage() {
your browser's local storage.
</Typography>
{/* API Version Detection */}
<VersionWarning autoDetect showDetails />
{/* Controller Health Status */}
<Box mb={3} p={2} bgcolor="background.paper" borderRadius={1} border={1} borderColor="divider">
<Typography variant="subtitle2" gutterBottom>
@@ -0,0 +1,130 @@
/**
* Version Warning Component
*
* Displays warnings about API version compatibility and issues.
*/
import { Alert, Box, Button, Link } from '@mui/material';
import React from 'react';
import { SealedSecret } from '../lib/SealedSecretCRD';
export interface VersionWarningProps {
/** Whether to auto-detect version on mount */
autoDetect?: boolean;
/** Whether to show detailed version information */
showDetails?: boolean;
}
/**
* Component that detects and displays API version information
*
* Shows warnings if:
* - CRD is not installed
* - Version detection fails
* - Using non-default version (informational)
*/
export function VersionWarning({ autoDetect = true, showDetails = false }: VersionWarningProps) {
const [loading, setLoading] = React.useState(true);
const [detectedVersion, setDetectedVersion] = React.useState<string | null>(null);
const [error, setError] = React.useState<string | null>(null);
const detectVersion = React.useCallback(async () => {
setLoading(true);
setError(null);
const result = await SealedSecret.detectApiVersion();
if (result.ok) {
setDetectedVersion(result.value);
setError(null);
} else if (result.ok === false) {
setDetectedVersion(null);
setError(result.error);
}
setLoading(false);
}, []);
React.useEffect(() => {
if (autoDetect) {
detectVersion();
}
}, [autoDetect, detectVersion]);
// Don't show anything while loading
if (loading) {
return null;
}
// Show error if detection failed
if (error) {
return (
<Box mb={2}>
<Alert severity="error" action={
<Button color="inherit" size="small" onClick={detectVersion}>
Retry
</Button>
}>
<strong>API Version Detection Failed</strong>
<br />
{error}
{error.includes('not found') && (
<>
<br />
<br />
Install Sealed Secrets with:{' '}
<code style={{ fontSize: '0.875em' }}>
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
</code>
<br />
Or visit:{' '}
<Link
href="https://github.com/bitnami-labs/sealed-secrets"
target="_blank"
rel="noopener noreferrer"
color="inherit"
>
github.com/bitnami-labs/sealed-secrets
</Link>
</>
)}
</Alert>
</Box>
);
}
// Show informational message if using non-default version
if (detectedVersion && detectedVersion !== SealedSecret.DEFAULT_VERSION) {
return (
<Box mb={2}>
<Alert severity="info">
<strong>API Version Detected</strong>
<br />
Using API version: <code>{detectedVersion}</code>
{showDetails && (
<>
<br />
Default version: <code>{SealedSecret.DEFAULT_VERSION}</code>
</>
)}
</Alert>
</Box>
);
}
// Show success if explicitly showing details
if (showDetails && detectedVersion) {
return (
<Box mb={2}>
<Alert severity="success">
<strong>API Version Detected</strong>
<br />
Using API version: <code>{detectedVersion}</code>
</Alert>
</Box>
);
}
// Default: show nothing (version detected successfully)
return null;
}