From fe35b2c0ed9c1efd38f3934f858e24c3bce03cab Mon Sep 17 00:00:00 2001 From: devthejo Date: Thu, 2 Jun 2022 18:45:06 +0200 Subject: [PATCH] fix: add dep tree plantuml --- packages/common/utils/stream-to-string.js | 8 + packages/kontinuous/package.json | 1 + .../src/build/infos/component-tree-infos.js | 207 ++++++++++++++++++ .../build/infos/dependencies-tree-infos.js | 67 ++++++ packages/kontinuous/src/build/output-infos.js | 207 +----------------- yarn.lock | 44 ++++ 6 files changed, 331 insertions(+), 203 deletions(-) create mode 100644 packages/common/utils/stream-to-string.js create mode 100644 packages/kontinuous/src/build/infos/component-tree-infos.js create mode 100644 packages/kontinuous/src/build/infos/dependencies-tree-infos.js diff --git a/packages/common/utils/stream-to-string.js b/packages/common/utils/stream-to-string.js new file mode 100644 index 0000000000..c29dc0e4ef --- /dev/null +++ b/packages/common/utils/stream-to-string.js @@ -0,0 +1,8 @@ +module.exports = (stream) => { + const chunks = [] + return new Promise((resolve, reject) => { + stream.on("data", (chunk) => chunks.push(Buffer.from(chunk))) + stream.on("error", (err) => reject(err)) + stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))) + }) +} diff --git a/packages/kontinuous/package.json b/packages/kontinuous/package.json index 207e639663..c18051213f 100644 --- a/packages/kontinuous/package.json +++ b/packages/kontinuous/package.json @@ -23,6 +23,7 @@ "lodash.defaultsdeep": "^4.6.1", "lodash.mergewith": "^4.6.2", "nctx": "^1.2.0", + "node-plantuml": "^0.9.0", "pino": "^7.10.0", "pino-pretty": "^7.6.0", "pretty-ms": "^7.0.1", diff --git a/packages/kontinuous/src/build/infos/component-tree-infos.js b/packages/kontinuous/src/build/infos/component-tree-infos.js new file mode 100644 index 0000000000..fefce3a88f --- /dev/null +++ b/packages/kontinuous/src/build/infos/component-tree-infos.js @@ -0,0 +1,207 @@ +// const fs = require("fs-extra") + +const ctx = require("~/ctx") +const logTree = require("~common/utils/log-tree") + +const getTreeInfos = {} + +getTreeInfos.Namespace = (resource) => { + const { manifest } = resource + return [{ name: `name: ${manifest.metadata.name}`}] +} + +getTreeInfos.Ingress = (resource)=>{ + const { manifest } = resource + const redirect = manifest.metadata?.annotations["nginx.ingress.kubernetes.io/permanent-redirect"] + return [...(manifest.spec?.rules ? [{ + name: "hosts", + children: manifest.spec.rules.map(({host})=>({name:`https://${host}`})) + }] : []), ...(redirect ? [{ + name: "redirect", + children: [{name: redirect}] + }] : [])] +} + +getTreeInfos.Deployment = (resource) => { + const { manifest } = resource + const containers = manifest.spec?.template?.spec?.containers + const initContainers = manifest.spec?.template?.spec?.initContainers + return [...(containers ? containers.map(container=>{ + return {name:container.name, children: [ + { + name: `image: ${container.image}`, + }, + { + name: `port${container.ports.length>1?"s":""}: ${container.ports.map(({ containerPort }) => containerPort).join(",")}`, + }, + ]}}) : []), + ...(initContainers ? initContainers.map(container=>{ + return {name: `${container.name} (init)`, children: [ + { + name: `image: ${container.image}`, + }, + ]}}) : []), + ] +} + +getTreeInfos.Service = (resource) => { + const { manifest } = resource + const ports = manifest.spec.ports + const children = [] + if(ports){ + const portStr = ports.map(port=> `${port.name ? port.name + "=" : ""}${port.port}:${port.targetPort}`) + children.push({ + name: `port${ports.length > 1 ? "s" : ""}: ${portStr}` + }) + } + return children +} + +getTreeInfos.ConfigMap = (resource) => { + const { manifest } = resource + return [{ name: `name: ${manifest.metadata.name}` }] +} + +getTreeInfos.SealedSecret = (resource) => { + const { manifest } = resource + return [{ name: `name: ${manifest.metadata.name}` }] +} + +getTreeInfos.Job = (resource) => { + const { manifest } = resource + const containers = manifest.spec?.template?.spec?.containers + const initContainers = manifest.spec?.template?.spec?.initContainers + return [...(containers ? containers.map(container => { + return { + name: container.name, children: [ + { + name: `image: ${container.image}`, + }, + ] + } + }) : []), + ...(initContainers ? initContainers.map(container => { + return { + name: `${container.name} (init)`, children: [ + { + name: `image: ${container.image}`, + }, + ] + } + }) : []), + ] +} + +getTreeInfos.CronJob = (resource) => { + const { manifest } = resource + const containers = manifest.spec?.jobTemplate?.spec?.template?.spec?.containers + const initContainers = manifest.spec?.jobTemplate?.spec?.template?.spec?.initContainers + return [ + { name: `schedule: ${manifest.spec.schedule}` }, + ...(containers ? containers.map(container => { + return { + name: container.name, children: [ + { + name: `image: ${container.image}`, + }, + ] + } + }) : []), + ...(initContainers ? initContainers.map(container => { + return { + name: `${container.name} (init)`, children: [ + { + name: `image: ${container.image}`, + }, + ] + } + }) : []), + ] +} + + +module.exports = async(manifests)=>{ + const logger = ctx.require("logger") + const config = ctx.require("config") + const { + buildPath, + } = config + + const componentResources = {} + const globalResources = {kinds: {}} + for (let manifest of manifests) { + if (!manifest.metadata?.labels?.component) { + if (!globalResources.kinds[manifest.kind]) { + globalResources.kinds[manifest.kind] = [] + } + globalResources.kinds[manifest.kind].push({ + name: manifest.metadata?.name, + manifest, + }) + } else { + const { component } = manifest.metadata.labels + if (!componentResources[component]){ + componentResources[component] = { + kinds: {} + } + } + if (!componentResources[component].kinds[manifest.kind]){ + componentResources[component].kinds[manifest.kind] = [] + } + componentResources[component].kinds[manifest.kind].push({ + name: manifest.metadata?.name, + manifest, + }) + } + } + const componentsTree = [] + for (const [name, component] of Object.entries(componentResources)){ + const resources = [] + for (const [kind, kindResources] of Object.entries(component.kinds)){ + const children = [] + for (const resource of kindResources){ + if (getTreeInfos[kind]){ + children.push(...getTreeInfos[kind](resource)) + } + } + resources.push({ + name: `${kind}`, + children, + }) + } + componentsTree.push({ + name, + children: resources, + }) + } + + const globalsTree = [] + for (const [kind, kindResources] of Object.entries(globalResources.kinds)) { + const children = [] + for (const resource of kindResources) { + if (getTreeInfos[kind]) { + children.push(...getTreeInfos[kind](resource)) + } + } + globalsTree.push({ + name: `${kind}`, + children, + }) + } + const tree = [ + { + name: "components", + children: componentsTree + }, + { + name: "globals", + children: globalsTree, + }, + ] + + const treeStr = logTree(tree) + logger.debug("\n"+treeStr) + + // await fs.writeFile(`${buildPath}/manifests.tree.md`, `\`\`\`\n${treeStr}\n\`\`\``) + +} \ No newline at end of file diff --git a/packages/kontinuous/src/build/infos/dependencies-tree-infos.js b/packages/kontinuous/src/build/infos/dependencies-tree-infos.js new file mode 100644 index 0000000000..d9f08192f8 --- /dev/null +++ b/packages/kontinuous/src/build/infos/dependencies-tree-infos.js @@ -0,0 +1,67 @@ +// const fs = require("fs-extra") +const plantuml = require("node-plantuml") +const camelcase = require("lodash.camelcase") +const ctx = require("~/ctx") + +const streamToString = require("~common/utils/stream-to-string") + +const changeGroupPrefix = "kapp.k14s.io/change-group" +const changeRulePrefix = "kapp.k14s.io/change-rule" +const changeRuleValuePrefix = "upsert after upserting kontinuous/" + +module.exports = async(manifests)=>{ + const logger = ctx.require("logger") + // const config = ctx.require("config") + + const flatDependencies = {} + + for (const manifest of manifests) { + const annotations = manifest.metadata?.annotations + if (!annotations || !annotations[changeGroupPrefix]) { + continue + } + const depKey = Object.keys(annotations) + .find(key => key.startsWith(`${changeGroupPrefix}.`)) + .split(".").pop() + + for (const [key, value] of Object.entries(annotations)) { + if ( + (key === changeRulePrefix || + key.startsWith(`${changeRulePrefix}.`)) + && value.startsWith(changeRuleValuePrefix) + ) { + if(!flatDependencies[depKey]){ + flatDependencies[depKey] = new Set() + } + let dep = value.slice(changeRuleValuePrefix.length) + dep = dep.split(".").shift() + dep = camelcase(dep) + flatDependencies[depKey].add(dep) + } + } + } + + + const uml = [] + for(const [key, dependenciesSet] of Object.entries(flatDependencies)){ + for(const dep of dependenciesSet){ + if(dep!==key){ + uml.push(`${dep} -> ${key}`) + } + } + } + + if(uml.length===0){ + return + } + + const gen = plantuml.generate(uml.join("\n"), { + format: "ascii" + }); + const result = await streamToString(gen.out) + logger.debug("\n"+result) + + // await fs.writeFile(`${config.buildPath}/dependencies.tree.md`, `uml\`\`\`\n${result}\n\`\`\``) + + +} \ No newline at end of file diff --git a/packages/kontinuous/src/build/output-infos.js b/packages/kontinuous/src/build/output-infos.js index 085913b3e9..d3caca546b 100644 --- a/packages/kontinuous/src/build/output-infos.js +++ b/packages/kontinuous/src/build/output-infos.js @@ -1,206 +1,7 @@ -const fs = require("fs-extra") - -const ctx = require("~/ctx") -const logTree = require("~common/utils/log-tree") - -const getTreeInfos = {} - -getTreeInfos.Namespace = (resource) => { - const { manifest } = resource - return [{ name: `name: ${manifest.metadata.name}`}] -} - -getTreeInfos.Ingress = (resource)=>{ - const { manifest } = resource - const redirect = manifest.metadata?.annotations["nginx.ingress.kubernetes.io/permanent-redirect"] - return [...(manifest.spec?.rules ? [{ - name: "hosts", - children: manifest.spec.rules.map(({host})=>({name:`https://${host}`})) - }] : []), ...(redirect ? [{ - name: "redirect", - children: [{name: redirect}] - }] : [])] -} - -getTreeInfos.Deployment = (resource) => { - const { manifest } = resource - const containers = manifest.spec?.template?.spec?.containers - const initContainers = manifest.spec?.template?.spec?.initContainers - return [...(containers ? containers.map(container=>{ - return {name:container.name, children: [ - { - name: `image: ${container.image}`, - }, - { - name: `port${container.ports.length>1?"s":""}: ${container.ports.map(({ containerPort }) => containerPort).join(",")}`, - }, - ]}}) : []), - ...(initContainers ? initContainers.map(container=>{ - return {name: `${container.name} (init)`, children: [ - { - name: `image: ${container.image}`, - }, - ]}}) : []), - ] -} - -getTreeInfos.Service = (resource) => { - const { manifest } = resource - const ports = manifest.spec.ports - const children = [] - if(ports){ - const portStr = ports.map(port=> `${port.name ? port.name + "=" : ""}${port.port}:${port.targetPort}`) - children.push({ - name: `port${ports.length > 1 ? "s" : ""}: ${portStr}` - }) - } - return children -} - -getTreeInfos.ConfigMap = (resource) => { - const { manifest } = resource - return [{ name: `name: ${manifest.metadata.name}` }] -} - -getTreeInfos.SealedSecret = (resource) => { - const { manifest } = resource - return [{ name: `name: ${manifest.metadata.name}` }] -} - -getTreeInfos.Job = (resource) => { - const { manifest } = resource - const containers = manifest.spec?.template?.spec?.containers - const initContainers = manifest.spec?.template?.spec?.initContainers - return [...(containers ? containers.map(container => { - return { - name: container.name, children: [ - { - name: `image: ${container.image}`, - }, - ] - } - }) : []), - ...(initContainers ? initContainers.map(container => { - return { - name: `${container.name} (init)`, children: [ - { - name: `image: ${container.image}`, - }, - ] - } - }) : []), - ] -} - -getTreeInfos.CronJob = (resource) => { - const { manifest } = resource - const containers = manifest.spec?.jobTemplate?.spec?.template?.spec?.containers - const initContainers = manifest.spec?.jobTemplate?.spec?.template?.spec?.initContainers - return [ - { name: `schedule: ${manifest.spec.schedule}` }, - ...(containers ? containers.map(container => { - return { - name: container.name, children: [ - { - name: `image: ${container.image}`, - }, - ] - } - }) : []), - ...(initContainers ? initContainers.map(container => { - return { - name: `${container.name} (init)`, children: [ - { - name: `image: ${container.image}`, - }, - ] - } - }) : []), - ] -} +const componentTreeInfos = require("./infos/component-tree-infos") +const dependenciesTreeInfos = require("./infos/dependencies-tree-infos") module.exports = async (manifests, _values) => { - const logger = ctx.require("logger") - const config = ctx.require("config") - const { - buildPath, - } = config - - const componentResources = {} - const globalResources = {kinds: {}} - for (let manifest of manifests) { - if (!manifest.metadata?.labels?.component) { - if (!globalResources.kinds[manifest.kind]) { - globalResources.kinds[manifest.kind] = [] - } - globalResources.kinds[manifest.kind].push({ - name: manifest.metadata?.name, - manifest, - }) - } else { - const { component } = manifest.metadata.labels - if (!componentResources[component]){ - componentResources[component] = { - kinds: {} - } - } - if (!componentResources[component].kinds[manifest.kind]){ - componentResources[component].kinds[manifest.kind] = [] - } - componentResources[component].kinds[manifest.kind].push({ - name: manifest.metadata?.name, - manifest, - }) - } - } - const componentsTree = [] - for (const [name, component] of Object.entries(componentResources)){ - const resources = [] - for (const [kind, kindResources] of Object.entries(component.kinds)){ - const children = [] - for (const resource of kindResources){ - if (getTreeInfos[kind]){ - children.push(...getTreeInfos[kind](resource)) - } - } - resources.push({ - name: `${kind}`, - children, - }) - } - componentsTree.push({ - name, - children: resources, - }) - } - - const globalsTree = [] - for (const [kind, kindResources] of Object.entries(globalResources.kinds)) { - const children = [] - for (const resource of kindResources) { - if (getTreeInfos[kind]) { - children.push(...getTreeInfos[kind](resource)) - } - } - globalsTree.push({ - name: `${kind}`, - children, - }) - } - const tree = [ - { - name: "components", - children: componentsTree - }, - { - name: "globals", - children: globalsTree, - }, - ] - - const treeStr = logTree(tree) - logger.debug("\n"+treeStr) - - await fs.writeFile(`${buildPath}/manifests.tree.md`, `\`\`\`\n${treeStr}\n\`\`\``) - + await componentTreeInfos(manifests) + await dependenciesTreeInfos(manifests) } diff --git a/yarn.lock b/yarn.lock index 697491f44d..cee09067d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6040,6 +6040,7 @@ __metadata: lodash.defaultsdeep: "npm:^4.6.1" lodash.mergewith: "npm:^4.6.2" nctx: "npm:^1.2.0" + node-plantuml: "npm:^0.9.0" pino: "npm:^7.10.0" pino-pretty: "npm:^7.6.0" pretty-ms: "npm:^7.0.1" @@ -6863,6 +6864,42 @@ __metadata: languageName: node linkType: hard +"node-nailgun-client@npm:^0.1.0": + version: 0.1.2 + resolution: "node-nailgun-client@npm:0.1.2" + dependencies: + commander: "npm:^2.8.1" + bin: + node-nailgun-client: index.js + checksum: 6bc0af24071cd18855c5ae1c9222457d0fe632e1f1c683ef3483afed48c794f23c27f7f112dcc40947c62ac3d2a429e86670b7de702de1f791a7bb05911b3c96 + languageName: node + linkType: hard + +"node-nailgun-server@npm:^0.1.4": + version: 0.1.4 + resolution: "node-nailgun-server@npm:0.1.4" + dependencies: + commander: "npm:^2.8.1" + bin: + node-nailgun-server: index.js + checksum: 72afa0d712b8452361d44be6cf8ab5991f20b97de65fdaf7f1d1b86b3425d8491b7add89419dfc9ec2c8e3ed4879f8da777b09afed1b34d17b11e2e8e7c8833f + languageName: node + linkType: hard + +"node-plantuml@npm:^0.9.0": + version: 0.9.0 + resolution: "node-plantuml@npm:0.9.0" + dependencies: + commander: "npm:^2.8.1" + node-nailgun-client: "npm:^0.1.0" + node-nailgun-server: "npm:^0.1.4" + plantuml-encoder: "npm:^1.2.5" + bin: + puml: index.js + checksum: 546ae80d82f47e6df2ff76cb7539b12d4d8924a02b1f7f3d50e5b611a04361846633ec6f767343f1db4ceb5216407549fefccd7c224c4420dc8ff783f5e26dcc + languageName: node + linkType: hard + "node-releases@npm:^2.0.2": version: 2.0.2 resolution: "node-releases@npm:2.0.2" @@ -7596,6 +7633,13 @@ __metadata: languageName: node linkType: hard +"plantuml-encoder@npm:^1.2.5": + version: 1.4.0 + resolution: "plantuml-encoder@npm:1.4.0" + checksum: 1144dff1390ae5d91d2da9ed51469ab11f69cc5c49a55f36bf002acb7611b9fcc4595d926ccc9fe629c2dfbae35e765e43de1c99feb21301235bb508bb360c0d + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1"