/** * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ import { KubeObject } from "../kube-object"; import { autoBind } from "../../utils"; import { IMetrics, metricsApi } from "./metrics.api"; import { KubeApi } from "../kube-api"; import type { KubeJsonApiData } from "../kube-json-api"; import { isClusterPageContext } from "../../utils/cluster-id-url-parsing"; import type { RequireExactlyOne } from "type-fest"; export class IngressApi extends KubeApi { } export function getMetricsForIngress(ingress: string, namespace: string): Promise { const opts = { category: "ingress", ingress, namespace }; return metricsApi.getMetrics({ bytesSentSuccess: opts, bytesSentFailure: opts, requestDurationSeconds: opts, responseDurationSeconds: opts, }, { namespace, }); } export interface IIngressMetrics { [metric: string]: T; bytesSentSuccess: T; bytesSentFailure: T; requestDurationSeconds: T; responseDurationSeconds: T; } export interface ILoadBalancerIngress { hostname?: string; ip?: string; } // extensions/v1beta1 interface IExtensionsBackend { serviceName: string; servicePort: number | string; } // networking.k8s.io/v1 interface INetworkingBackend { service: IIngressService; } export type IIngressBackend = IExtensionsBackend | INetworkingBackend; export interface IIngressService { name: string; port: { name?: string; number?: number; }; } export const getBackendServiceNamePort = (backend: IIngressBackend) => { // .service is available with networking.k8s.io/v1, otherwise using extensions/v1beta1 interface const serviceName = "service" in backend ? backend.service.name : backend.serviceName; // Port is specified either with a number or name const servicePort = "service" in backend ? backend.service.port.number ?? backend.service.port.name : backend.servicePort; return { serviceName, servicePort }; }; export interface Ingress { spec: { tls: { secretName: string; }[]; rules?: { host?: string; http: { paths: { path?: string; backend: IIngressBackend; }[]; }; }[]; // extensions/v1beta1 backend?: IExtensionsBackend; /** * The default backend which is exactly on of: * - service * - resource */ defaultBackend?: RequireExactlyOne; }; status: { loadBalancer: { ingress: ILoadBalancerIngress[]; }; }; } export class Ingress extends KubeObject { static kind = "Ingress"; static namespaced = true; static apiBase = "/apis/networking.k8s.io/v1/ingresses"; constructor(data: KubeJsonApiData) { super(data); autoBind(this); } getRoutes() { const { spec: { tls, rules }} = this; if (!rules) return []; let protocol = "http"; const routes: string[] = []; if (tls && tls.length > 0) { protocol += "s"; } rules.map(rule => { const host = rule.host ? rule.host : "*"; if (rule.http && rule.http.paths) { rule.http.paths.forEach(path => { const { serviceName, servicePort } = getBackendServiceNamePort(path.backend); routes.push(`${protocol}://${host}${path.path || "/"} ⇢ ${serviceName}:${servicePort}`); }); } }); return routes; } getServiceNamePort(): IExtensionsBackend { const { spec: { backend, defaultBackend } = {}} = this; const serviceName = defaultBackend?.service?.name ?? backend?.serviceName; const servicePort = defaultBackend?.service?.port.number ?? defaultBackend?.service?.port.name ?? backend?.servicePort; return { serviceName, servicePort, }; } getHosts() { const { spec: { rules }} = this; if (!rules) return []; return rules.filter(rule => rule.host).map(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 && rules.length > 0) { if (rules.some(rule => Object.prototype.hasOwnProperty.call(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() { const { status: { loadBalancer = { ingress: [] }}} = this; return (loadBalancer.ingress ?? []).map(address => ( address.hostname || address.ip )); } } let ingressApi: IngressApi; if (isClusterPageContext()) { ingressApi = new IngressApi({ objectConstructor: Ingress, // Add fallback for Kubernetes <1.19 checkPreferredVersion: true, fallbackApiBases: ["/apis/extensions/v1beta1/ingresses"], }); } export { ingressApi, };