From 15441582584978530e0b93aed7f07d84529e87d3 Mon Sep 17 00:00:00 2001 From: girardy Date: Tue, 24 Mar 2026 14:55:45 +0100 Subject: [PATCH 1/3] ci: refactor helm chart sync workflows into reusable template - Extract chart synchronization logic into a reusable workflow (`sync-chart-template.yml`) - Update `sync-operator-charts.yml` to use the new template, splitting the sync into two distinct jobs - Add new `sync-netbird-chart.yml` workflow to sync the main netbird chart --- .github/workflows/sync-chart-template.yml | 149 ++++++++++++++++++ .github/workflows/sync-netbird-chart.yml | 18 +++ .github/workflows/sync-operator-charts.yml | 167 +++------------------ 3 files changed, 188 insertions(+), 146 deletions(-) create mode 100644 .github/workflows/sync-chart-template.yml create mode 100644 .github/workflows/sync-netbird-chart.yml diff --git a/.github/workflows/sync-chart-template.yml b/.github/workflows/sync-chart-template.yml new file mode 100644 index 0000000..674825f --- /dev/null +++ b/.github/workflows/sync-chart-template.yml @@ -0,0 +1,149 @@ +name: Sync Chart Template + +on: + workflow_call: + inputs: + upstream_repo: + required: true + type: string + description: "Upstream repository (e.g., netbirdio/netbird)" + upstream_branch: + required: true + type: string + description: "Upstream branch to sync from" + upstream_path: + required: true + type: string + description: "Path in upstream (e.g., infrastructure_files/helm/netbird)" + local_path: + required: true + type: string + description: "Local path (e.g., charts/netbird)" + chart_name: + required: true + type: string + description: "Chart name for display (e.g., netbird)" + pr_branch_prefix: + required: true + type: string + description: "PR branch prefix (e.g., sync/netbird-chart)" + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Quick version check + id: version-check + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + UPSTREAM_REPO="${{ inputs.upstream_repo }}" + UPSTREAM_BRANCH="${{ inputs.upstream_branch }}" + UPSTREAM_PATH="${{ inputs.upstream_path }}" + LOCAL_PATH="${{ inputs.local_path }}" + + upstream_version=$(curl -s \ + -H "Authorization: token $GH_TOKEN" \ + "https://api.github.com/repos/${UPSTREAM_REPO}/contents/${UPSTREAM_PATH}/Chart.yaml?ref=${UPSTREAM_BRANCH}" \ + | jq -r '.content' | base64 -d | grep '^version:' | awk '{print $2}' 2>/dev/null) + + local_version=$(curl -s \ + -H "Authorization: token $GH_TOKEN" \ + "https://api.github.com/repos/${{ github.repository }}/contents/${LOCAL_PATH}/Chart.yaml?ref=main" \ + | jq -r '.content' | base64 -d | grep '^version:' | awk '{print $2}' 2>/dev/null) + + if [ -z "$upstream_version" ] || [ -z "$local_version" ]; then + echo "Warning: Could not fetch version for $LOCAL_PATH (upstream: $UPSTREAM_PATH)" + echo "changed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "✓ ${{ inputs.chart_name }} | Local: $local_version | Upstream: $upstream_version" + + if [ "$upstream_version" != "$local_version" ]; then + echo "changed=true" >> "$GITHUB_OUTPUT" + echo " → Version change detected: $local_version → $upstream_version" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + fi + + - name: Check for existing sync PR + if: steps.version-check.outputs.changed == 'true' + id: check-pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + existing_pr=$(gh pr list \ + --repo "${{ github.repository }}" \ + --head "${{ inputs.pr_branch_prefix }}" \ + --state open \ + --json number \ + --jq '.[0].number // empty') + + if [ -n "$existing_pr" ]; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "Open sync PR already exists: #${existing_pr}" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Checkout this repo + if: steps.version-check.outputs.changed == 'true' && steps.check-pr.outputs.skip != 'true' + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.CHART_SYNC_TOKEN }} + + - name: Clone upstream and sync charts + if: steps.version-check.outputs.changed == 'true' && steps.check-pr.outputs.skip != 'true' + id: sync + run: | + UPSTREAM_REPO="https://github.com/${{ inputs.upstream_repo }}.git" + + git clone --depth 1 "$UPSTREAM_REPO" /tmp/upstream + + upstream_sha=$(git -C /tmp/upstream rev-parse --short HEAD) + echo "upstream_sha=$upstream_sha" >> "$GITHUB_OUTPUT" + + rsync -avc --delete "/tmp/upstream/${{ inputs.upstream_path }}/" "${{ inputs.local_path }}/" + + version=$(grep '^version:' "${{ inputs.local_path }}/Chart.yaml" | awk '{print $2}') + echo "version=$version" >> "$GITHUB_OUTPUT" + + rm -rf /tmp/upstream + + - name: Create PR + if: steps.version-check.outputs.changed == 'true' && steps.check-pr.outputs.skip != 'true' + env: + GH_TOKEN: ${{ secrets.CHART_SYNC_TOKEN }} + run: | + BRANCH="${{ inputs.pr_branch_prefix }}-${{ steps.sync.outputs.upstream_sha }}" + + if git diff --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then + echo "No file changes after sync" + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git checkout -b "$BRANCH" + git add -A ${{ inputs.local_path }} + git commit -m "sync: ${{ inputs.chart_name }} from upstream (${{ steps.sync.outputs.upstream_sha }})" + git push origin "$BRANCH" + + BODY="## Synced Chart from Upstream + + Upstream commit: ${{ inputs.upstream_repo }}@${{ steps.sync.outputs.upstream_sha }} + + | Chart | Version | + |-------|---------| + | ${{ inputs.chart_name }} | ${{ steps.sync.outputs.version }} |" + + gh pr create \ + --title "sync: ${{ inputs.chart_name }} from upstream" \ + --body "$BODY" \ + --base main \ + --head "$BRANCH" + + gh pr merge "$BRANCH" --auto --squash diff --git a/.github/workflows/sync-netbird-chart.yml b/.github/workflows/sync-netbird-chart.yml new file mode 100644 index 0000000..acfc400 --- /dev/null +++ b/.github/workflows/sync-netbird-chart.yml @@ -0,0 +1,18 @@ +name: Sync Netbird Chart + +on: + schedule: + - cron: '0 */6 * * *' + workflow_dispatch: + +jobs: + sync-netbird-chart: + uses: ./.github/workflows/sync-chart-template.yml + with: + upstream_repo: netbirdio/netbird + upstream_branch: main + upstream_path: infrastructure_files/helm/netbird + local_path: charts/netbird + chart_name: netbird + pr_branch_prefix: sync/netbird-chart + secrets: inherit diff --git a/.github/workflows/sync-operator-charts.yml b/.github/workflows/sync-operator-charts.yml index bc82445..aff526a 100644 --- a/.github/workflows/sync-operator-charts.yml +++ b/.github/workflows/sync-operator-charts.yml @@ -6,149 +6,24 @@ on: workflow_dispatch: jobs: - sync: - runs-on: ubuntu-latest - steps: - - name: Quick version check - id: version-check - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - UPSTREAM_REPO="netbirdio/kubernetes-operator" - UPSTREAM_BRANCH="main" - - # Fetch upstream Chart.yaml files - upstream_operator_version=$(curl -s \ - -H "Authorization: token $GH_TOKEN" \ - "https://api.github.com/repos/${UPSTREAM_REPO}/contents/helm/kubernetes-operator/Chart.yaml?ref=${UPSTREAM_BRANCH}" \ - | jq -r '.content' | base64 -d | grep '^version:' | awk '{print $2}') - - upstream_config_version=$(curl -s \ - -H "Authorization: token $GH_TOKEN" \ - "https://api.github.com/repos/${UPSTREAM_REPO}/contents/helm/netbird-operator-config/Chart.yaml?ref=${UPSTREAM_BRANCH}" \ - | jq -r '.content' | base64 -d | grep '^version:' | awk '{print $2}') - - # Fetch local Chart.yaml files - local_operator_version=$(curl -s \ - -H "Authorization: token $GH_TOKEN" \ - "https://api.github.com/repos/${{ github.repository }}/contents/charts/kubernetes-operator/Chart.yaml?ref=main" \ - | jq -r '.content' | base64 -d | grep '^version:' | awk '{print $2}') - - local_config_version=$(curl -s \ - -H "Authorization: token $GH_TOKEN" \ - "https://api.github.com/repos/${{ github.repository }}/contents/charts/netbird-operator-config/Chart.yaml?ref=main" \ - | jq -r '.content' | base64 -d | grep '^version:' | awk '{print $2}') - - echo "upstream_operator_version=$upstream_operator_version" >> "$GITHUB_OUTPUT" - echo "upstream_config_version=$upstream_config_version" >> "$GITHUB_OUTPUT" - echo "local_operator_version=$local_operator_version" >> "$GITHUB_OUTPUT" - echo "local_config_version=$local_config_version" >> "$GITHUB_OUTPUT" - - if [ "$upstream_operator_version" = "$local_operator_version" ] && \ - [ "$upstream_config_version" = "$local_config_version" ]; then - echo "changed=false" >> "$GITHUB_OUTPUT" - echo "No version changes detected" - else - echo "changed=true" >> "$GITHUB_OUTPUT" - echo "Version changes detected:" - echo " kubernetes-operator: $local_operator_version -> $upstream_operator_version" - echo " netbird-operator-config: $local_config_version -> $upstream_config_version" - fi - - - name: Check for existing sync PR - if: steps.version-check.outputs.changed == 'true' - id: check-pr - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - existing_pr=$(gh pr list \ - --repo "${{ github.repository }}" \ - --head "sync/operator-charts" \ - --state open \ - --json number \ - --jq '.[0].number // empty') - - if [ -n "$existing_pr" ]; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "Open sync PR already exists: #${existing_pr}" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - fi - - - name: Checkout this repo - if: steps.version-check.outputs.changed == 'true' && steps.check-pr.outputs.skip != 'true' - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.CHART_SYNC_TOKEN }} - - - name: Clone upstream and sync charts - if: steps.version-check.outputs.changed == 'true' && steps.check-pr.outputs.skip != 'true' - id: sync - run: | - UPSTREAM_REPO="https://github.com/netbirdio/kubernetes-operator.git" - - # Shallow clone upstream - git clone --depth 1 "$UPSTREAM_REPO" /tmp/upstream - - # Get upstream short SHA - upstream_sha=$(git -C /tmp/upstream rev-parse --short HEAD) - echo "upstream_sha=$upstream_sha" >> "$GITHUB_OUTPUT" - - # Sync charts using rsync --delete to mirror upstream exactly - rsync -avc --delete /tmp/upstream/helm/kubernetes-operator/ charts/kubernetes-operator/ - rsync -avc --delete /tmp/upstream/helm/netbird-operator-config/ charts/netbird-operator-config/ - - # Extract versions - operator_version=$(grep '^version:' charts/kubernetes-operator/Chart.yaml | awk '{print $2}') - config_version=$(grep '^version:' charts/netbird-operator-config/Chart.yaml | awk '{print $2}') - echo "operator_version=$operator_version" >> "$GITHUB_OUTPUT" - echo "config_version=$config_version" >> "$GITHUB_OUTPUT" - - # Clean up - rm -rf /tmp/upstream - - - name: Create PR - if: steps.version-check.outputs.changed == 'true' && steps.check-pr.outputs.skip != 'true' - env: - GH_TOKEN: ${{ secrets.CHART_SYNC_TOKEN }} - run: | - BRANCH="sync/operator-charts-${{ steps.sync.outputs.upstream_sha }}" - OPERATOR_VERSION="${{ steps.sync.outputs.operator_version }}" - CONFIG_VERSION="${{ steps.sync.outputs.config_version }}" - - # Check if there are actual changes - if git diff --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then - echo "No file changes after sync — versions may differ in API but files are identical" - exit 0 - fi - - # Configure git - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - # Create branch, commit, and push - git checkout -b "$BRANCH" - git add -A charts/kubernetes-operator/ charts/netbird-operator-config/ - git commit -m "sync operator charts from upstream (${{ steps.sync.outputs.upstream_sha }})" - git push origin "$BRANCH" - - # Build PR body - BODY="## Synced Charts from Upstream - - Upstream commit: netbirdio/kubernetes-operator@${{ steps.sync.outputs.upstream_sha }} - - | Chart | Version | - |-------|---------| - | kubernetes-operator | ${OPERATOR_VERSION} | - | netbird-operator-config | ${CONFIG_VERSION} |" - - # Open PR - gh pr create \ - --title "sync operator charts from upstream" \ - --body "$BODY" \ - --base main \ - --head "$BRANCH" - - # Enable auto-merge (requires branch protection with required checks) - gh pr merge "$BRANCH" --auto --squash + sync-kubernetes-operator: + uses: ./.github/workflows/sync-chart-template.yml + with: + upstream_repo: netbirdio/kubernetes-operator + upstream_branch: main + upstream_path: helm/kubernetes-operator + local_path: charts/kubernetes-operator + chart_name: kubernetes-operator + pr_branch_prefix: sync/kubernetes-operator + secrets: inherit + + sync-netbird-operator-config: + uses: ./.github/workflows/sync-chart-template.yml + with: + upstream_repo: netbirdio/kubernetes-operator + upstream_branch: main + upstream_path: helm/netbird-operator-config + local_path: charts/netbird-operator-config + chart_name: netbird-operator-config + pr_branch_prefix: sync/netbird-operator-config + secrets: inherit From f840526b29572ad26ee4bcac983606c098a20875 Mon Sep 17 00:00:00 2001 From: girardy Date: Tue, 24 Mar 2026 16:32:30 +0100 Subject: [PATCH 2/3] ci: add version update workflows for netbird and dashboard components --- .github/workflows/sync-netbird-chart.yml | 25 ++-- .github/workflows/update-version-template.yml | 107 ++++++++++++++++++ 2 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/update-version-template.yml diff --git a/.github/workflows/sync-netbird-chart.yml b/.github/workflows/sync-netbird-chart.yml index acfc400..0480bba 100644 --- a/.github/workflows/sync-netbird-chart.yml +++ b/.github/workflows/sync-netbird-chart.yml @@ -6,13 +6,22 @@ on: workflow_dispatch: jobs: - sync-netbird-chart: - uses: ./.github/workflows/sync-chart-template.yml + update-app-version: + uses: ./.github/workflows/update-version-template.yml with: - upstream_repo: netbirdio/netbird - upstream_branch: main - upstream_path: infrastructure_files/helm/netbird - local_path: charts/netbird - chart_name: netbird - pr_branch_prefix: sync/netbird-chart + docker_repo: netbirdio/netbird + file_path: charts/netbird/Chart.yaml + key: appVersion + pr_prefix: bump/netbird-appversion + component_name: netbird + secrets: inherit + + update-dashboard-version: + uses: ./.github/workflows/update-version-template.yml + with: + docker_repo: netbirdio/dashboard + file_path: charts/netbird/values.yaml + key: dashboard.image.tag + pr_prefix: bump/netbird-dashboard + component_name: dashboard secrets: inherit diff --git a/.github/workflows/update-version-template.yml b/.github/workflows/update-version-template.yml new file mode 100644 index 0000000..91dfd1c --- /dev/null +++ b/.github/workflows/update-version-template.yml @@ -0,0 +1,107 @@ +name: Update Version Template + +on: + workflow_call: + inputs: + docker_repo: + required: true + type: string + description: "Docker Hub repository (e.g., netbirdio/netbird)" + file_path: + required: true + type: string + description: "File to update (e.g., charts/netbird/Chart.yaml)" + key: + required: true + type: string + description: "Key to update (e.g., appVersion or dashboard.image.tag)" + pr_prefix: + required: true + type: string + description: "PR branch prefix (e.g., update-netbird-appversion)" + component_name: + required: true + type: string + description: "Component name for PR title (e.g., netbird)" + +jobs: + update-version: + runs-on: ubuntu-latest + steps: + - name: Install yq + run: | + wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + chmod +x /usr/local/bin/yq + + - name: Get latest Docker tag + id: docker-tag + run: | + # Fetch tags and filter for semantic versioning (X.Y.Z format, no suffixes) + latest_tag=$(curl -s "https://registry.hub.docker.com/v2/repositories/${{ inputs.docker_repo }}/tags/?page_size=50" \ + | jq -r '.results[].name' \ + | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' \ + | sort -rV \ + | head -1) + + if [ -z "$latest_tag" ]; then + echo "Could not fetch latest semantic version tag for ${{ inputs.docker_repo }}" + exit 1 + fi + echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT" + + - name: Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.CHART_SYNC_TOKEN }} + + - name: Get current version + id: current-version + run: | + current_version=$(yq eval ".${{ inputs.key }}" "${{ inputs.file_path }}" | tr -d '"') + echo "current_version=$current_version" >> "$GITHUB_OUTPUT" + + - name: Check if update needed + id: check-update + run: | + if [ "${{ steps.docker-tag.outputs.latest_tag }}" != "${{ steps.current-version.outputs.current_version }}" ]; then + echo "update=true" >> "$GITHUB_OUTPUT" + else + echo "update=false" >> "$GITHUB_OUTPUT" + fi + + - name: Update version in file + if: steps.check-update.outputs.update == 'true' + run: | + yq eval -i ".${{ inputs.key }} = \"${{ steps.docker-tag.outputs.latest_tag }}\"" "${{ inputs.file_path }}" + + - name: Create PR + if: steps.check-update.outputs.update == 'true' + env: + GH_TOKEN: ${{ secrets.CHART_SYNC_TOKEN }} + run: | + BRANCH="${{ inputs.pr_prefix }}-${{ steps.docker-tag.outputs.latest_tag }}" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git checkout -b "$BRANCH" + git add "${{ inputs.file_path }}" + git commit -m "chore: update ${{ inputs.component_name }} ${{ inputs.key }} to ${{ steps.docker-tag.outputs.latest_tag }}" + git push origin "$BRANCH" + + BODY="## Updated ${{ inputs.component_name }} ${{ inputs.key }} + + Updated ${{ inputs.key }} to ${{ steps.docker-tag.outputs.latest_tag }} based on latest Docker image tag. + + | Component | ${{ inputs.key }} | + |-----------|-------------------| + | ${{ inputs.component_name }} | ${{ steps.docker-tag.outputs.latest_tag }} |" + + gh pr create \ + --title "chore: update ${{ inputs.component_name }} ${{ inputs.key }} to ${{ steps.docker-tag.outputs.latest_tag }}" \ + --body "$BODY" \ + --base main \ + --head "$BRANCH" + + gh pr merge "$BRANCH" --auto --squash From 196e8e468db001c719a0baab65d3731e8a6fefac Mon Sep 17 00:00:00 2001 From: girardy Date: Tue, 24 Mar 2026 16:40:53 +0100 Subject: [PATCH 3/3] ci: add version update workflows for netbird and dashboard components --- .github/workflows/update-version-template.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-version-template.yml b/.github/workflows/update-version-template.yml index 91dfd1c..e41299f 100644 --- a/.github/workflows/update-version-template.yml +++ b/.github/workflows/update-version-template.yml @@ -36,10 +36,10 @@ jobs: - name: Get latest Docker tag id: docker-tag run: | - # Fetch tags and filter for semantic versioning (X.Y.Z format, no suffixes) + # Fetch tags and filter for semantic versioning (X.Y.Z or vX.Y.Z format, no suffixes) latest_tag=$(curl -s "https://registry.hub.docker.com/v2/repositories/${{ inputs.docker_repo }}/tags/?page_size=50" \ | jq -r '.results[].name' \ - | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' \ + | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' \ | sort -rV \ | head -1)