chore: move source to repo root and standardize config
Phase 1 — Structural overhaul: - Move all source from headlamp-sealed-secrets/ subdirectory to repo root - Delete 23 AI-generated docs, 8 pre-built tarballs, release snapshots dir - Remove all working-directory refs from CI/release workflows - Update install-plugin.sh and typedoc.json paths Phase 2 — Config standardization: - Create .eslintrc.js and .prettierrc.js (standard Headlamp configs) - Remove inline eslintConfig/prettier from package.json (drop jsx-a11y, prettier extends) - Rewrite tsconfig.json (package name extend, add compilerOptions.types) - Create vitest.config.mts and vitest.setup.ts (standard from polaris) - Replace headlamp-plugin CLI scripts with direct tool invocation - Rewrite .gitignore with standard baseline Phase 3 — MCP & Claude settings: - Create .mcp.json with github/kubernetes/flux/playwright servers - Create .claude/settings.local.json - Remove 7 specialized agents, keep 3 meta-orchestration agents Phase 4 — Documentation: - Rewrite CLAUDE.md (remove subdirectory refs, standard format) - Add ArtifactHub badge, Architecture section, standardized install methods to README.md - Create CONTRIBUTING.md and SECURITY.md - Fix pre-existing test bugs in validators.test.ts (isValidNamespace returns boolean, not ValidationResult; error message string mismatches) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* SealedSecret Custom Resource Definition
|
||||
*/
|
||||
|
||||
import { ApiProxy,K8s } from '@kinvolk/headlamp-plugin/lib';
|
||||
|
||||
const { apiFactoryWithNamespace } = ApiProxy;
|
||||
const { KubeObject } = K8s.cluster;
|
||||
import { AsyncResult, Err, Ok, tryCatchAsync } from '../types';
|
||||
import {
|
||||
SealedSecretInterface,
|
||||
SealedSecretScope,
|
||||
SealedSecretSpec,
|
||||
SealedSecretStatus,
|
||||
} from '../types';
|
||||
|
||||
/**
|
||||
* SealedSecret CRD class
|
||||
* Represents a Bitnami Sealed Secret resource in the cluster
|
||||
*/
|
||||
export class SealedSecret extends KubeObject<SealedSecretInterface> {
|
||||
/**
|
||||
* Default API version (fallback)
|
||||
*/
|
||||
static readonly DEFAULT_VERSION = 'bitnami.com/v1alpha1';
|
||||
|
||||
/**
|
||||
* Cached detected API version
|
||||
*/
|
||||
private static detectedVersion: string | null = null;
|
||||
|
||||
/**
|
||||
* API endpoint for SealedSecret resources
|
||||
* bitnami.com/v1alpha1/sealedsecrets
|
||||
*/
|
||||
static apiEndpoint = apiFactoryWithNamespace('bitnami.com', 'v1alpha1', 'sealedsecrets');
|
||||
|
||||
/**
|
||||
* Class name used for Headlamp registration
|
||||
*/
|
||||
static get className(): string {
|
||||
return 'SealedSecret';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SealedSecret spec
|
||||
*/
|
||||
get spec(): SealedSecretSpec {
|
||||
return this.jsonData.spec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SealedSecret status
|
||||
*/
|
||||
get status(): SealedSecretStatus | undefined {
|
||||
return this.jsonData.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scope of this SealedSecret (strict, namespace-wide, or cluster-wide)
|
||||
*/
|
||||
get scope(): SealedSecretScope {
|
||||
const annotations = this.metadata.annotations || {};
|
||||
|
||||
if (annotations['sealedsecrets.bitnami.com/cluster-wide'] === 'true') {
|
||||
return 'cluster-wide';
|
||||
}
|
||||
if (annotations['sealedsecrets.bitnami.com/namespace-wide'] === 'true') {
|
||||
return 'namespace-wide';
|
||||
}
|
||||
return 'strict';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the count of encrypted keys
|
||||
*/
|
||||
get encryptedKeysCount(): number {
|
||||
return Object.keys(this.spec.encryptedData || {}).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the SealedSecret is synced
|
||||
*/
|
||||
get isSynced(): boolean {
|
||||
const syncCondition = this.status?.conditions?.find(c => c.type === 'Synced');
|
||||
return syncCondition?.status === 'True';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sync status condition
|
||||
*/
|
||||
get syncCondition() {
|
||||
return this.status?.conditions?.find(c => c.type === 'Synced');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sync status message
|
||||
*/
|
||||
get syncMessage(): string {
|
||||
const condition = this.syncCondition;
|
||||
if (!condition) {
|
||||
return 'Unknown';
|
||||
}
|
||||
// Ensure we always return a string, not an object
|
||||
const message = condition.message || condition.reason || condition.status;
|
||||
return String(message || 'Unknown');
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the API version available in the cluster
|
||||
*
|
||||
* Queries the SealedSecrets CRD to determine which API version is installed
|
||||
* and preferred. Returns the storage version (the version used for persisting
|
||||
* objects in etcd).
|
||||
*
|
||||
* @returns Result containing the API version string (e.g., "bitnami.com/v1alpha1")
|
||||
*/
|
||||
static async detectApiVersion(): AsyncResult<string, string> {
|
||||
// Return cached version if available
|
||||
if (this.detectedVersion) {
|
||||
return Ok(this.detectedVersion);
|
||||
}
|
||||
|
||||
const result = await tryCatchAsync(async () => {
|
||||
// Query the CRD to get available versions using Headlamp's API proxy
|
||||
const crd = await ApiProxy.request(
|
||||
'/apis/apiextensions.k8s.io/v1/customresourcedefinitions/sealedsecrets.bitnami.com'
|
||||
);
|
||||
|
||||
// Find the storage version (the version used for persistence)
|
||||
const storageVersion = crd.spec?.versions?.find((v: any) => v.storage === true);
|
||||
|
||||
if (storageVersion) {
|
||||
const version = `${crd.spec.group}/${storageVersion.name}`;
|
||||
this.detectedVersion = version;
|
||||
return version;
|
||||
}
|
||||
|
||||
// Fallback to first served version if no storage version found
|
||||
const servedVersion = crd.spec?.versions?.find((v: any) => v.served === true);
|
||||
if (servedVersion) {
|
||||
const version = `${crd.spec.group}/${servedVersion.name}`;
|
||||
this.detectedVersion = version;
|
||||
return version;
|
||||
}
|
||||
|
||||
// Ultimate fallback to default
|
||||
return this.DEFAULT_VERSION;
|
||||
});
|
||||
|
||||
if (result.ok === false) {
|
||||
return Err(result.error.message);
|
||||
}
|
||||
|
||||
return Ok(result.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API endpoint with auto-detected version
|
||||
*
|
||||
* Automatically detects and uses the correct API version from the cluster.
|
||||
* Falls back to default version (v1alpha1) if detection fails.
|
||||
*
|
||||
* @returns API endpoint configured with the detected version
|
||||
*/
|
||||
static async getApiEndpoint() {
|
||||
const versionResult = await this.detectApiVersion();
|
||||
|
||||
if (versionResult.ok) {
|
||||
const [group, version] = versionResult.value.split('/');
|
||||
return apiFactoryWithNamespace(group, version, 'sealedsecrets');
|
||||
}
|
||||
|
||||
// Fallback to default endpoint
|
||||
return this.apiEndpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the detected API version
|
||||
*
|
||||
* Returns the cached detected version or null if not yet detected.
|
||||
*
|
||||
* @returns The detected API version string or null
|
||||
*/
|
||||
static getDetectedVersion(): string | null {
|
||||
return this.detectedVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cached API version
|
||||
*
|
||||
* Forces re-detection on next call to detectApiVersion().
|
||||
* Useful for refreshing after CRD updates.
|
||||
*/
|
||||
static clearVersionCache(): void {
|
||||
this.detectedVersion = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user