mirror of
https://github.com/DI0IK/homepage-plus.git
synced 2025-07-13 16:30:31 +00:00
Merge pull request #448 from jameswynn/kubernetes
Support for Kubernetes and Longhorn
This commit is contained in:
commit
627ce179ef
33 changed files with 1789 additions and 43 deletions
|
@ -5,7 +5,12 @@ import path from "path";
|
|||
import yaml from "js-yaml";
|
||||
|
||||
import checkAndCopyConfig, { getSettings } from "utils/config/config";
|
||||
import { servicesFromConfig, servicesFromDocker, cleanServiceGroups } from "utils/config/service-helpers";
|
||||
import {
|
||||
servicesFromConfig,
|
||||
servicesFromDocker,
|
||||
cleanServiceGroups,
|
||||
servicesFromKubernetes
|
||||
} from "utils/config/service-helpers";
|
||||
import { cleanWidgetGroups, widgetsFromConfig } from "utils/config/widget-helpers";
|
||||
|
||||
export async function bookmarksResponse() {
|
||||
|
@ -44,19 +49,28 @@ export async function widgetsResponse() {
|
|||
}
|
||||
|
||||
export async function servicesResponse() {
|
||||
let discoveredServices;
|
||||
let discoveredDockerServices;
|
||||
let discoveredKubernetesServices;
|
||||
let configuredServices;
|
||||
let initialSettings;
|
||||
|
||||
try {
|
||||
discoveredServices = cleanServiceGroups(await servicesFromDocker());
|
||||
if (discoveredServices?.length === 0) {
|
||||
discoveredDockerServices = cleanServiceGroups(await servicesFromDocker());
|
||||
if (discoveredDockerServices?.length === 0) {
|
||||
console.debug("No containers were found with homepage labels.");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to discover services, please check docker.yaml for errors or remove example entries.");
|
||||
if (e) console.error(e.toString());
|
||||
discoveredServices = [];
|
||||
discoveredDockerServices = [];
|
||||
}
|
||||
|
||||
try {
|
||||
discoveredKubernetesServices = cleanServiceGroups(await servicesFromKubernetes());
|
||||
} catch (e) {
|
||||
console.error("Failed to discover services, please check kubernetes.yaml for errors or remove example entries.");
|
||||
if (e) console.error(e.toString());
|
||||
discoveredKubernetesServices = [];
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -76,7 +90,11 @@ export async function servicesResponse() {
|
|||
}
|
||||
|
||||
const mergedGroupsNames = [
|
||||
...new Set([discoveredServices.map((group) => group.name), configuredServices.map((group) => group.name)].flat()),
|
||||
...new Set([
|
||||
discoveredDockerServices.map((group) => group.name),
|
||||
discoveredKubernetesServices.map((group) => group.name),
|
||||
configuredServices.map((group) => group.name),
|
||||
].flat()),
|
||||
];
|
||||
|
||||
const sortedGroups = [];
|
||||
|
@ -84,12 +102,17 @@ export async function servicesResponse() {
|
|||
const definedLayouts = initialSettings.layout ? Object.keys(initialSettings.layout) : null;
|
||||
|
||||
mergedGroupsNames.forEach((groupName) => {
|
||||
const discoveredGroup = discoveredServices.find((group) => group.name === groupName) || { services: [] };
|
||||
const discoveredDockerGroup = discoveredDockerServices.find((group) => group.name === groupName) || { services: [] };
|
||||
const discoveredKubernetesGroup = discoveredKubernetesServices.find((group) => group.name === groupName) || { services: [] };
|
||||
const configuredGroup = configuredServices.find((group) => group.name === groupName) || { services: [] };
|
||||
|
||||
const mergedGroup = {
|
||||
name: groupName,
|
||||
services: [...discoveredGroup.services, ...configuredGroup.services].filter((service) => service),
|
||||
services: [
|
||||
...discoveredDockerGroup.services,
|
||||
...discoveredKubernetesGroup.services,
|
||||
...configuredGroup.services
|
||||
].filter((service) => service),
|
||||
};
|
||||
|
||||
if (definedLayouts) {
|
||||
|
|
30
src/utils/config/kubernetes.js
Normal file
30
src/utils/config/kubernetes.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import path from "path";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
import yaml from "js-yaml";
|
||||
import { KubeConfig } from "@kubernetes/client-node";
|
||||
|
||||
import checkAndCopyConfig from "utils/config/config";
|
||||
|
||||
export default function getKubeConfig() {
|
||||
checkAndCopyConfig("kubernetes.yaml");
|
||||
|
||||
const configFile = path.join(process.cwd(), "config", "kubernetes.yaml");
|
||||
const configData = readFileSync(configFile, "utf8");
|
||||
const config = yaml.load(configData);
|
||||
const kc = new KubeConfig();
|
||||
|
||||
switch (config?.mode) {
|
||||
case 'cluster':
|
||||
kc.loadFromCluster();
|
||||
break;
|
||||
case 'default':
|
||||
kc.loadFromDefault();
|
||||
break;
|
||||
case 'disabled':
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return kc;
|
||||
}
|
|
@ -4,9 +4,14 @@ import path from "path";
|
|||
import yaml from "js-yaml";
|
||||
import Docker from "dockerode";
|
||||
import * as shvl from "shvl";
|
||||
import { NetworkingV1Api } from "@kubernetes/client-node";
|
||||
|
||||
import createLogger from "utils/logger";
|
||||
import checkAndCopyConfig from "utils/config/config";
|
||||
import getDockerArguments from "utils/config/docker";
|
||||
import getKubeConfig from "utils/config/kubernetes";
|
||||
|
||||
const logger = createLogger("service-helpers");
|
||||
|
||||
export async function servicesFromConfig() {
|
||||
checkAndCopyConfig("services.yaml");
|
||||
|
@ -108,6 +113,89 @@ 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}`;
|
||||
}
|
||||
|
||||
export async function servicesFromKubernetes() {
|
||||
const ANNOTATION_BASE = 'gethomepage.dev';
|
||||
const ANNOTATION_WIDGET_BASE = `${ANNOTATION_BASE}/widget.`;
|
||||
const ANNOTATION_POD_SELECTOR = `${ANNOTATION_BASE}/pod-selector`;
|
||||
|
||||
checkAndCopyConfig("kubernetes.yaml");
|
||||
|
||||
try {
|
||||
const kc = getKubeConfig();
|
||||
if (!kc) {
|
||||
return [];
|
||||
}
|
||||
const networking = kc.makeApiClient(NetworkingV1Api);
|
||||
|
||||
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);
|
||||
return null;
|
||||
});
|
||||
if (!ingressList) {
|
||||
return [];
|
||||
}
|
||||
const services = ingressList.items
|
||||
.filter((ingress) => ingress.metadata.annotations && ingress.metadata.annotations[`${ANNOTATION_BASE}/enabled`] === 'true')
|
||||
.map((ingress) => {
|
||||
const constructedService = {
|
||||
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",
|
||||
icon: ingress.metadata.annotations[`${ANNOTATION_BASE}/icon`] || '',
|
||||
description: ingress.metadata.annotations[`${ANNOTATION_BASE}/description`] || '',
|
||||
};
|
||||
if (ingress.metadata.annotations[ANNOTATION_POD_SELECTOR]) {
|
||||
constructedService.podSelector = ingress.metadata.annotations[ANNOTATION_POD_SELECTOR];
|
||||
}
|
||||
Object.keys(ingress.metadata.annotations).forEach((annotation) => {
|
||||
if (annotation.startsWith(ANNOTATION_WIDGET_BASE)) {
|
||||
shvl.set(constructedService, annotation.replace(`${ANNOTATION_BASE}/`, ""), ingress.metadata.annotations[annotation]);
|
||||
}
|
||||
});
|
||||
|
||||
return constructedService;
|
||||
});
|
||||
|
||||
const mappedServiceGroups = [];
|
||||
|
||||
services.forEach((serverService) => {
|
||||
let serverGroup = mappedServiceGroups.find((searchedGroup) => searchedGroup.name === serverService.group);
|
||||
if (!serverGroup) {
|
||||
mappedServiceGroups.push({
|
||||
name: serverService.group,
|
||||
services: [],
|
||||
});
|
||||
serverGroup = mappedServiceGroups[mappedServiceGroups.length - 1];
|
||||
}
|
||||
|
||||
const { name: serviceName, group: serverServiceGroup, ...pushedService } = serverService;
|
||||
const result = {
|
||||
name: serviceName,
|
||||
...pushedService,
|
||||
};
|
||||
|
||||
serverGroup.services.push(result);
|
||||
});
|
||||
|
||||
return mappedServiceGroups;
|
||||
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export function cleanServiceGroups(groups) {
|
||||
return groups.map((serviceGroup) => ({
|
||||
name: serviceGroup.name,
|
||||
|
@ -123,7 +211,10 @@ export function cleanServiceGroups(groups) {
|
|||
container,
|
||||
currency, // coinmarketcap widget
|
||||
symbols,
|
||||
defaultinterval
|
||||
defaultinterval,
|
||||
namespace, // kubernetes widget
|
||||
app,
|
||||
podSelector
|
||||
} = cleanedService.widget;
|
||||
|
||||
cleanedService.widget = {
|
||||
|
@ -141,6 +232,11 @@ export function cleanServiceGroups(groups) {
|
|||
if (server) cleanedService.widget.server = server;
|
||||
if (container) cleanedService.widget.container = container;
|
||||
}
|
||||
if (type === "kubernetes") {
|
||||
if (namespace) cleanedService.widget.namespace = namespace;
|
||||
if (app) cleanedService.widget.app = app;
|
||||
if (podSelector) cleanedService.widget.podSelector = podSelector;
|
||||
}
|
||||
}
|
||||
|
||||
return cleanedService;
|
||||
|
@ -171,5 +267,15 @@ export default async function getServiceWidget(group, service) {
|
|||
}
|
||||
}
|
||||
|
||||
const kubernetesServices = await servicesFromKubernetes();
|
||||
const kubernetesServiceGroup = kubernetesServices.find((g) => g.name === group);
|
||||
if (kubernetesServiceGroup) {
|
||||
const kubernetesServiceEntry = kubernetesServiceGroup.services.find((s) => s.name === service);
|
||||
if (kubernetesServiceEntry) {
|
||||
const { widget } = kubernetesServiceEntry;
|
||||
return widget;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue