From 52b6134790cd2140b41916b1069a3aa27bdfd4d0 Mon Sep 17 00:00:00 2001 From: Steven Wade Date: Tue, 11 Mar 2025 18:48:45 +0000 Subject: [PATCH 1/5] fix: compare BASE with merged state instead of HEAD directly Signed-off-by: Steven Wade --- README.md | 13 +++++++++++- kustdiff | 60 +++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 1e05108..969635d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ Managing Kubernetes configurations across multiple environments and PRs can be c ## Features - Automatically builds Kustomize configurations from both PR branches -- Generates a diff between base and head configurations +- Generates a diff showing what would actually change upon merge (not just differences between branches) +- Intelligently handles parallel changes to base and feature branches - Configurable root directory and search depth for Kustomize files - Commits must meet [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) - Automated with GitHub Actions ([commit-lint](https://github.com/conventional-changelog/commitlint/#what-is-commitlint)) @@ -22,6 +23,16 @@ Managing Kubernetes configurations across multiple environments and PRs can be c - Commits must be signed with [Developer Certificate of Origin (DCO)](https://developercertificate.org/) - Automated with GitHub App ([DCO](https://github.com/apps/dco)) +## How It Works +Unlike simple diff tools, this action: + +1. Builds Kustomize output from the base branch +1. Creates a temporary merge of the head branch into base (simulating the actual PR merge) +1. Builds Kustomize output from the merged state +1. Compares these outputs to show exactly what would change after merging + +This approach correctly handles cases where changes have been made to the base branch in parallel to the feature branch, avoiding misleading diffs that incorrectly suggest changes would be reverted. + ## Inputs | Name | Description | Required | Default | diff --git a/kustdiff b/kustdiff index 0ff25ef..b208193 100755 --- a/kustdiff +++ b/kustdiff @@ -39,23 +39,22 @@ function safe_filename() { echo "$1" | sed 's/[^a-zA-Z0-9.]/_/g' } -function build { +function build_ref { local ref="$1" - local safe_ref=$(safe_filename "$ref") + local output_dir="$2" echo "Checking out ref: $ref" git checkout "$ref" --quiet - mkdir -p "$TMP_DIR/$safe_ref" + mkdir -p "$output_dir" for envpath in $(get_targets); do local relative_path="${envpath#$ROOT_DIR/}" local safe_path=$(safe_filename "$relative_path") - local output_file="$TMP_DIR/$safe_ref/${safe_path}.yaml" + local output_file="$output_dir/${safe_path}.yaml" echo "Running kustomize for $envpath" kustomize build "$envpath" -o "$output_file" -# debug_log "Contents of $output_file:" -# debug_log "$(cat "$output_file")" -# debug_log "End of $output_file" -# debug_log "------------------------------------" + if [ "$DEBUG" = "true" ]; then + debug_log "Built kustomize for $envpath to $output_file" + fi done } @@ -65,29 +64,56 @@ function main { validate_max_depth git config --global --add safe.directory "$GITHUB_WORKSPACE" - local diff escaped_output output - build "$INPUT_HEAD_REF" - build "$INPUT_BASE_REF" - local safe_head_ref=$(safe_dirname "$INPUT_HEAD_REF") - local safe_base_ref=$(safe_dirname "$INPUT_BASE_REF") + # Save current state to restore later + local current_branch + current_branch=$(git rev-parse --abbrev-ref HEAD) + # Build BASE output + local safe_base_ref=$(safe_filename "$INPUT_BASE_REF") + local base_output_dir="$TMP_DIR/base" + build_ref "$INPUT_BASE_REF" "$base_output_dir" + + # Create a temporary merge branch + local merge_branch="temp-merge-$RANDOM" + git checkout -b "$merge_branch" "$INPUT_BASE_REF" --quiet + + debug_log "Creating temporary merge of $INPUT_HEAD_REF into $INPUT_BASE_REF" + + # Attempt to merge HEAD into BASE + if ! git merge "$INPUT_HEAD_REF" --quiet; then + echo "Merge conflict detected. Cannot automatically merge $INPUT_HEAD_REF into $INPUT_BASE_REF." + git merge --abort + git checkout "$current_branch" --quiet + exit 1 + fi + + # Build merged output + local merged_output_dir="$TMP_DIR/merged" + build_ref "$merge_branch" "$merged_output_dir" + + # Compare outputs set +e - diff=$(git diff --no-index "$TMP_DIR/$safe_base_ref" "$TMP_DIR/$safe_head_ref") + diff=$(git diff --no-index "$base_output_dir" "$merged_output_dir") + local diff_exit_code=$? debug_log "Git diff output:" debug_log "$diff" debug_log "End of git diff output" debug_log "------------------------------------" - if [[ -z "$diff" ]]; then - output="No differences found between $INPUT_BASE_REF and $INPUT_HEAD_REF" + # Clean up temporary branches + git checkout "$current_branch" --quiet + git branch -D "$merge_branch" --quiet + + if [[ $diff_exit_code -eq 0 ]]; then + output="No differences found in kustomize output after merging $INPUT_HEAD_REF into $INPUT_BASE_REF" else # Just pass through the raw git diff output output="$diff" fi - escaped_output=${output//$'\n'/'%0A'} + local escaped_output=${output//$'\n'/'%0A'} if [ ${#escaped_output} -gt 65000 ]; then escaped_output="Output is greater than 65000 characters, and therefore too large to print as a github comment." From 922933cd5acd91b789ed2e2c004f54a6821658fc Mon Sep 17 00:00:00 2001 From: Steven Wade Date: Tue, 11 Mar 2025 19:13:01 +0000 Subject: [PATCH 2/5] feat: upgrading alpine and kustomize to align with latest flux release Signed-off-by: Steven Wade --- Dockerfile | 4 ++-- Makefile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6da91d6..7efd731 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ -FROM alpine:3.21.2 +FROM alpine:3.21.3 RUN apk update && apk --no-cache add bash curl git SHELL ["/bin/bash", "-o", "pipefail", "-c"] -ARG KUSTOMIZE=5.4.3 +ARG KUSTOMIZE=5.6.0 RUN curl -sL https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE}/kustomize_v${KUSTOMIZE}_linux_amd64.tar.gz | \ tar xz && mv kustomize /usr/local/bin/kustomize diff --git a/Makefile b/Makefile index 912b493..5cd38bb 100644 --- a/Makefile +++ b/Makefile @@ -16,4 +16,4 @@ build: docker buildx build --build-arg BUILDPLATFORM=$(BUILDPLATFORM) --build-arg TARGETARCH=$(GOARCH) -t local/$(PROJNAME) . scan: build - trivy --light -s "UNKNOWN,MEDIUM,HIGH,CRITICAL" --exit-code 1 local/$(PROJNAME) + trivy image -s "UNKNOWN,MEDIUM,HIGH,CRITICAL" --exit-code 1 local/$(PROJNAME) From 643ac8d517eefb63828698af169bb8db7933dc26 Mon Sep 17 00:00:00 2001 From: Steven Wade Date: Thu, 13 Mar 2025 09:56:51 +0000 Subject: [PATCH 3/5] fix: improve branch reference handling and compare with merged state Signed-off-by: Steven Wade --- kustdiff | 70 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/kustdiff b/kustdiff index b208193..5ae4611 100755 --- a/kustdiff +++ b/kustdiff @@ -39,6 +39,14 @@ function safe_filename() { echo "$1" | sed 's/[^a-zA-Z0-9.]/_/g' } +function git_with_error_handling() { + if ! "$@"; then + echo "Error executing git command: $*" + return 1 + fi + return 0 +} + function build_ref { local ref="$1" local output_dir="$2" @@ -63,28 +71,65 @@ function main { validate_root_dir validate_max_depth - git config --global --add safe.directory "$GITHUB_WORKSPACE" + git_with_error_handling git config --global --add safe.directory "$GITHUB_WORKSPACE" # Save current state to restore later local current_branch - current_branch=$(git rev-parse --abbrev-ref HEAD) + current_branch=$(git rev-parse --abbrev-ref HEAD || echo "detached") + + # Check if the branch exists locally or as a remote reference + function resolve_branch_ref() { + local branch_name="$1" + + # First check if it's a local branch + if git show-ref --verify --quiet "refs/heads/$branch_name"; then + echo "$branch_name" + return 0 + fi + + # Next check if it's a remote branch + if git show-ref --verify --quiet "refs/remotes/origin/$branch_name"; then + echo "origin/$branch_name" + return 0 + fi + + # Finally check if it's a valid commit SHA + if git cat-file -e "$branch_name^{commit}" 2>/dev/null; then + echo "$branch_name" + return 0 + fi + + # If we get here, we couldn't resolve the reference + echo "Error: Could not resolve reference: $branch_name" + return 1 + } + + # Resolve the BASE and HEAD references + local base_ref_resolved + base_ref_resolved=$(resolve_branch_ref "$INPUT_BASE_REF") || exit 1 + debug_log "Resolved base reference: $base_ref_resolved (from $INPUT_BASE_REF)" # Build BASE output local safe_base_ref=$(safe_filename "$INPUT_BASE_REF") local base_output_dir="$TMP_DIR/base" - build_ref "$INPUT_BASE_REF" "$base_output_dir" + build_ref "$base_ref_resolved" "$base_output_dir" + + # Resolve HEAD reference + local head_ref_resolved + head_ref_resolved=$(resolve_branch_ref "$INPUT_HEAD_REF") || exit 1 + debug_log "Resolved head reference: $head_ref_resolved (from $INPUT_HEAD_REF)" # Create a temporary merge branch local merge_branch="temp-merge-$RANDOM" - git checkout -b "$merge_branch" "$INPUT_BASE_REF" --quiet + git checkout -b "$merge_branch" "$base_ref_resolved" --quiet - debug_log "Creating temporary merge of $INPUT_HEAD_REF into $INPUT_BASE_REF" + debug_log "Creating temporary merge of $head_ref_to_use into $base_ref_to_use (via $merge_branch)" # Attempt to merge HEAD into BASE - if ! git merge "$INPUT_HEAD_REF" --quiet; then - echo "Merge conflict detected. Cannot automatically merge $INPUT_HEAD_REF into $INPUT_BASE_REF." - git merge --abort - git checkout "$current_branch" --quiet + if ! git merge "$head_ref_to_use" --quiet; then + echo "Merge conflict detected. Cannot automatically merge $head_ref_to_use into $base_ref_to_use." + git merge --abort || true + git checkout "$current_branch" --quiet || git checkout "$base_ref_to_use" --quiet || true exit 1 fi @@ -94,17 +139,18 @@ function main { # Compare outputs set +e - diff=$(git diff --no-index "$base_output_dir" "$merged_output_dir") + diff=$(git diff --no-index "$base_output_dir" "$merged_output_dir" 2>&1) local diff_exit_code=$? + debug_log "Git diff exit code: $diff_exit_code" debug_log "Git diff output:" debug_log "$diff" debug_log "End of git diff output" debug_log "------------------------------------" # Clean up temporary branches - git checkout "$current_branch" --quiet - git branch -D "$merge_branch" --quiet + git checkout "$current_branch" --quiet || git checkout "$base_ref_to_use" --quiet || true + git branch -D "$merge_branch" --quiet || true if [[ $diff_exit_code -eq 0 ]]; then output="No differences found in kustomize output after merging $INPUT_HEAD_REF into $INPUT_BASE_REF" From 0f97029fad0a6bae964c75eb49d9b19c94773fe6 Mon Sep 17 00:00:00 2001 From: Steven Wade Date: Thu, 13 Mar 2025 16:01:35 +0000 Subject: [PATCH 4/5] fix: improve branch reference handling for kustomize-diff Signed-off-by: Steven Wade --- kustdiff | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/kustdiff b/kustdiff index 5ae4611..5156dc6 100755 --- a/kustdiff +++ b/kustdiff @@ -39,14 +39,6 @@ function safe_filename() { echo "$1" | sed 's/[^a-zA-Z0-9.]/_/g' } -function git_with_error_handling() { - if ! "$@"; then - echo "Error executing git command: $*" - return 1 - fi - return 0 -} - function build_ref { local ref="$1" local output_dir="$2" @@ -71,7 +63,7 @@ function main { validate_root_dir validate_max_depth - git_with_error_handling git config --global --add safe.directory "$GITHUB_WORKSPACE" + git config --global --add safe.directory "$GITHUB_WORKSPACE" || true # Save current state to restore later local current_branch @@ -123,13 +115,13 @@ function main { local merge_branch="temp-merge-$RANDOM" git checkout -b "$merge_branch" "$base_ref_resolved" --quiet - debug_log "Creating temporary merge of $head_ref_to_use into $base_ref_to_use (via $merge_branch)" + debug_log "Creating temporary merge of $head_ref_resolved into $base_ref_resolved (via $merge_branch)" # Attempt to merge HEAD into BASE - if ! git merge "$head_ref_to_use" --quiet; then - echo "Merge conflict detected. Cannot automatically merge $head_ref_to_use into $base_ref_to_use." + if ! git merge "$head_ref_resolved" --quiet; then + echo "Merge conflict detected. Cannot automatically merge $INPUT_HEAD_REF into $INPUT_BASE_REF." git merge --abort || true - git checkout "$current_branch" --quiet || git checkout "$base_ref_to_use" --quiet || true + git checkout "$current_branch" --quiet || git checkout "$base_ref_resolved" --quiet || true exit 1 fi @@ -149,7 +141,7 @@ function main { debug_log "------------------------------------" # Clean up temporary branches - git checkout "$current_branch" --quiet || git checkout "$base_ref_to_use" --quiet || true + git checkout "$current_branch" --quiet || git checkout "$base_ref_resolved" --quiet || true git branch -D "$merge_branch" --quiet || true if [[ $diff_exit_code -eq 0 ]]; then From b7c34be7c1964a1e9b0ddc553cfffee2d81ecab7 Mon Sep 17 00:00:00 2001 From: Steven Wade Date: Thu, 10 Apr 2025 15:57:20 +0100 Subject: [PATCH 5/5] fix: improve branch reference handling and git identity for kustomize-diff Signed-off-by: Steven Wade --- kustdiff | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kustdiff b/kustdiff index 5156dc6..c659440 100755 --- a/kustdiff +++ b/kustdiff @@ -63,6 +63,10 @@ function main { validate_root_dir validate_max_depth + # Set up git identity for merge operations + git config --global user.email "github-actions@github.com" || true + git config --global user.name "GitHub Actions" || true + git config --global --add safe.directory "$GITHUB_WORKSPACE" || true # Save current state to restore later