DRY
This commit is contained in:
parent
36f4bdc26d
commit
0fe21d9bf7
@ -5,6 +5,7 @@ import { SettingsContext } from "utils/contexts/settings";
|
|||||||
import ResolvedIcon from "components/resolvedicon";
|
import ResolvedIcon from "components/resolvedicon";
|
||||||
|
|
||||||
export default function Item({ bookmark }) {
|
export default function Item({ bookmark }) {
|
||||||
|
console.log(bookmark);
|
||||||
const description = bookmark.description ?? new URL(bookmark.href).hostname;
|
const description = bookmark.description ?? new URL(bookmark.href).hostname;
|
||||||
const { settings } = useContext(SettingsContext);
|
const { settings } = useContext(SettingsContext);
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import {getSettings} from "utils/config/config";
|
import { getSettings } from "utils/config/config";
|
||||||
import {
|
import { bookmarksFromConfig, bookmarksFromDocker } from "utils/config/bookmark-helpers";
|
||||||
bookmarksFromConfig,
|
|
||||||
bookmarksFromDocker
|
|
||||||
} from "utils/config/bookmark-helpers";
|
|
||||||
import {
|
import {
|
||||||
servicesFromConfig,
|
servicesFromConfig,
|
||||||
servicesFromDocker,
|
servicesFromDocker,
|
||||||
@ -27,15 +24,13 @@ export async function bookmarksResponse() {
|
|||||||
let discoveredDockerBookmarks;
|
let discoveredDockerBookmarks;
|
||||||
let configuredBookmarks;
|
let configuredBookmarks;
|
||||||
let initialSettings;
|
let initialSettings;
|
||||||
console.debug("Bookmark response called");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
discoveredDockerBookmarks = await bookmarksFromDocker();
|
discoveredDockerBookmarks = await bookmarksFromDocker();
|
||||||
if (discoveredDockerBookmarks?.length === 0) {
|
if (discoveredDockerBookmarks?.length === 0) {
|
||||||
console.debug("No containers were found with bookmark labels");
|
console.debug("No containers were found with homepage bookmark labels");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to discover bookmarks, please check docker.yaml for errors or remove example entries.")
|
console.error("Failed to discover bookmarks, please check docker.yaml for errors or remove example entries.");
|
||||||
if (e) console.error(e.toString());
|
if (e) console.error(e.toString());
|
||||||
discoveredDockerBookmarks = [];
|
discoveredDockerBookmarks = [];
|
||||||
}
|
}
|
||||||
@ -62,28 +57,22 @@ export async function bookmarksResponse() {
|
|||||||
|
|
||||||
const mergedGroupsNames = [
|
const mergedGroupsNames = [
|
||||||
...new Set(
|
...new Set(
|
||||||
[
|
[discoveredDockerBookmarks.map((group) => group.name), configuredBookmarks.map((group) => group.name)].flat(),
|
||||||
discoveredDockerBookmarks.map((group) => group.name),
|
),
|
||||||
configuredBookmarks.map((group) => group.name),
|
];
|
||||||
].flat(),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
mergedGroupsNames.forEach((groupName) => {
|
mergedGroupsNames.forEach((groupName) => {
|
||||||
|
const discoveredDockerGroup = discoveredDockerBookmarks.find((group) => group.name === groupName) || {
|
||||||
const discoveredDockerGroup = discoveredDockerBookmarks.find((group) => group.name === groupName) || {
|
|
||||||
bookmarks: [],
|
bookmarks: [],
|
||||||
};
|
};
|
||||||
const configuredGroup = configuredBookmarks.find((group) => group.name === groupName) || { bookmarks: [] };
|
const configuredGroup = configuredBookmarks.find((group) => group.name === groupName) || { bookmarks: [] };
|
||||||
|
|
||||||
|
const mergedGroup = {
|
||||||
const mergedGroup = {
|
name: groupName,
|
||||||
name: groupName,
|
bookmarks: [...discoveredDockerGroup.bookmarks, ...configuredGroup.bookmarks].filter((bookmark) => bookmark),
|
||||||
bookmarks: [...discoveredDockerGroup.bookmarks, ...configuredGroup.bookmarks]
|
|
||||||
.filter((bookmark) => bookmark)
|
|
||||||
// .sort(compareBookmarks), // TODO is a sort needed?
|
// .sort(compareBookmarks), // TODO is a sort needed?
|
||||||
}
|
};
|
||||||
|
|
||||||
if (definedLayouts) {
|
if (definedLayouts) {
|
||||||
const layoutIndex = definedLayouts.findIndex((layout) => layout === mergedGroup.name);
|
const layoutIndex = definedLayouts.findIndex((layout) => layout === mergedGroup.name);
|
||||||
if (layoutIndex > -1) sortedGroups[layoutIndex] = mergedGroup;
|
if (layoutIndex > -1) sortedGroups[layoutIndex] = mergedGroup;
|
||||||
else unsortedGroups.push(mergedGroup);
|
else unsortedGroups.push(mergedGroup);
|
||||||
|
|||||||
@ -2,17 +2,24 @@ import { promises as fs } from "fs";
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import Docker from "dockerode";
|
|
||||||
import { CustomObjectsApi, NetworkingV1Api } from "@kubernetes/client-node";
|
|
||||||
|
|
||||||
|
import {
|
||||||
|
getDockerServers,
|
||||||
|
listDockerContainers,
|
||||||
|
getUrlFromIngress,
|
||||||
|
mapObjectsToGroup,
|
||||||
|
getIngressList,
|
||||||
|
ANNOTATION_BASE,
|
||||||
|
ANNOTATION_WIDGET_BASE,
|
||||||
|
} from "./integration-helpers";
|
||||||
|
|
||||||
|
import createLogger from "utils/logger";
|
||||||
import checkAndCopyConfig, { getSettings, CONF_DIR, substituteEnvironmentVars } from "utils/config/config";
|
import checkAndCopyConfig, { getSettings, CONF_DIR, substituteEnvironmentVars } from "utils/config/config";
|
||||||
import getDockerArguments from "utils/config/docker";
|
|
||||||
import getKubeConfig from "utils/config/kubernetes";
|
|
||||||
import { checkCRD, getUrlFromIngress } from "./service-helpers";
|
|
||||||
import * as shvl from "utils/config/shvl";
|
import * as shvl from "utils/config/shvl";
|
||||||
|
|
||||||
export async function bookmarksFromConfig() {
|
const logger = createLogger("bookmark-helpers");
|
||||||
|
|
||||||
|
export async function bookmarksFromConfig() {
|
||||||
checkAndCopyConfig("bookmarks.yaml");
|
checkAndCopyConfig("bookmarks.yaml");
|
||||||
|
|
||||||
const bookmarksYaml = path.join(CONF_DIR, "bookmarks.yaml");
|
const bookmarksYaml = path.join(CONF_DIR, "bookmarks.yaml");
|
||||||
@ -32,62 +39,9 @@ export async function bookmarksFromConfig() {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return bookmarksArray;
|
return bookmarksArray;
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDockerServers = async () => {
|
|
||||||
checkAndCopyConfig("docker.yaml");
|
|
||||||
const dockerYaml = path.join(CONF_DIR, "docker.yaml");
|
|
||||||
const rawDockerFileContents = await fs.readFile(dockerYaml, "utf8");
|
|
||||||
const dockerFileContents = substituteEnvironmentVars(rawDockerFileContents);
|
|
||||||
return yaml.load(dockerFileContents);
|
|
||||||
}
|
|
||||||
|
|
||||||
const listDockerContainers = async (servers, serverName) => {
|
|
||||||
const isSwarm = !!servers[serverName].swarm;
|
|
||||||
const docker = new Docker(getDockerArguments(serverName).conn);
|
|
||||||
const listProperties = { all: true };
|
|
||||||
const containers = await (isSwarm
|
|
||||||
? docker.listbookmarks(listProperties)
|
|
||||||
: docker.listContainers(listProperties));
|
|
||||||
|
|
||||||
// bad docker connections can result in a <Buffer ...> object?
|
|
||||||
// in any case, this ensures the result is the expected array
|
|
||||||
if (!Array.isArray(containers)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return containers;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapObjectsToGroup = (servers, objectName) => {
|
|
||||||
const mappedObjectGroups = [];
|
|
||||||
|
|
||||||
servers.forEach((server) => {
|
|
||||||
server[objectName].forEach((serverObject) => {
|
|
||||||
let serverGroup = mappedObjectGroups.find((searchedGroup) => searchedGroup.name === serverObject.group);
|
|
||||||
if (!serverGroup) {
|
|
||||||
const gObject = {name: serverObject.group}
|
|
||||||
gObject[objectName] = []
|
|
||||||
mappedObjectGroups.push(gObject);
|
|
||||||
serverGroup = mappedObjectGroups[mappedObjectGroups.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name: serverObjectName, group: serverObjectGroup, ...pushedObject } = serverObject;
|
|
||||||
const result = {
|
|
||||||
name: serverObjectName,
|
|
||||||
...pushedObject,
|
|
||||||
};
|
|
||||||
|
|
||||||
serverGroup[objectName].push(result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return mappedObjectGroups;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function bookmarksFromDocker() {
|
export async function bookmarksFromDocker() {
|
||||||
|
|
||||||
const servers = await getDockerServers();
|
const servers = await getDockerServers();
|
||||||
|
|
||||||
if (!servers) {
|
if (!servers) {
|
||||||
@ -109,9 +63,8 @@ export async function bookmarksFromDocker() {
|
|||||||
Object.keys(containerLabels).forEach((label) => {
|
Object.keys(containerLabels).forEach((label) => {
|
||||||
if (label.startsWith("homepage.bookmarks.")) {
|
if (label.startsWith("homepage.bookmarks.")) {
|
||||||
const containerNameNoSlash = containerName.replace(/^\//, "");
|
const containerNameNoSlash = containerName.replace(/^\//, "");
|
||||||
const cleanLabel = label.replace(`homepage.bookmarks.${containerNameNoSlash}.`, "");
|
|
||||||
|
|
||||||
let value = cleanLabel;
|
let value = label.replace(`homepage.bookmarks.${containerNameNoSlash}.`, "");
|
||||||
if (instanceName && value.startsWith(`instance.${instanceName}.`)) {
|
if (instanceName && value.startsWith(`instance.${instanceName}.`)) {
|
||||||
value = value.replace(`instance.${instanceName}.`, "");
|
value = value.replace(`instance.${instanceName}.`, "");
|
||||||
} else if (value.startsWith("instance.")) {
|
} else if (value.startsWith("instance.")) {
|
||||||
@ -140,76 +93,12 @@ export async function bookmarksFromDocker() {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const mappedBookmarkGroups = mapObjectsToGroup(bookmarkServers, 'bookmarks');
|
return mapObjectsToGroup(bookmarkServers, "bookmarks");
|
||||||
return mappedBookmarkGroups;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function bookmarksFromKubernetes() {
|
export async function bookmarksFromKubernetes() {
|
||||||
const ANNOTATION_BASE = "gethomepage.dev";
|
|
||||||
const ANNOTATION_WIDGET_BASE = `${ANNOTATION_BASE}/widget.`;
|
|
||||||
|
|
||||||
checkAndCopyConfig("kubernetes.yaml");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const kc = getKubeConfig();
|
const ingressList = await getIngressList();
|
||||||
if (!kc) {
|
|
||||||
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);
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
const traefikIngressList = [...(traefikIngressListContaino?.items ?? []), ...(traefikIngressListIo?.items ?? [])];
|
|
||||||
|
|
||||||
if (traefikIngressList.length > 0) {
|
|
||||||
const traefikbookmarks = traefikIngressList.filter(
|
|
||||||
(ingress) => ingress.metadata.annotations && ingress.metadata.annotations[`${ANNOTATION_BASE}/href`],
|
|
||||||
);
|
|
||||||
ingressList.items.push(...traefikbookmarks);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ingressList) {
|
if (!ingressList) {
|
||||||
return [];
|
return [];
|
||||||
@ -230,9 +119,6 @@ export async function bookmarksFromKubernetes() {
|
|||||||
description: ingress.metadata.annotations[`${ANNOTATION_BASE}/description`] || "",
|
description: ingress.metadata.annotations[`${ANNOTATION_BASE}/description`] || "",
|
||||||
type: "bookmark",
|
type: "bookmark",
|
||||||
};
|
};
|
||||||
if (ingress.metadata.annotations[`${ANNOTATION_BASE}/pod-selector`]) {
|
|
||||||
constructedBookmark.podSelector = ingress.metadata.annotations[`${ANNOTATION_BASE}/pod-selector`];
|
|
||||||
}
|
|
||||||
Object.keys(ingress.metadata.annotations).forEach((annotation) => {
|
Object.keys(ingress.metadata.annotations).forEach((annotation) => {
|
||||||
if (annotation.startsWith(ANNOTATION_WIDGET_BASE)) {
|
if (annotation.startsWith(ANNOTATION_WIDGET_BASE)) {
|
||||||
shvl.set(
|
shvl.set(
|
||||||
@ -279,4 +165,3 @@ export async function bookmarksFromKubernetes() {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
161
src/utils/config/integration-helpers.js
Normal file
161
src/utils/config/integration-helpers.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
import { promises as fs } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
import Docker from "dockerode";
|
||||||
|
import yaml from "js-yaml";
|
||||||
|
import { CustomObjectsApi, NetworkingV1Api, ApiextensionsV1Api } from "@kubernetes/client-node";
|
||||||
|
|
||||||
|
import getKubeConfig from "utils/config/kubernetes";
|
||||||
|
import createLogger from "utils/logger";
|
||||||
|
import getDockerArguments from "utils/config/docker";
|
||||||
|
import checkAndCopyConfig, { CONF_DIR, substituteEnvironmentVars } from "utils/config/config";
|
||||||
|
|
||||||
|
const logger = createLogger("integration-helpers");
|
||||||
|
|
||||||
|
export async function getDockerServers() {
|
||||||
|
checkAndCopyConfig("docker.yaml");
|
||||||
|
const dockerYaml = path.join(CONF_DIR, "docker.yaml");
|
||||||
|
const rawDockerFileContents = await fs.readFile(dockerYaml, "utf8");
|
||||||
|
const dockerFileContents = substituteEnvironmentVars(rawDockerFileContents);
|
||||||
|
return yaml.load(dockerFileContents);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listDockerContainers(servers, serverName) {
|
||||||
|
const isSwarm = !!servers[serverName].swarm;
|
||||||
|
const docker = new Docker(getDockerArguments(serverName).conn);
|
||||||
|
const listProperties = { all: true };
|
||||||
|
const containers = await (isSwarm ? docker.listbookmarks(listProperties) : docker.listContainers(listProperties));
|
||||||
|
|
||||||
|
// bad docker connections can result in a <Buffer ...> object?
|
||||||
|
// in any case, this ensures the result is the expected array
|
||||||
|
if (!Array.isArray(containers)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return containers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapObjectsToGroup(servers, objectName) {
|
||||||
|
const mappedObjectGroups = [];
|
||||||
|
|
||||||
|
servers.forEach((server) => {
|
||||||
|
server[objectName].forEach((serverObject) => {
|
||||||
|
let serverGroup = mappedObjectGroups.find((searchedGroup) => searchedGroup.name === serverObject.group);
|
||||||
|
if (!serverGroup) {
|
||||||
|
const gObject = { name: serverObject.group };
|
||||||
|
gObject[objectName] = [];
|
||||||
|
mappedObjectGroups.push(gObject);
|
||||||
|
serverGroup = mappedObjectGroups[mappedObjectGroups.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name: serverObjectName, group: serverObjectGroup, ...pushedObject } = serverObject;
|
||||||
|
const result = {
|
||||||
|
name: serverObjectName,
|
||||||
|
...pushedObject,
|
||||||
|
};
|
||||||
|
|
||||||
|
serverGroup[objectName].push(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return mappedObjectGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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 checkCRD(kc, name) {
|
||||||
|
const apiExtensions = kc.makeApiClient(ApiextensionsV1Api);
|
||||||
|
const exist = await apiExtensions
|
||||||
|
.readCustomResourceDefinitionStatus(name)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(async (error) => {
|
||||||
|
if (error.statusCode === 403) {
|
||||||
|
logger.error(
|
||||||
|
"Error checking if CRD %s exists. Make sure to add the following permission to your RBAC: %d %s %s",
|
||||||
|
name,
|
||||||
|
error.statusCode,
|
||||||
|
error.body.message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return exist;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ANNOTATION_BASE = "gethomepage.dev";
|
||||||
|
export const ANNOTATION_WIDGET_BASE = `${ANNOTATION_BASE}/widget.`;
|
||||||
|
|
||||||
|
export async function getIngressList() {
|
||||||
|
checkAndCopyConfig("kubernetes.yaml");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const kc = getKubeConfig();
|
||||||
|
if (!kc) {
|
||||||
|
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);
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,13 +2,19 @@ import { promises as fs } from "fs";
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import Docker from "dockerode";
|
|
||||||
import { CustomObjectsApi, NetworkingV1Api, ApiextensionsV1Api } from "@kubernetes/client-node";
|
import {
|
||||||
|
getDockerServers,
|
||||||
|
listDockerContainers,
|
||||||
|
getUrlFromIngress,
|
||||||
|
mapObjectsToGroup,
|
||||||
|
getIngressList,
|
||||||
|
ANNOTATION_BASE,
|
||||||
|
ANNOTATION_WIDGET_BASE,
|
||||||
|
} from "./integration-helpers";
|
||||||
|
|
||||||
import createLogger from "utils/logger";
|
import createLogger from "utils/logger";
|
||||||
import checkAndCopyConfig, { CONF_DIR, getSettings, substituteEnvironmentVars } from "utils/config/config";
|
import checkAndCopyConfig, { CONF_DIR, getSettings, substituteEnvironmentVars } from "utils/config/config";
|
||||||
import getDockerArguments from "utils/config/docker";
|
|
||||||
import getKubeConfig from "utils/config/kubernetes";
|
|
||||||
import * as shvl from "utils/config/shvl";
|
import * as shvl from "utils/config/shvl";
|
||||||
|
|
||||||
const logger = createLogger("service-helpers");
|
const logger = createLogger("service-helpers");
|
||||||
@ -48,12 +54,7 @@ export async function servicesFromConfig() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function servicesFromDocker() {
|
export async function servicesFromDocker() {
|
||||||
checkAndCopyConfig("docker.yaml");
|
const servers = await getDockerServers();
|
||||||
|
|
||||||
const dockerYaml = path.join(CONF_DIR, "docker.yaml");
|
|
||||||
const rawDockerFileContents = await fs.readFile(dockerYaml, "utf8");
|
|
||||||
const dockerFileContents = substituteEnvironmentVars(rawDockerFileContents);
|
|
||||||
const servers = yaml.load(dockerFileContents);
|
|
||||||
|
|
||||||
if (!servers) {
|
if (!servers) {
|
||||||
return [];
|
return [];
|
||||||
@ -65,18 +66,7 @@ export async function servicesFromDocker() {
|
|||||||
Object.keys(servers).map(async (serverName) => {
|
Object.keys(servers).map(async (serverName) => {
|
||||||
try {
|
try {
|
||||||
const isSwarm = !!servers[serverName].swarm;
|
const isSwarm = !!servers[serverName].swarm;
|
||||||
const docker = new Docker(getDockerArguments(serverName).conn);
|
const containers = await listDockerContainers(servers, serverName);
|
||||||
const listProperties = { all: true };
|
|
||||||
const containers = await (isSwarm
|
|
||||||
? docker.listServices(listProperties)
|
|
||||||
: docker.listContainers(listProperties));
|
|
||||||
|
|
||||||
// bad docker connections can result in a <Buffer ...> object?
|
|
||||||
// in any case, this ensures the result is the expected array
|
|
||||||
if (!Array.isArray(containers)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const discovered = containers.map((container) => {
|
const discovered = containers.map((container) => {
|
||||||
let constructedService = null;
|
let constructedService = null;
|
||||||
const containerLabels = isSwarm ? shvl.get(container, "Spec.Labels") : container.Labels;
|
const containerLabels = isSwarm ? shvl.get(container, "Spec.Labels") : container.Labels;
|
||||||
@ -113,127 +103,13 @@ export async function servicesFromDocker() {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const mappedServiceGroups = [];
|
return mapObjectsToGroup(serviceServers, "services");
|
||||||
|
|
||||||
serviceServers.forEach((server) => {
|
|
||||||
server.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export 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 checkCRD(kc, name) {
|
|
||||||
const apiExtensions = kc.makeApiClient(ApiextensionsV1Api);
|
|
||||||
const exist = await apiExtensions
|
|
||||||
.readCustomResourceDefinitionStatus(name)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(async (error) => {
|
|
||||||
if (error.statusCode === 403) {
|
|
||||||
logger.error(
|
|
||||||
"Error checking if CRD %s exists. Make sure to add the following permission to your RBAC: %d %s %s",
|
|
||||||
name,
|
|
||||||
error.statusCode,
|
|
||||||
error.body.message,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return exist;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function servicesFromKubernetes() {
|
export async function servicesFromKubernetes() {
|
||||||
const ANNOTATION_BASE = "gethomepage.dev";
|
|
||||||
const ANNOTATION_WIDGET_BASE = `${ANNOTATION_BASE}/widget.`;
|
|
||||||
const { instanceName } = getSettings();
|
|
||||||
|
|
||||||
checkAndCopyConfig("kubernetes.yaml");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const kc = getKubeConfig();
|
const ingressList = await getIngressList();
|
||||||
if (!kc) {
|
if (!ingressList?.items) {
|
||||||
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);
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 [];
|
return [];
|
||||||
}
|
}
|
||||||
const services = ingressList.items
|
const services = ingressList.items
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user