-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
200 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
#!/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.24.8 --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']) | ||
del(['spec', 'template', 'metadata', 'annotations']) | ||
del(['spec', 'template', 'metadata', 'labels']) | ||
}) | ||
|
||
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()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
--- | ||
name: HelmRelease Diff | ||
|
||
on: # yamllint disable-line rule:truthy | ||
pull_request: | ||
branches: [master] | ||
paths: ['**.yaml'] | ||
|
||
env: | ||
KUBERNETES_DIR: ./ | ||
|
||
jobs: | ||
changed-files: | ||
name: Detect File Changes | ||
runs-on: ubuntu-latest | ||
outputs: | ||
matrix: ${{ steps.set-matrix.outputs.matrix }} | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 | ||
|
||
- name: Get changed files | ||
id: changed-files | ||
uses: tj-actions/changed-files@b2d17f51244a144849c6b37a3a6791b98a51d86f # v35.9.2 | ||
with: | ||
json: true | ||
files: | | ||
cluster/**/helm-release.yaml | ||
- id: set-matrix | ||
run: echo "matrix={\"file\":${{ steps.changed-files.outputs.all_changed_files }}}" >> "${GITHUB_OUTPUT}" | ||
|
||
diff: | ||
name: Diff on Helm Releases | ||
runs-on: ubuntu-latest | ||
needs: [changed-files] | ||
strategy: | ||
matrix: ${{ fromJSON(needs.changed-files.outputs.matrix) }} | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 | ||
|
||
- name: Checkout default branch | ||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 | ||
with: | ||
ref: ${{ github.event.repository.default_branch }} | ||
path: default_branch | ||
|
||
- name: Setup Homebrew | ||
uses: Homebrew/actions/setup-homebrew@master | ||
|
||
- name: Setup Tools | ||
run: | | ||
brew install helm homeport/tap/dyff kustomize yq | ||
- name: Diff | ||
id: diff | ||
run: | | ||
diff=$(npx zx ./.github/scripts/helmReleaseDiff.mjs \ | ||
--current-release "default_branch/${{ matrix.file }}" \ | ||
--incoming-release "${{ matrix.file }}" \ | ||
--kubernetes-dir ${{ env.KUBERNETES_DIR }} \ | ||
--diff-tool "diff") | ||
# shellcheck disable=SC2129 | ||
echo "diff<<EOF" >> "${GITHUB_OUTPUT}" | ||
echo "${diff}" >> "${GITHUB_OUTPUT}" | ||
echo "EOF" >> "${GITHUB_OUTPUT}" | ||
- name: Find Comment | ||
if: ${{ always() && steps.diff.outputs.diff != '' }} | ||
uses: peter-evans/find-comment@a54c31d7fa095754bfef525c0c8e5e5674c4b4b1 # v2.4.0 | ||
id: find-comment | ||
with: | ||
issue-number: ${{ github.event.pull_request.number }} | ||
body-includes: 'Helm Release Diff: ${{ matrix.file }}' | ||
|
||
- name: Create or update comment | ||
if: ${{ always() && steps.diff.outputs.diff != '' }} | ||
uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1 | ||
with: | ||
comment-id: ${{ steps.find-comment.outputs.comment-id }} | ||
issue-number: ${{ github.event.pull_request.number }} | ||
body: | | ||
Helm Release Diff: `${{ matrix.file }}` | ||
```diff | ||
${{ steps.diff.outputs.diff }} | ||
``` | ||
edit-mode: replace |