From 0fe21d9bf7f450600fff3d1e08177faba98918d0 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Sun, 17 Dec 2023 23:58:43 -0800 Subject: [PATCH] DRY --- src/components/bookmarks/item.jsx | 1 + src/utils/config/api-response.js | 37 ++---- src/utils/config/bookmark-helpers.js | 147 +++------------------- src/utils/config/integration-helpers.js | 161 ++++++++++++++++++++++++ src/utils/config/service-helpers.js | 154 +++-------------------- 5 files changed, 206 insertions(+), 294 deletions(-) create mode 100644 src/utils/config/integration-helpers.js diff --git a/src/components/bookmarks/item.jsx b/src/components/bookmarks/item.jsx index 5d3b351b..44d77ef2 100644 --- a/src/components/bookmarks/item.jsx +++ b/src/components/bookmarks/item.jsx @@ -5,6 +5,7 @@ import { SettingsContext } from "utils/contexts/settings"; import ResolvedIcon from "components/resolvedicon"; export default function Item({ bookmark }) { + console.log(bookmark); const description = bookmark.description ?? new URL(bookmark.href).hostname; const { settings } = useContext(SettingsContext); diff --git a/src/utils/config/api-response.js b/src/utils/config/api-response.js index c1f2d5ce..69514b34 100644 --- a/src/utils/config/api-response.js +++ b/src/utils/config/api-response.js @@ -1,9 +1,6 @@ /* eslint-disable no-console */ -import {getSettings} from "utils/config/config"; -import { - bookmarksFromConfig, - bookmarksFromDocker -} from "utils/config/bookmark-helpers"; +import { getSettings } from "utils/config/config"; +import { bookmarksFromConfig, bookmarksFromDocker } from "utils/config/bookmark-helpers"; import { servicesFromConfig, servicesFromDocker, @@ -27,15 +24,13 @@ export async function bookmarksResponse() { let discoveredDockerBookmarks; let configuredBookmarks; let initialSettings; - console.debug("Bookmark response called"); - try { discoveredDockerBookmarks = await bookmarksFromDocker(); 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) { - 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()); discoveredDockerBookmarks = []; } @@ -62,28 +57,22 @@ export async function bookmarksResponse() { const mergedGroupsNames = [ ...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) => { - -const discoveredDockerGroup = discoveredDockerBookmarks.find((group) => group.name === groupName) || { + const discoveredDockerGroup = discoveredDockerBookmarks.find((group) => group.name === groupName) || { bookmarks: [], }; const configuredGroup = configuredBookmarks.find((group) => group.name === groupName) || { bookmarks: [] }; - - const mergedGroup = { - name: groupName, - bookmarks: [...discoveredDockerGroup.bookmarks, ...configuredGroup.bookmarks] - .filter((bookmark) => bookmark) + const mergedGroup = { + name: groupName, + bookmarks: [...discoveredDockerGroup.bookmarks, ...configuredGroup.bookmarks].filter((bookmark) => bookmark), // .sort(compareBookmarks), // TODO is a sort needed? - } + }; - if (definedLayouts) { + if (definedLayouts) { const layoutIndex = definedLayouts.findIndex((layout) => layout === mergedGroup.name); if (layoutIndex > -1) sortedGroups[layoutIndex] = mergedGroup; else unsortedGroups.push(mergedGroup); diff --git a/src/utils/config/bookmark-helpers.js b/src/utils/config/bookmark-helpers.js index cf0797e2..be3092ce 100644 --- a/src/utils/config/bookmark-helpers.js +++ b/src/utils/config/bookmark-helpers.js @@ -2,17 +2,24 @@ import { promises as fs } from "fs"; import path from "path"; 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 getDockerArguments from "utils/config/docker"; -import getKubeConfig from "utils/config/kubernetes"; -import { checkCRD, getUrlFromIngress } from "./service-helpers"; import * as shvl from "utils/config/shvl"; -export async function bookmarksFromConfig() { +const logger = createLogger("bookmark-helpers"); +export async function bookmarksFromConfig() { checkAndCopyConfig("bookmarks.yaml"); const bookmarksYaml = path.join(CONF_DIR, "bookmarks.yaml"); @@ -32,62 +39,9 @@ export async function bookmarksFromConfig() { })); 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 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() { - const servers = await getDockerServers(); if (!servers) { @@ -109,9 +63,8 @@ export async function bookmarksFromDocker() { Object.keys(containerLabels).forEach((label) => { if (label.startsWith("homepage.bookmarks.")) { 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}.`)) { value = value.replace(`instance.${instanceName}.`, ""); } else if (value.startsWith("instance.")) { @@ -140,76 +93,12 @@ export async function bookmarksFromDocker() { }), ); - const mappedBookmarkGroups = mapObjectsToGroup(bookmarkServers, 'bookmarks'); - return mappedBookmarkGroups; + return mapObjectsToGroup(bookmarkServers, "bookmarks"); } - export async function bookmarksFromKubernetes() { - const ANNOTATION_BASE = "gethomepage.dev"; - const ANNOTATION_WIDGET_BASE = `${ANNOTATION_BASE}/widget.`; - - 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 traefikbookmarks = traefikIngressList.filter( - (ingress) => ingress.metadata.annotations && ingress.metadata.annotations[`${ANNOTATION_BASE}/href`], - ); - ingressList.items.push(...traefikbookmarks); - } + const ingressList = await getIngressList(); if (!ingressList) { return []; @@ -230,9 +119,6 @@ export async function bookmarksFromKubernetes() { description: ingress.metadata.annotations[`${ANNOTATION_BASE}/description`] || "", 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) => { if (annotation.startsWith(ANNOTATION_WIDGET_BASE)) { shvl.set( @@ -279,4 +165,3 @@ export async function bookmarksFromKubernetes() { throw e; } } - diff --git a/src/utils/config/integration-helpers.js b/src/utils/config/integration-helpers.js new file mode 100644 index 00000000..f8a91885 --- /dev/null +++ b/src/utils/config/integration-helpers.js @@ -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 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; + } +} diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index d6b65ee4..3690c990 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -2,13 +2,19 @@ import { promises as fs } from "fs"; import path from "path"; 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 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"; const logger = createLogger("service-helpers"); @@ -48,12 +54,7 @@ export async function servicesFromConfig() { } export async function servicesFromDocker() { - checkAndCopyConfig("docker.yaml"); - - const dockerYaml = path.join(CONF_DIR, "docker.yaml"); - const rawDockerFileContents = await fs.readFile(dockerYaml, "utf8"); - const dockerFileContents = substituteEnvironmentVars(rawDockerFileContents); - const servers = yaml.load(dockerFileContents); + const servers = await getDockerServers(); if (!servers) { return []; @@ -65,18 +66,7 @@ export async function servicesFromDocker() { Object.keys(servers).map(async (serverName) => { try { const isSwarm = !!servers[serverName].swarm; - const docker = new Docker(getDockerArguments(serverName).conn); - const listProperties = { all: true }; - const containers = await (isSwarm - ? docker.listServices(listProperties) - : docker.listContainers(listProperties)); - - // bad docker connections can result in a object? - // in any case, this ensures the result is the expected array - if (!Array.isArray(containers)) { - return []; - } - + const containers = await listDockerContainers(servers, serverName); const discovered = containers.map((container) => { let constructedService = null; const containerLabels = isSwarm ? shvl.get(container, "Spec.Labels") : container.Labels; @@ -113,127 +103,13 @@ export async function servicesFromDocker() { }), ); - const mappedServiceGroups = []; - - 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; + return mapObjectsToGroup(serviceServers, "services"); } export async function servicesFromKubernetes() { - const ANNOTATION_BASE = "gethomepage.dev"; - const ANNOTATION_WIDGET_BASE = `${ANNOTATION_BASE}/widget.`; - const { instanceName } = getSettings(); - - 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); - } - - if (!ingressList) { + const ingressList = await getIngressList(); + if (!ingressList?.items) { return []; } const services = ingressList.items