From 52c1e4d9569dd9815668de88ce17a017e9737924 Mon Sep 17 00:00:00 2001 From: Dustin Lactin Date: Tue, 5 Mar 2024 15:00:39 -0700 Subject: [PATCH 1/4] feat: added render and diff helm action --- render-and-diff-helm/README.md | 29 +++++++ render-and-diff-helm/action.yml | 141 ++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 render-and-diff-helm/README.md create mode 100644 render-and-diff-helm/action.yml diff --git a/render-and-diff-helm/README.md b/render-and-diff-helm/README.md new file mode 100644 index 0000000..46f2081 --- /dev/null +++ b/render-and-diff-helm/README.md @@ -0,0 +1,29 @@ +# Render and diff helm charts + +This action is used to render helm charts on the base and ref branches of a pull request, create a diff between charts that have been modified and then post the diff as a comment on the pull request. + +## Inputs + +### `chart_path` + +The path filter for helm charts in the repository. Default `'**/k8s/**/**'` + +### Example usage +``` +name: render-and-diff-charts + +on: + pull_request: + paths: + - '**/k8s/**/**' + +jobs: + run_helm_chart_diff: + permissions: + contents: read + id-token: write + runs-on: ubuntu-latest + steps: + - name: Render and diff modified helm charts + uses: mozilla-it/deploy-actions/deployment-status@v3.9.0 +``` \ No newline at end of file diff --git a/render-and-diff-helm/action.yml b/render-and-diff-helm/action.yml new file mode 100644 index 0000000..837cce1 --- /dev/null +++ b/render-and-diff-helm/action.yml @@ -0,0 +1,141 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +name: Matrix render and diff helm charts +description: Renders BASE and REF helm charts then posts the diff of the rendered charts as a comment on the pull request + +on: + workflow_call: + inputs: + chart_path: + description: Path filter for helm charts + required: false + type: string + default: '**/k8s/**' + +env: + CHART_PATH: ${{ inputs.chart_path }} + +jobs: + get_changed_helm_charts: + runs-on: ubuntu-latest + outputs: + matrix_charts: ${{ steps.find_changed_charts.outputs.matrix_changed_charts }} + charts: ${{ steps.find_changed_charts.outputs.changed_charts }} + steps: + - name: checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: '100' + + - name: find changed helm charts + id: find_changed_charts + run: | + git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} + echo matrix_changed_charts=$(git diff --name-only ${{ github.base_ref }}...HEAD -- '**/k8s/**/*.yaml' '**/k8s/**/*.yml' '**/k8s/**/*.tpl' '**/k8s/**/*.tmpl' | cut -d'/' -f1,2,3 | uniq | jq -R 'split("\n")' | jq -s 'flatten(1)') >> $GITHUB_OUTPUT + echo changed_charts=$(git diff --name-only ${{ github.base_ref }}...HEAD -- '**/k8s/**/*.yaml' '**/k8s/**/*.yml' '**/k8s/**/*.tpl' '**/k8s/**/*.tmpl' | cut -d'/' -f1,2,3 | uniq) >> $GITHUB_OUTPUT + + render_head_ref_charts: + runs-on: ubuntu-latest + needs: get_changed_helm_charts + strategy: + matrix: + chart: ${{ fromJSON(needs.get_changed_helm_charts.outputs.matrix_charts) }} + steps: + - name: checkout repository + uses: actions/checkout@v4 + + - name: setup helm + uses: azure/setup-helm@v4.0.0 + + - name: render ${{ matrix.chart }} from head ref + id: render_head + run: | + mkdir -p shared/head-charts + git fetch origin ${{ github.head_ref }} + git checkout ${{ github.head_ref }} + values_files="${{ matrix.chart }}"/values-* + for values_file in $(basename -a $values_files); do + helm template "${{ matrix.chart }}" -f "${{ matrix.chart }}/values.yaml" -f "${{ matrix.chart }}/${values_file}" --output-dir "shared/head-charts/${{ matrix.chart }}/${values_file}" + done + echo sanitized_name=$(echo "${{ matrix.chart }}" | sed 's/\//-/g') >> $GITHUB_OUTPUT + + - name: upload artifact + uses: actions/upload-artifact@v4 + with: + name: "shared-head-${{ steps.render_head.outputs.sanitized_name }}" + path: "shared" + + render_base_ref_charts: + runs-on: ubuntu-latest + needs: get_changed_helm_charts + strategy: + matrix: + chart: ${{ fromJSON(needs.get_changed_helm_charts.outputs.matrix_charts) }} + steps: + - name: checkout repository + uses: actions/checkout@v4 + + - name: setup helm + uses: azure/setup-helm@v4.0.0 + + - name: render ${{ matrix.chart }} from base ref + id: render_base + run: | + mkdir -p shared/base-charts + git fetch origin ${{ github.base_ref }} + git checkout ${{ github.base_ref }} + values_files="${{ matrix.chart }}"/values-* + for values_file in $(basename -a $values_files); do + helm template "${{ matrix.chart }}" -f "${{ matrix.chart }}/values.yaml" -f "${{ matrix.chart }}/${values_file}" --output-dir "shared/base-charts/${{ matrix.chart }}/${values_file}" + done + echo sanitized_name=$(echo "${{ matrix.chart }}" | sed 's/\//-/g') >> $GITHUB_OUTPUT + + - name: upload artifact + uses: actions/upload-artifact@v4 + with: + name: "shared-base-${{ steps.render_base.outputs.sanitized_name }}" + path: "shared" + + diff_helm_charts: + runs-on: ubuntu-latest + needs: + - get_changed_helm_charts + - render_base_ref_charts + - render_head_ref_charts + steps: + - name: setup helm + uses: azure/setup-helm@v4.0.0 + + - name: download artifacts + uses: actions/download-artifact@v4 + with: + pattern: shared-* + merge-multiple: true + path: "shared" + + - name: diff helm charts + id: diff_helm_charts + run: | + for chart in ${{ needs.get_changed_helm_charts.outputs.charts }}; do + chart_diff_output=$(diff -r "shared/base-charts/${chart}" "shared/head-charts/${chart}" || true) + if [ -n "$chart_diff_output" ]; then + echo -e "Changes found in chart: ${chart}\n$(diff -ruN shared/base-charts/${chart} shared/head-charts/${chart})\n" >> diff.log + fi + done + + - name: post diff as comment on pull request + if: needs.get_changed_helm_charts.outputs.charts != '' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const diff = fs.readFileSync('diff.log', 'utf8'); + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: '```diff\n' + diff + '\n```' + }) From 381d4a51e9e0e60d0879610d148c3d5daa7f2761 Mon Sep 17 00:00:00 2001 From: Dustin Lactin Date: Wed, 6 Mar 2024 10:26:00 -0700 Subject: [PATCH 2/4] fix: Extended github script to split large diffs into multiple comments, using summary comments instead of full blocks, excluding empty files from diff comments --- render-and-diff-helm/action.yml | 86 ++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 23 deletions(-) diff --git a/render-and-diff-helm/action.yml b/render-and-diff-helm/action.yml index 837cce1..0d622a7 100644 --- a/render-and-diff-helm/action.yml +++ b/render-and-diff-helm/action.yml @@ -5,6 +5,7 @@ name: Matrix render and diff helm charts description: Renders BASE and REF helm charts then posts the diff of the rendered charts as a comment on the pull request + on: workflow_call: inputs: @@ -35,7 +36,7 @@ jobs: git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} echo matrix_changed_charts=$(git diff --name-only ${{ github.base_ref }}...HEAD -- '**/k8s/**/*.yaml' '**/k8s/**/*.yml' '**/k8s/**/*.tpl' '**/k8s/**/*.tmpl' | cut -d'/' -f1,2,3 | uniq | jq -R 'split("\n")' | jq -s 'flatten(1)') >> $GITHUB_OUTPUT echo changed_charts=$(git diff --name-only ${{ github.base_ref }}...HEAD -- '**/k8s/**/*.yaml' '**/k8s/**/*.yml' '**/k8s/**/*.tpl' '**/k8s/**/*.tmpl' | cut -d'/' -f1,2,3 | uniq) >> $GITHUB_OUTPUT - + render_head_ref_charts: runs-on: ubuntu-latest needs: get_changed_helm_charts @@ -54,13 +55,16 @@ jobs: run: | mkdir -p shared/head-charts git fetch origin ${{ github.head_ref }} - git checkout ${{ github.head_ref }} - values_files="${{ matrix.chart }}"/values-* - for values_file in $(basename -a $values_files); do - helm template "${{ matrix.chart }}" -f "${{ matrix.chart }}/values.yaml" -f "${{ matrix.chart }}/${values_file}" --output-dir "shared/head-charts/${{ matrix.chart }}/${values_file}" - done + git checkout ${{ github.head_ref }} -- + if [ -d "${{ matrix.chart }}" ]; then + helm dependency build "${{ matrix.chart }}" + values_files="${{ matrix.chart }}"/values-* + for values_file in $(basename -a $values_files); do + helm template "${{ matrix.chart }}" -f "${{ matrix.chart }}/values.yaml" -f "${{ matrix.chart }}/${values_file}" --output-dir "shared/head-charts/${{ matrix.chart }}/${values_file}" + done + fi echo sanitized_name=$(echo "${{ matrix.chart }}" | sed 's/\//-/g') >> $GITHUB_OUTPUT - + echo "${{ matrix.chart }} does not exist in ${{ github.base_ref }} ref" - name: upload artifact uses: actions/upload-artifact@v4 with: @@ -85,13 +89,16 @@ jobs: run: | mkdir -p shared/base-charts git fetch origin ${{ github.base_ref }} - git checkout ${{ github.base_ref }} - values_files="${{ matrix.chart }}"/values-* - for values_file in $(basename -a $values_files); do - helm template "${{ matrix.chart }}" -f "${{ matrix.chart }}/values.yaml" -f "${{ matrix.chart }}/${values_file}" --output-dir "shared/base-charts/${{ matrix.chart }}/${values_file}" - done + git checkout ${{ github.base_ref }} -- + if [ -d "${{ matrix.chart }}" ]; then + helm dependency build "${{ matrix.chart }}" + values_files="${{ matrix.chart }}"/values-* + for values_file in $(basename -a $values_files); do + helm template "${{ matrix.chart }}" -f "${{ matrix.chart }}/values.yaml" -f "${{ matrix.chart }}/${values_file}" --output-dir "shared/base-charts/${{ matrix.chart }}/${values_file}" + done + fi echo sanitized_name=$(echo "${{ matrix.chart }}" | sed 's/\//-/g') >> $GITHUB_OUTPUT - + echo "${{ matrix.chart }} does not exist in ${{ github.base_ref }} ref" - name: upload artifact uses: actions/upload-artifact@v4 with: @@ -100,7 +107,7 @@ jobs: diff_helm_charts: runs-on: ubuntu-latest - needs: + needs: - get_changed_helm_charts - render_base_ref_charts - render_head_ref_charts @@ -121,21 +128,54 @@ jobs: for chart in ${{ needs.get_changed_helm_charts.outputs.charts }}; do chart_diff_output=$(diff -r "shared/base-charts/${chart}" "shared/head-charts/${chart}" || true) if [ -n "$chart_diff_output" ]; then - echo -e "Changes found in chart: ${chart}\n$(diff -ruN shared/base-charts/${chart} shared/head-charts/${chart})\n" >> diff.log + echo -e "Changes found in chart: ${chart}\n$(diff -ru shared/base-charts/${chart} shared/head-charts/${chart})\n" >> diff.log fi done - - name: post diff as comment on pull request if: needs.get_changed_helm_charts.outputs.charts != '' uses: actions/github-script@v7 with: script: | const fs = require('fs'); + const comment_char_limit = 65536; // GitHub comment character limit const diff = fs.readFileSync('diff.log', 'utf8'); - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: '```diff\n' + diff + '\n```' - }) + + function splitComment(comment, maxSize, sepEnd, sepStart, comStart) { + // Adapted from Atlantis SplitComment function + // https://github.com/runatlantis/atlantis/blob/main/server/events/vcs/common/common.go#L18 + if (comment.length <= (comment_char_limit - comStart.length)) { + return [comStart + diff] + } + maxWithSep = comment_char_limit - sepEnd.length - sepStart.length; + var comments = []; + var numComments = Math.ceil(comment.length / maxWithSep); + for (var i = 0; i < numComments; i++) { + var upTo = Math.min(comment.length, (i + 1) * maxWithSep); + var portion = comment.slice(i * maxWithSep, upTo); + if (i < numComments - 1) { + portion += sepEnd; + } + if (i > 0) { + portion = sepStart + portion + } else { + portion = comStart + portion + } + comments.push(portion); + } + return comments; + } + + var sepEnd = "\n```\n" + "\n
\n\n**Warning**: Output length greater than max comment size. Continued in next comment."; + var sepStart = "Continued from previous comment.\n
Show Output\n\n" + "```diff\n"; + var comStart = "Changes found in Helm charts.\n
Show Output\n\n" + "```diff\n"; + + comments = splitComment(diff, comment_char_limit, sepEnd, sepStart, comStart); + + for (const comment of comments) { + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }) + } \ No newline at end of file From c058aa120e4f1a89e685b16a00c996d1e1be6a7d Mon Sep 17 00:00:00 2001 From: Dustin Lactin Date: Wed, 6 Mar 2024 10:31:35 -0700 Subject: [PATCH 3/4] fix: removed chart name and ref output from render_base and render_head steps --- render-and-diff-helm/action.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/render-and-diff-helm/action.yml b/render-and-diff-helm/action.yml index 0d622a7..196541f 100644 --- a/render-and-diff-helm/action.yml +++ b/render-and-diff-helm/action.yml @@ -64,7 +64,6 @@ jobs: done fi echo sanitized_name=$(echo "${{ matrix.chart }}" | sed 's/\//-/g') >> $GITHUB_OUTPUT - echo "${{ matrix.chart }} does not exist in ${{ github.base_ref }} ref" - name: upload artifact uses: actions/upload-artifact@v4 with: @@ -98,7 +97,6 @@ jobs: done fi echo sanitized_name=$(echo "${{ matrix.chart }}" | sed 's/\//-/g') >> $GITHUB_OUTPUT - echo "${{ matrix.chart }} does not exist in ${{ github.base_ref }} ref" - name: upload artifact uses: actions/upload-artifact@v4 with: From 31a3aa40b702d63f30e7fde51d57f8e820df04e9 Mon Sep 17 00:00:00 2001 From: Dustin Lactin Date: Wed, 6 Mar 2024 12:07:09 -0700 Subject: [PATCH 4/4] fix: updated example to include permissions required to consume workflow --- render-and-diff-helm/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render-and-diff-helm/README.md b/render-and-diff-helm/README.md index 46f2081..dfd9ddf 100644 --- a/render-and-diff-helm/README.md +++ b/render-and-diff-helm/README.md @@ -21,7 +21,7 @@ jobs: run_helm_chart_diff: permissions: contents: read - id-token: write + pull-requests: write runs-on: ubuntu-latest steps: - name: Render and diff modified helm charts