1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/common/k8s-api/endpoints/ingress.api.ts
Sebastian Malton 1be1b4418f Make KubeApi consume its dependencies while not breaking the extension API
Signed-off-by: Sebastian Malton <sebastian@malton.name>
2023-01-05 13:28:56 -05:00

214 lines
5.2 KiB
TypeScript

/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object";
import { hasTypedProperty, isString, iter } from "../../utils";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api";
import type { RequireExactlyOne } from "type-fest";
export class IngressApi extends KubeApi<Ingress> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {
super(deps, {
...opts ?? {},
objectConstructor: Ingress,
// Add fallback for Kubernetes <1.19
checkPreferredVersion: true,
fallbackApiBases: ["/apis/extensions/v1beta1/ingresses"],
});
}
}
export interface ILoadBalancerIngress {
hostname?: string;
ip?: string;
}
// extensions/v1beta1
export interface ExtensionsBackend {
serviceName?: string;
servicePort?: number | string;
}
// networking.k8s.io/v1
export interface NetworkingBackend {
service?: IngressService;
}
export type IngressBackend = (ExtensionsBackend | NetworkingBackend) & {
resource?: TypedLocalObjectReference;
};
export interface IngressService {
name: string;
port: RequireExactlyOne<{
name: string;
number: number;
}>;
}
function isExtensionsBackend(backend: IngressBackend): backend is ExtensionsBackend {
return hasTypedProperty(backend, "serviceName", isString);
}
/**
* Format an ingress backend into the name of the service and port
* @param backend The ingress target
*/
export function getBackendServiceNamePort(backend: IngressBackend | undefined): string {
if (!backend) {
return "<unknown>";
}
if (isExtensionsBackend(backend)) {
return `${backend.serviceName}:${backend.servicePort}`;
}
if (backend.service) {
const { name, port } = backend.service;
return `${name}:${port.number ?? port.name}`;
}
return "<unknown>";
}
export interface HTTPIngressPath {
pathType: "Exact" | "Prefix" | "ImplementationSpecific";
path?: string;
backend?: IngressBackend;
}
export interface HTTPIngressRuleValue {
paths: HTTPIngressPath[];
}
export interface IngressRule {
host?: string;
http?: HTTPIngressRuleValue;
}
export interface IngressSpec {
tls: {
secretName: string;
}[];
rules?: IngressRule[];
// extensions/v1beta1
backend?: ExtensionsBackend;
/**
* The default backend which is exactly on of:
* - service
* - resource
*/
defaultBackend?: RequireExactlyOne<NetworkingBackend & {
resource: {
apiGroup: string;
kind: string;
name: string;
};
}>;
}
export interface IngressStatus {
loadBalancer: {
ingress?: ILoadBalancerIngress[];
};
}
export class Ingress extends KubeObject<
NamespaceScopedMetadata,
IngressStatus,
IngressSpec
> {
static readonly kind = "Ingress";
static readonly namespaced = true;
static readonly apiBase = "/apis/networking.k8s.io/v1/ingresses";
getRules() {
return this.spec.rules ?? [];
}
getRoutes(): string[] {
return computeRouteDeclarations(this).map(({ url, service }) => `${url}${service}`);
}
getServiceNamePort(): ExtensionsBackend | undefined {
const { spec: { backend, defaultBackend } = {}} = this;
const serviceName = defaultBackend?.service?.name ?? backend?.serviceName;
const servicePort = defaultBackend?.service?.port.number ?? defaultBackend?.service?.port.name ?? backend?.servicePort;
if (!serviceName || !servicePort) {
return undefined;
}
return {
serviceName,
servicePort,
};
}
getHosts() {
const { spec: { rules = [] }} = this;
return [...iter.filterMap(rules, rule => rule.host)];
}
getPorts() {
const ports: number[] = [];
const { spec: { tls, rules = [], backend, defaultBackend }} = this;
const httpPort = 80;
const tlsPort = 443;
// Note: not using the port name (string)
const servicePort = defaultBackend?.service?.port.number ?? backend?.servicePort;
if (rules.length > 0) {
if (rules.some(rule => rule.http)) {
ports.push(httpPort);
}
} else if (servicePort !== undefined) {
ports.push(Number(servicePort));
}
if (tls && tls.length > 0) {
ports.push(tlsPort);
}
return ports.join(", ");
}
getLoadBalancers() {
return this.status?.loadBalancer?.ingress?.map(address => (
address.hostname || address.ip
)) ?? [];
}
}
export interface ComputedIngressRoute {
displayAsLink: boolean;
pathname: string;
url: string;
service: string;
}
export function computeRuleDeclarations(ingress: Ingress, rule: IngressRule): ComputedIngressRoute[] {
const { host = "*", http: { paths } = { paths: [] }} = rule;
const protocol = (ingress.spec?.tls?.length ?? 0) === 0
? "http"
: "https";
return paths.map(({ path = "/", backend }) => ({
displayAsLink: !host.includes("*"),
pathname: path,
url: `${protocol}://${host}${path}`,
service: getBackendServiceNamePort(backend),
}));
}
export function computeRouteDeclarations(ingress: Ingress): ComputedIngressRoute[] {
return ingress.getRules().flatMap(rule => computeRuleDeclarations(ingress, rule));
}