mirror of
https://github.com/auricom/home-cluster.git
synced 2025-09-17 18:24:14 +02:00
160 lines
5.3 KiB
JavaScript
Executable File
160 lines
5.3 KiB
JavaScript
Executable File
#!/usr/bin/env zx
|
|
$.verbose = false;
|
|
|
|
/**
|
|
* * helmReleaseDiff.mjs
|
|
* * Runs `helm template` with your Helm values and then runs `dyff` across Flux HelmRelease manifests
|
|
* @param --current-release The source Flux HelmRelease to compare against the target
|
|
* @param --incoming-release The target Flux HelmRelease to compare against the source
|
|
* @param --kubernetes-dir The directory containing your Flux manifests including the HelmRepository manifests
|
|
* * Limitations:
|
|
* * Does not work with multiple HelmRelease maninfests in the same YAML document
|
|
*/
|
|
const CurrentRelease = argv["current-release"];
|
|
const IncomingRelease = argv["incoming-release"];
|
|
const KubernetesDir = argv["kubernetes-dir"];
|
|
|
|
const dyff = await which("dyff");
|
|
const helm = await which("helm");
|
|
const kustomize = await which("kustomize");
|
|
|
|
async function helmRelease(releaseFile) {
|
|
const helmRelease = await fs.readFile(releaseFile, "utf8");
|
|
const doc = YAML.parseAllDocuments(helmRelease).map((item) => item.toJS());
|
|
const release = doc.filter(
|
|
(item) =>
|
|
item.apiVersion === "helm.toolkit.fluxcd.io/v2beta1" &&
|
|
item.kind === "HelmRelease"
|
|
);
|
|
return release[0];
|
|
}
|
|
|
|
async function helmRepositoryUrl(kubernetesDir, releaseName) {
|
|
const files = await globby([`${kubernetesDir}/**/*.yaml`]);
|
|
for await (const file of files) {
|
|
const contents = await fs.readFile(file, "utf8");
|
|
const doc = YAML.parseAllDocuments(contents).map((item) => item.toJS());
|
|
if (
|
|
"apiVersion" in doc[0] &&
|
|
doc[0].apiVersion === "source.toolkit.fluxcd.io/v1beta2" &&
|
|
"kind" in doc[0] &&
|
|
doc[0].kind === "HelmRepository" &&
|
|
"metadata" in doc[0] &&
|
|
"name" in doc[0].metadata &&
|
|
doc[0].metadata.name === releaseName
|
|
) {
|
|
return doc[0].spec.url;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function kustomizeBuild(releaseBaseDir, releaseName) {
|
|
const build =
|
|
await $`${kustomize} build --load-restrictor=LoadRestrictionsNone ${releaseBaseDir}`;
|
|
const docs = YAML.parseAllDocuments(build.stdout).map((item) => item.toJS());
|
|
const release = docs.filter(
|
|
(item) =>
|
|
item.apiVersion === "helm.toolkit.fluxcd.io/v2beta1" &&
|
|
item.kind === "HelmRelease" &&
|
|
item.metadata.name === releaseName
|
|
);
|
|
return release[0];
|
|
}
|
|
|
|
async function helmRepoAdd(registryName, registryUrl) {
|
|
await $`${helm} repo add ${registryName} ${registryUrl}`;
|
|
}
|
|
|
|
async function helmTemplate(
|
|
releaseName,
|
|
registryName,
|
|
chartName,
|
|
chartVersion,
|
|
chartValues
|
|
) {
|
|
const values = new YAML.Document();
|
|
values.contents = chartValues;
|
|
const valuesFile = await $`mktemp`;
|
|
await fs.writeFile(valuesFile.stdout.trim(), values.toString());
|
|
|
|
const manifestsFile = await $`mktemp`;
|
|
const manifests =
|
|
await $`${helm} template --kube-version 1.26.0 --release-name ${releaseName} --include-crds=false ${registryName}/${chartName} --version ${chartVersion} --values ${valuesFile.stdout.trim()}`;
|
|
|
|
// Remove docs that are CustomResourceDefinition and keys which contain generated fields
|
|
let documents = YAML.parseAllDocuments(manifests.stdout.trim());
|
|
documents = documents.filter(
|
|
(doc) => doc.get("kind") !== "CustomResourceDefinition"
|
|
);
|
|
documents.forEach((doc) => {
|
|
const del = (path) => (doc.hasIn(path) ? doc.deleteIn(path) : false);
|
|
del(["metadata", "labels", "app.kubernetes.io/version"]);
|
|
del(["metadata", "labels", "chart"]);
|
|
del(["metadata", "labels", "helm.sh/chart"]);
|
|
del([
|
|
"spec",
|
|
"template",
|
|
"metadata",
|
|
"labels",
|
|
"app.kubernetes.io/version",
|
|
]);
|
|
del(["spec", "template", "metadata", "labels", "chart"]);
|
|
del(["spec", "template", "metadata", "labels", "helm.sh/chart"]);
|
|
});
|
|
|
|
await fs.writeFile(
|
|
manifestsFile.stdout.trim(),
|
|
documents.map((doc) => doc.toString({ directives: true })).join("\n")
|
|
);
|
|
return manifestsFile.stdout.trim();
|
|
}
|
|
|
|
// Generate current template from Helm values
|
|
const currentRelease = await helmRelease(CurrentRelease);
|
|
const currentBuild = await kustomizeBuild(
|
|
path.dirname(CurrentRelease),
|
|
currentRelease.metadata.name
|
|
);
|
|
const currentRepositoryUrl = await helmRepositoryUrl(
|
|
KubernetesDir,
|
|
currentBuild.spec.chart.spec.sourceRef.name
|
|
);
|
|
await helmRepoAdd(
|
|
currentBuild.spec.chart.spec.sourceRef.name,
|
|
currentRepositoryUrl
|
|
);
|
|
const currentManifests = await helmTemplate(
|
|
currentBuild.metadata.name,
|
|
currentBuild.spec.chart.spec.sourceRef.name,
|
|
currentBuild.spec.chart.spec.chart,
|
|
currentBuild.spec.chart.spec.version,
|
|
currentBuild.spec.values
|
|
);
|
|
|
|
// Generate incoming template from Helm values
|
|
const incomingRelease = await helmRelease(IncomingRelease);
|
|
const incomingBuild = await kustomizeBuild(
|
|
path.dirname(IncomingRelease),
|
|
incomingRelease.metadata.name
|
|
);
|
|
const incomingRepositoryUrl = await helmRepositoryUrl(
|
|
KubernetesDir,
|
|
incomingBuild.spec.chart.spec.sourceRef.name
|
|
);
|
|
await helmRepoAdd(
|
|
incomingBuild.spec.chart.spec.sourceRef.name,
|
|
incomingRepositoryUrl
|
|
);
|
|
const incomingManifests = await helmTemplate(
|
|
incomingBuild.metadata.name,
|
|
incomingBuild.spec.chart.spec.sourceRef.name,
|
|
incomingBuild.spec.chart.spec.chart,
|
|
incomingBuild.spec.chart.spec.version,
|
|
incomingBuild.spec.values
|
|
);
|
|
|
|
// Print diff using dyff
|
|
const diff =
|
|
await $`${dyff} --color=off --truecolor=off between --omit-header --ignore-order-changes --detect-kubernetes=true --output=human ${currentManifests} ${incomingManifests}`;
|
|
echo(diff.stdout.trim());
|