From a15b5bd6922ec55e15db3d11878e764d938c57cd Mon Sep 17 00:00:00 2001 From: djeinstine Date: Wed, 6 Nov 2024 11:15:52 +0000 Subject: [PATCH] First step of integrating gateway-api with homepage. --- src/utils/config/kubernetes.js | 20 ++- src/utils/config/service-helpers.js | 205 +++++++++++----------- src/utils/kubernetes/kubernetes-routes.js | 180 +++++++++++++++++++ 3 files changed, 305 insertions(+), 100 deletions(-) create mode 100644 src/utils/kubernetes/kubernetes-routes.js diff --git a/src/utils/config/kubernetes.js b/src/utils/config/kubernetes.js index 14dcb082..c8106cfb 100644 --- a/src/utils/config/kubernetes.js +++ b/src/utils/config/kubernetes.js @@ -11,13 +11,31 @@ const extractKubeData = (config) => { //kubeconfig const kc = new KubeConfig(); kc.loadFromCluster() + //route let route="ingress"; if (config?.route=="gateway"){ route="gateway"; } + + //traefik + let traefik=true; + if (config?.traefik=="disable"){ + traefik=false; + } + + //traefik + let metrics=true; + if (config?.metrics=="disable"){ + metrics=false; + } + + //return return {"config":kc, - "route":route}; + "route":route, + "traefik":traefik, + "metrics":metrics + }; } export default function getKubeArguments() { diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index 42676195..138ed907 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -3,12 +3,12 @@ import path from "path"; import yaml from "js-yaml"; import Docker from "dockerode"; -import { CustomObjectsApi, NetworkingV1Api, ApiextensionsV1Api } from "@kubernetes/client-node"; +import { ApiextensionsV1Api } from "@kubernetes/client-node"; import createLogger from "utils/logger"; import checkAndCopyConfig, { CONF_DIR, getSettings, substituteEnvironmentVars } from "utils/config/config"; import getDockerArguments from "utils/config/docker"; -import getKubeArguments from "utils/config/kubernetes"; +import {getUrlSchema,getRouteList} from "utils/kubernetes/kubernetes-routes"; import * as shvl from "utils/config/shvl"; const logger = createLogger("service-helpers"); @@ -151,12 +151,12 @@ export async function servicesFromDocker() { return mappedServiceGroups; } -function getUrlFromIngress(ingress) { - const urlHost = ingress.spec.rules[0].host; - const urlPath = ingress.spec.rules[0].http.paths[0].path; - const urlSchema = ingress.spec.tls ? "https" : "http"; - return `${urlSchema}://${urlHost}${urlPath}`; -} +// function getUrlFromroute(route) { +// const urlHost = route.spec.rules[0].host; +// const urlPath = route.spec.rules[0].http.paths[0].path; +// const urlSchema = route.spec.tls ? "https" : "http"; +// return `${urlSchema}://${urlHost}${urlPath}`; +// } export async function checkCRD(kc, name) { const apiExtensions = kc.makeApiClient(ApiextensionsV1Api); @@ -186,115 +186,122 @@ export async function servicesFromKubernetes() { checkAndCopyConfig("kubernetes.yaml"); try { - const kc = getKubeArguments().config; - if (!kc) { + // const kc = getKubeArguments().config; + // if (!kc) { + // return []; + // } + // const networking = kc.makeApiClient(NetworkingV1Api); + // const crd = kc.makeApiClient(CustomObjectsApi); + + // const routeList = await networking + // .listrouteForAllNamespaces(null, null, null, null) + // .then((response) => response.body) + // .catch((error) => { + // logger.error("Error getting routees: %d %s %s", error.statusCode, error.body, error.response); + // logger.debug(error); + // return null; + // }); + + // const traefikContainoExists = await checkCRD(kc, "routeroutes.traefik.containo.us"); + // const traefikExists = await checkCRD(kc, "routeroutes.traefik.io"); + + // const traefikrouteListContaino = await crd + // .listClusterCustomObject("traefik.containo.us", "v1alpha1", "routeroutes") + // .then((response) => response.body) + // .catch(async (error) => { + // if (traefikContainoExists) { + // logger.error( + // "Error getting traefik routees from traefik.containo.us: %d %s %s", + // error.statusCode, + // error.body, + // error.response, + // ); + // logger.debug(error); + // } + + // return []; + // }); + + // const traefikrouteListIo = await crd + // .listClusterCustomObject("traefik.io", "v1alpha1", "routeroutes") + // .then((response) => response.body) + // .catch(async (error) => { + // if (traefikExists) { + // logger.error( + // "Error getting traefik routees from traefik.io: %d %s %s", + // error.statusCode, + // error.body, + // error.response, + // ); + // logger.debug(error); + // } + + // return []; + // }); + + // const traefikrouteList = [...(traefikrouteListContaino?.items ?? []), ...(traefikrouteListIo?.items ?? [])]; + + // if (traefikrouteList.length > 0) { + // const traefikServices = traefikrouteList.filter( + // (route) => route.metadata.annotations && route.metadata.annotations[`${ANNOTATION_BASE}/href`], + // ); + // routeList.items.push(...traefikServices); + // } + + // if (!routeList) { + // return []; + // } + const routeList = await getRouteList(); + + if (!routeList) { return []; } - const networking = kc.makeApiClient(NetworkingV1Api); - const crd = kc.makeApiClient(CustomObjectsApi); - const ingressList = await networking - .listIngressForAllNamespaces(null, null, null, null) - .then((response) => response.body) - .catch((error) => { - logger.error("Error getting ingresses: %d %s %s", error.statusCode, error.body, error.response); - logger.debug(error); - return null; - }); - - const traefikContainoExists = await checkCRD(kc, "ingressroutes.traefik.containo.us"); - const traefikExists = await checkCRD(kc, "ingressroutes.traefik.io"); - - const traefikIngressListContaino = await crd - .listClusterCustomObject("traefik.containo.us", "v1alpha1", "ingressroutes") - .then((response) => response.body) - .catch(async (error) => { - if (traefikContainoExists) { - logger.error( - "Error getting traefik ingresses from traefik.containo.us: %d %s %s", - error.statusCode, - error.body, - error.response, - ); - logger.debug(error); - } - - return []; - }); - - const traefikIngressListIo = await crd - .listClusterCustomObject("traefik.io", "v1alpha1", "ingressroutes") - .then((response) => response.body) - .catch(async (error) => { - if (traefikExists) { - logger.error( - "Error getting traefik ingresses from traefik.io: %d %s %s", - error.statusCode, - error.body, - error.response, - ); - logger.debug(error); - } - - return []; - }); - - const traefikIngressList = [...(traefikIngressListContaino?.items ?? []), ...(traefikIngressListIo?.items ?? [])]; - - if (traefikIngressList.length > 0) { - const traefikServices = traefikIngressList.filter( - (ingress) => ingress.metadata.annotations && ingress.metadata.annotations[`${ANNOTATION_BASE}/href`], - ); - ingressList.items.push(...traefikServices); - } - - if (!ingressList) { - return []; - } - const services = ingressList.items + const services = routeList .filter( - (ingress) => - ingress.metadata.annotations && - ingress.metadata.annotations[`${ANNOTATION_BASE}/enabled`] === "true" && - (!ingress.metadata.annotations[`${ANNOTATION_BASE}/instance`] || - ingress.metadata.annotations[`${ANNOTATION_BASE}/instance`] === instanceName || - `${ANNOTATION_BASE}/instance.${instanceName}` in ingress.metadata.annotations), + (route) => + route.metadata.annotations && + route.metadata.annotations[`${ANNOTATION_BASE}/enabled`] === "true" && + (!route.metadata.annotations[`${ANNOTATION_BASE}/instance`] || + route.metadata.annotations[`${ANNOTATION_BASE}/instance`] === instanceName || + `${ANNOTATION_BASE}/instance.${instanceName}` in route.metadata.annotations), ) - .map((ingress) => { + .map((route) => { let constructedService = { - app: ingress.metadata.annotations[`${ANNOTATION_BASE}/app`] || ingress.metadata.name, - namespace: ingress.metadata.namespace, - href: ingress.metadata.annotations[`${ANNOTATION_BASE}/href`] || getUrlFromIngress(ingress), - name: ingress.metadata.annotations[`${ANNOTATION_BASE}/name`] || ingress.metadata.name, - group: ingress.metadata.annotations[`${ANNOTATION_BASE}/group`] || "Kubernetes", - weight: ingress.metadata.annotations[`${ANNOTATION_BASE}/weight`] || "0", - icon: ingress.metadata.annotations[`${ANNOTATION_BASE}/icon`] || "", - description: ingress.metadata.annotations[`${ANNOTATION_BASE}/description`] || "", + app: route.metadata.annotations[`${ANNOTATION_BASE}/app`] || route.metadata.name, + namespace: route.metadata.namespace, + href: route.metadata.annotations[`${ANNOTATION_BASE}/href`] || getUrlSchema(route), + name: route.metadata.annotations[`${ANNOTATION_BASE}/name`] || route.metadata.name, + group: route.metadata.annotations[`${ANNOTATION_BASE}/group`] || "Kubernetes", + weight: route.metadata.annotations[`${ANNOTATION_BASE}/weight`] || "0", + icon: route.metadata.annotations[`${ANNOTATION_BASE}/icon`] || "", + description: route.metadata.annotations[`${ANNOTATION_BASE}/description`] || "", external: false, type: "service", }; - if (ingress.metadata.annotations[`${ANNOTATION_BASE}/external`]) { + console.log("href is ",constructedService.href); + if (route.metadata.annotations[`${ANNOTATION_BASE}/external`]) { constructedService.external = - String(ingress.metadata.annotations[`${ANNOTATION_BASE}/external`]).toLowerCase() === "true"; + String(route.metadata.annotations[`${ANNOTATION_BASE}/external`]).toLowerCase() === "true"; } - if (ingress.metadata.annotations[`${ANNOTATION_BASE}/pod-selector`] !== undefined) { - constructedService.podSelector = ingress.metadata.annotations[`${ANNOTATION_BASE}/pod-selector`]; + if (route.metadata.annotations[`${ANNOTATION_BASE}/pod-selector`] !== undefined) { + constructedService.podSelector = route.metadata.annotations[`${ANNOTATION_BASE}/pod-selector`]; } - if (ingress.metadata.annotations[`${ANNOTATION_BASE}/ping`]) { - constructedService.ping = ingress.metadata.annotations[`${ANNOTATION_BASE}/ping`]; + if (route.metadata.annotations[`${ANNOTATION_BASE}/ping`]) { + constructedService.ping = route.metadata.annotations[`${ANNOTATION_BASE}/ping`]; } - if (ingress.metadata.annotations[`${ANNOTATION_BASE}/siteMonitor`]) { - constructedService.siteMonitor = ingress.metadata.annotations[`${ANNOTATION_BASE}/siteMonitor`]; + if (route.metadata.annotations[`${ANNOTATION_BASE}/siteMonitor`]) { + constructedService.siteMonitor = route.metadata.annotations[`${ANNOTATION_BASE}/siteMonitor`]; } - if (ingress.metadata.annotations[`${ANNOTATION_BASE}/statusStyle`]) { - constructedService.statusStyle = ingress.metadata.annotations[`${ANNOTATION_BASE}/statusStyle`]; + if (route.metadata.annotations[`${ANNOTATION_BASE}/statusStyle`]) { + constructedService.statusStyle = route.metadata.annotations[`${ANNOTATION_BASE}/statusStyle`]; } - Object.keys(ingress.metadata.annotations).forEach((annotation) => { + Object.keys(route.metadata.annotations).forEach((annotation) => { if (annotation.startsWith(ANNOTATION_WIDGET_BASE)) { shvl.set( constructedService, annotation.replace(`${ANNOTATION_BASE}/`, ""), - ingress.metadata.annotations[annotation], + route.metadata.annotations[annotation], ); } }); @@ -305,7 +312,7 @@ export async function servicesFromKubernetes() { logger.error("Error attempting k8s environment variable substitution."); logger.debug(e); } - + console.log(constructedService) return constructedService; }); diff --git a/src/utils/kubernetes/kubernetes-routes.js b/src/utils/kubernetes/kubernetes-routes.js new file mode 100644 index 00000000..fcbfa23f --- /dev/null +++ b/src/utils/kubernetes/kubernetes-routes.js @@ -0,0 +1,180 @@ + + +import { CustomObjectsApi, NetworkingV1Api, CoreV1Api } from "@kubernetes/client-node"; +import getKubeArguments from "utils/config/kubernetes"; + +const kubeArguments = getKubeArguments(); +const kc = kubeArguments.config; + +const apiGroup = 'gateway.networking.k8s.io'; +const version = 'v1'; + +let crd; +let core; +let networking; +let routingType; +let traefik; + +const getSchemaFromGateway = async (gatewayRef) => { + try { + const gateway = await crd.getNamespacedCustomObject(apiGroup, version, gatewayRef.namespace,"gateways",gatewayRef.name); + const listener = gateway.body.spec.listeners.filter((listener)=>listener.name==gatewayRef.sectionName)[0]; + return listener.protocol.toLowerCase(); + } catch (err) { + console.error(err); + } +}; + +function getUrlFromHttpRoute(ingress) { + const urlHost = ingress.spec.hostnames[0]; + const urlPath = ingress.spec.rules[0].matches[0].path.value; + //const urlSchema = await getSchemaFromGateway(ingress.spec.parentRefs[0]) ? "https" : "http"; + const urlSchema = "https" + return `${urlSchema}://${urlHost}${urlPath}`; +} + + +function getUrlFromIngress(ingress) { + const urlHost = ingress.spec.rules[0].host; + const urlPath = ingress.spec.rules[0].http.paths[0].path; + const urlSchema = ingress.spec.tls ? "https" : "http"; + return `${urlSchema}://${urlHost}${urlPath}`; +} + +async function getHttpRouteList(){ + const httpRouteList = new Array(); + + const namespaces = await core.listNamespace() + .then((response) => response.body.items.map(ns => ns.metadata.name)) + .catch((error) => { + logger.error("Error getting namespaces: %d %s %s", error.statusCode, error.body, error.response); + logger.debug(error); + return null; + }) + + if (namespaces){ + // Iterate over each namespace + for (const namespace of namespaces) { + try { + // Fetch the httproute from one namespaces + const customObject = await crd.listNamespacedCustomObject(apiGroup,version,namespace,'httproutes'); + if (customObject.body.items.length !== 0){ + httpRouteList.push(customObject.body.items[0]); + } + + } catch (err) { + console.error(`Error fetching httproutes objects in namespace "${namespace}":`, err.body || err.message); + } + } + } + return httpRouteList; +} + +async function getIngressList(){ + + const ingressList = await networking + .listIngressForAllNamespaces(null, null, null, null) + .then((response) => response.body) + .catch((error) => { + logger.error("Error getting ingresses: %d %s %s", error.statusCode, error.body, error.response); + logger.debug(error); + return null; + }); + + if (traefik){ + const traefikContainoExists = await checkCRD(kc, "ingressroutes.traefik.containo.us"); + const traefikExists = await checkCRD(kc, "ingressroutes.traefik.io"); + + const traefikIngressListContaino = await crd + .listClusterCustomObject("traefik.containo.us", "v1alpha1", "ingressroutes") + .then((response) => response.body) + .catch(async (error) => { + if (traefikContainoExists) { + logger.error( + "Error getting traefik ingresses from traefik.containo.us: %d %s %s", + error.statusCode, + error.body, + error.response, + ); + logger.debug(error); + } + + return []; + }); + + const traefikIngressListIo = await crd + .listClusterCustomObject("traefik.io", "v1alpha1", "ingressroutes") + .then((response) => response.body) + .catch(async (error) => { + if (traefikExists) { + logger.error( + "Error getting traefik ingresses from traefik.io: %d %s %s", + error.statusCode, + error.body, + error.response, + ); + logger.debug(error); + } + + return []; + }); + + const traefikIngressList = [...(traefikIngressListContaino?.items ?? []), ...(traefikIngressListIo?.items ?? [])]; + + if (traefikIngressList.length > 0) { + const traefikServices = traefikIngressList.filter( + (ingress) => ingress.metadata.annotations && ingress.metadata.annotations[`${ANNOTATION_BASE}/href`], + ); + ingressList.items.push(...traefikServices); + } + } + + return ingressList.items; +} + +export async function getRouteList(){ + + let routeList = new Array(); + + if (!kc) { + return []; + } + + crd = kc.makeApiClient(CustomObjectsApi); + core = kc.makeApiClient(CoreV1Api); + networking = kc.makeApiClient(NetworkingV1Api); + + routingType = kubeArguments.route; + traefik = kubeArguments.traefik; + + + switch (routingType) { + case "ingress": + routeList = await getIngressList(); + break; + case "gateway": + routeList = await getHttpRouteList(); + break; + default: + routeList = await getIngressList(); + } + + return routeList; +} + +export function getUrlSchema(route) { + let urlSchema; + + switch (routingType) { + case "ingress": + urlSchema = getUrlFromIngress(route); + break; + case "gateway": + urlSchema = getUrlFromHttpRoute(route); + break; + default: + urlSchema = getUrlFromIngress(route); + } + + return urlSchema; + } \ No newline at end of file