UNPKG

@kinvolk/headlamp-plugin

Version:

The needed infrastructure for building Headlamp plugins.

178 lines (163 loc) 5.1 kB
/* * Copyright 2025 The Kubernetes Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { ClusterDomainClaim, KnativeDomainMapping } from '../resources/knative'; export function getClusterDomainClaim( dm: KnativeDomainMapping, clusterDomainClaims: ClusterDomainClaim[] | null, cluster: string, namespace: string ): { state: 'unknown' | 'missing' | 'present'; claim: ClusterDomainClaim | null; } { const clusters = [cluster]; if (!clusterDomainClaims) { return { state: 'unknown', claim: null }; } const host = dm.host?.trim(); if (!host) { return { state: 'unknown', claim: null }; } // DomainMappingSection is single-cluster today, but keep this safe if it ever becomes multi-cluster. const effectiveCluster = dm.cluster || cluster; const requireClusterMatch = clusters.length > 1; const claim = clusterDomainClaims.find( cdc => (!requireClusterMatch || cdc.cluster === effectiveCluster) && cdc.metadata?.name === host && cdc.targetNamespace === namespace ) ?? null; if (claim && !claim.cluster && effectiveCluster) { claim.cluster = effectiveCluster; } return claim ? { state: 'present', claim } : { state: 'missing', claim: null }; } function isValidDomain(host: string): boolean { // very permissive host validation; rely on API for authoritative validation const h = host.trim(); if (h.length < 1 || h.length > 253) return false; // simple label check (letters, digits, hyphen; labels do not start/end with hyphen) return h.split('.').every(label => /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i.test(label)); } export async function ensureClusterDomainClaim(opts: { host: string; cluster: string; namespace: string; }) { try { await ClusterDomainClaim.apiEndpoint.post( { apiVersion: ClusterDomainClaim.apiVersion, kind: ClusterDomainClaim.kind, metadata: { name: opts.host }, spec: { namespace: opts.namespace }, }, {}, opts.cluster ); } catch (e: unknown) { const error = e as { message?: string } | undefined; const msg = String(error?.message || ''); // Ignore if already exists or conflicts (loosely check for 409/AlreadyExists messages) if (!/AlreadyExists|409|exists/i.test(msg)) { throw e; } } } /** * Creates a ClusterDomainClaim and then a DomainMapping. * Throws an error if validation or API calls fail. */ export async function createDomainMapping( domainInput: string, cluster: string, namespace: string, serviceName: string ) { const host = domainInput.trim(); if (!host) { throw new Error('Please enter a domain name'); } if (!isValidDomain(host)) { throw new Error('Invalid domain name format'); } if (!cluster) { throw new Error('No cluster available'); } // Create Cluster Domain Claim await ensureClusterDomainClaim({ host, cluster, namespace }); // Create respective Domain Mapping await KnativeDomainMapping.apiEndpoint.post( { apiVersion: KnativeDomainMapping.apiVersion, kind: KnativeDomainMapping.kind, metadata: { name: host, namespace, }, spec: { ref: { apiVersion: 'serving.knative.dev/v1', kind: 'Service', name: serviceName, namespace, }, }, }, {}, cluster ); } /** * Creates a ClusterDomainClaim. * Throws an error if validation or API calls fail. */ export async function createClusterDomainClaim(dm: KnativeDomainMapping, namespace: string) { const host = dm.host?.trim(); if (!host) { throw new Error('Domain name is missing'); } try { await ensureClusterDomainClaim({ host, cluster: dm.cluster, namespace }); // Add dummy annotation to trigger DomainMapping reconciliation try { await dm.patch({ metadata: { annotations: { 'knative.headlamp.dev/reconciledAt': new Date().toISOString(), }, }, }); } catch (e2: unknown) { const error2 = e2 as { message?: string } | undefined; const detail2 = error2?.message?.trim(); throw new Error( detail2 ? `Failed to annotate DomainMapping: ${detail2}` : 'Failed to annotate DomainMapping' ); } } catch (e: unknown) { const error = e as { message?: string } | undefined; const detail = error?.message?.trim(); throw new Error( detail ? `Failed to create ClusterDomainClaim: ${detail}` : 'Failed to create ClusterDomainClaim' ); } }