this repo has no description
1#!/usr/bin/env zx
2$.verbose = true
3/* Stolen from https://raw.githubusercontent.com/onedr0p/home-ops/main/.github/scripts/helmReleaseDiff.mjs */
4
5
6/**
7 * * helmReleaseDiff.mjs
8 * * Runs `helm template` with your Helm values and then runs `dyff` across Flux HelmRelease manifests
9 * @param --current-release The source Flux HelmRelease to compare against the target
10 * @param --incoming-release The target Flux HelmRelease to compare against the source
11 * @param --kubernetes-dir The directory containing your Flux manifests including the HelmRepository manifests
12 * * Limitations:
13 * * Does not work with multiple HelmRelease maninfests in the same YAML document
14 */
15const CurrentRelease = argv['current-release']
16const IncomingRelease = argv['incoming-release']
17const KubernetesDir = argv['kubernetes-dir']
18
19const dyff = await which('dyff')
20const helm = await which('helm')
21const kustomize = await which('kustomize')
22
23async function helmRelease(releaseFile) {
24 const helmRelease = await fs.readFile(releaseFile, 'utf8')
25 const doc = YAML.parseAllDocuments(helmRelease).map((item) => item.toJS())
26 const release = doc.filter((item) =>
27 item.apiVersion === 'helm.toolkit.fluxcd.io/v2beta1'
28 && item.kind === 'HelmRelease'
29 )
30 return release[0]
31}
32
33async function helmRepositoryUrl(kubernetesDir, releaseName) {
34 const files = await globby([`${kubernetesDir}/**/*.yaml`])
35 for await (const file of files) {
36 const contents = await fs.readFile(file, 'utf8')
37 const doc = YAML.parseAllDocuments(contents).map((item) => item.toJS())
38 if ('apiVersion' in doc[0] && doc[0].apiVersion === 'source.toolkit.fluxcd.io/v1beta2'
39 && 'kind' in doc[0] && doc[0].kind === 'HelmRepository'
40 && 'metadata' in doc[0] && 'name' in doc[0].metadata && doc[0].metadata.name === releaseName)
41 {
42 return doc[0].spec.url
43 }
44 }
45}
46
47async function kustomizeBuild(releaseBaseDir, releaseName) {
48 const build = await $`${kustomize} build --load-restrictor=LoadRestrictionsNone ${releaseBaseDir}`
49 const docs = YAML.parseAllDocuments(build.stdout).map((item) => item.toJS())
50 const release = docs.filter((item) =>
51 item.apiVersion === 'helm.toolkit.fluxcd.io/v2beta1'
52 && item.kind === 'HelmRelease'
53 && item.metadata.name === releaseName
54 )
55 return release[0]
56}
57
58async function helmRepoAdd (registryName, registryUrl) {
59 await $`${helm} repo add ${registryName} ${registryUrl}`
60}
61
62async function helmTemplate (releaseName, registryName, chartName, chartVersion, chartValues) {
63 const values = new YAML.Document()
64 values.contents = chartValues
65 const valuesFile = await $`mktemp`
66 await fs.writeFile(valuesFile.stdout.trim(), values.toString())
67
68 const manifestsFile = await $`mktemp`
69 const manifests = await $`${helm} template --kube-version 1.24.8 --release-name ${releaseName} --include-crds=false ${registryName}/${chartName} --version ${chartVersion} --values ${valuesFile.stdout.trim()}`
70
71 // Remove docs that are CustomResourceDefinition and keys which contain generated fields
72 let documents = YAML.parseAllDocuments(manifests.stdout.trim())
73 documents = documents.filter(doc => doc.get('kind') !== 'CustomResourceDefinition')
74 documents.forEach(doc => {
75 const del = (path) => doc.hasIn(path) ? doc.deleteIn(path) : false
76 del(['metadata', 'labels'])
77 del(['spec', 'template', 'metadata', 'annotations'])
78 del(['spec', 'template', 'metadata', 'labels'])
79 })
80
81 await fs.writeFile(manifestsFile.stdout.trim(), documents.map(doc => doc.toString({directives: true})).join('\n'))
82 return manifestsFile.stdout.trim()
83}
84
85// Generate current template from Helm values
86const currentRelease = await helmRelease(CurrentRelease)
87const currentBuild = await kustomizeBuild(path.dirname(CurrentRelease), currentRelease.metadata.name)
88const currentRepositoryUrl = await helmRepositoryUrl(KubernetesDir, currentBuild.spec.chart.spec.sourceRef.name)
89await helmRepoAdd(currentBuild.spec.chart.spec.sourceRef.name, currentRepositoryUrl)
90const currentManifests = await helmTemplate(
91 currentBuild.metadata.name,
92 currentBuild.spec.chart.spec.sourceRef.name,
93 currentBuild.spec.chart.spec.chart,
94 currentBuild.spec.chart.spec.version,
95 currentBuild.spec.values
96)
97
98// Generate incoming template from Helm values
99const incomingRelease = await helmRelease(IncomingRelease)
100const incomingBuild = await kustomizeBuild(path.dirname(IncomingRelease), incomingRelease.metadata.name)
101const incomingRepositoryUrl = await helmRepositoryUrl(KubernetesDir, incomingBuild.spec.chart.spec.sourceRef.name)
102await helmRepoAdd(incomingBuild.spec.chart.spec.sourceRef.name, incomingRepositoryUrl)
103const incomingManifests = await helmTemplate(
104 incomingBuild.metadata.name,
105 incomingBuild.spec.chart.spec.sourceRef.name,
106 incomingBuild.spec.chart.spec.chart,
107 incomingBuild.spec.chart.spec.version,
108 incomingBuild.spec.values
109)
110
111// Print diff using dyff
112const diff = await $`${dyff} --color=off --truecolor=off between --omit-header --ignore-order-changes --detect-kubernetes=true --output=human ${currentManifests} ${incomingManifests}`
113echo(diff.stdout.trim())