Compare commits

..

1 Commits

Author SHA1 Message Date
faizanahmad055
c9fb423f8d Use SHA512 instead of SHA1
Signed-off-by: faizanahmad055 <faizan.ahmad55@outlook.com>
2026-01-12 19:33:26 +01:00
23 changed files with 51 additions and 759 deletions

View File

@@ -54,7 +54,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.26'
go-version: '1.25'
cache: false
- name: Set up Docker Buildx

View File

@@ -22,7 +22,6 @@ env:
KUBERNETES_VERSION: "1.30.0"
KIND_VERSION: "0.23.0"
REGISTRY: ghcr.io
RELOADER_EDITION: oss
jobs:
qa:
@@ -155,7 +154,6 @@ jobs:
VERSION=merge-${{ steps.generate_tag.outputs.GIT_TAG }}
COMMIT=${{github.event.pull_request.head.sha}}
BUILD_DATE=${{ steps.prep.outputs.created }}
EDITION=${{ env.RELOADER_EDITION }}
BUILD_PARAMETERS=${{ env.BUILD_PARAMETERS }}
cache-to: type=inline
@@ -175,10 +173,6 @@ jobs:
pull: true
push: false
build-args: |
VERSION=merge-${{ steps.generate_tag.outputs.GIT_UBI_TAG }}
COMMIT=${{github.event.pull_request.head.sha}}
BUILD_DATE=${{ steps.prep.outputs.created }}
EDITION=${{ env.RELOADER_EDITION }}
BUILD_PARAMETERS=${{ env.BUILD_PARAMETERS }}
BUILDER_IMAGE=${{ env.GHCR_IMAGE_REPOSITORY }}:${{ steps.highest_tag.outputs.tag }}
cache-to: type=inline

View File

@@ -15,7 +15,6 @@ env:
KIND_VERSION: "0.23.0"
HELM_REGISTRY_URL: "https://stakater.github.io/stakater-charts"
REGISTRY: ghcr.io
RELOADER_EDITION: oss
jobs:
build:
@@ -104,12 +103,7 @@ jobs:
file: ${{ env.DOCKER_FILE_PATH }}
pull: true
push: true
build-args: |
VERSION=merge-${{ github.event.number }}
COMMIT=${{ github.sha }}
BUILD_DATE=${{ steps.prep.outputs.created }}
EDITION=${{ env.RELOADER_EDITION }}
BUILD_PARAMETERS=${{ env.BUILD_PARAMETERS }}
build-args: BUILD_PARAMETERS=${{ env.BUILD_PARAMETERS }}
cache-to: type=inline
platforms: linux/amd64,linux/arm,linux/arm64
tags: |
@@ -158,7 +152,6 @@ jobs:
VERSION=merge-${{ github.event.number }}
COMMIT=${{ github.sha }}
BUILD_DATE=${{ steps.prep.outputs.created }}
EDITION=${{ env.RELOADER_EDITION }}
BUILD_PARAMETERS=${{ env.BUILD_PARAMETERS }}
cache-to: type=inline
platforms: linux/amd64,linux/arm,linux/arm64

View File

@@ -11,7 +11,6 @@ env:
KUBERNETES_VERSION: "1.30.0"
KIND_VERSION: "0.23.0"
REGISTRY: ghcr.io
RELOADER_EDITION: oss
jobs:
release:
@@ -111,7 +110,6 @@ jobs:
VERSION=${{ steps.generate_tag.outputs.RELEASE_VERSION }}
COMMIT=${{ github.sha }}
BUILD_DATE=${{ steps.prep.outputs.created }}
EDITION=${{ env.RELOADER_EDITION }}
labels: |
org.opencontainers.image.source=${{ github.event.repository.clone_url }}
org.opencontainers.image.created=${{ steps.prep.outputs.created }}
@@ -162,7 +160,6 @@ jobs:
VERSION=${{ steps.generate_tag.outputs.RELEASE_VERSION }}
COMMIT=${{ github.sha }}
BUILD_DATE=${{ steps.prep.outputs.created }}
EDITION=${{ env.RELOADER_EDITION }}
labels: |
org.opencontainers.image.source=${{ github.event.repository.clone_url }}
org.opencontainers.image.created=${{ steps.prep.outputs.created }}

View File

@@ -2,7 +2,7 @@ ARG BUILDER_IMAGE
ARG BASE_IMAGE
# Build the manager binary
FROM --platform=${BUILDPLATFORM} ${BUILDER_IMAGE:-golang:1.26} AS builder
FROM --platform=${BUILDPLATFORM} ${BUILDER_IMAGE:-golang:1.25.5} AS builder
ARG TARGETOS
ARG TARGETARCH
@@ -12,7 +12,6 @@ ARG GOPRIVATE
ARG COMMIT
ARG VERSION
ARG BUILD_DATE
ARG EDITION=oss
WORKDIR /workspace
@@ -37,8 +36,7 @@ RUN CGO_ENABLED=0 \
GO111MODULE=on \
go build -ldflags="-s -w -X github.com/stakater/Reloader/pkg/common.Version=${VERSION} \
-X github.com/stakater/Reloader/pkg/common.Commit=${COMMIT} \
-X github.com/stakater/Reloader/pkg/common.BuildDate=${BUILD_DATE} \
-X github.com/stakater/Reloader/pkg/common.Edition=${EDITION}" \
-X github.com/stakater/Reloader/pkg/common.BuildDate=${BUILD_DATE}" \
-installsuffix 'static' -mod=mod -a -o manager ./
# Use distroless as minimal base image to package the manager binary

View File

@@ -1 +1 @@
1.4.13
1.4.12

View File

@@ -1,8 +1,8 @@
apiVersion: v1
name: reloader
description: Reloader chart that runs on kubernetes
version: 2.2.8
appVersion: v1.4.13
version: 2.2.7
appVersion: v1.4.12
keywords:
- Reloader
- kubernetes

View File

@@ -139,26 +139,21 @@ helm uninstall {{RELEASE_NAME}} -n {{NAMESPACE}}
#### 🔄 `reloadOnCreate` Behavior
**When true:**
✅ New ConfigMaps/Secrets trigger rolling updates for referencing workloads
**When false:**
❌ ConfigMaps/Secrets creations have no effect on referencing workloads
#### 🗑️ `reloadOnDelete` Behavior
**When true:**
✅ Deleted ConfigMaps/Secrets trigger rolling updates for referencing workloads
**When false:**
❌ ConfigMaps/Secrets deletions have no effect on referencing workloads
#### 🔄 `syncAfterRestart` Behavior
**When true:**
✅ New ConfigMaps/Secrets trigger rolling updates
✅ New deployments referencing existing resources reload
✅ In HA mode, new leader reloads all tracked workloads
**When false:**
❌ Updates during leader downtime are missed
⏳ Potential 15s delay window (default `LeaseDuration`)
#### 🗑️ `reloadOnDelete` Behavior
**When true:**
✅ Deleted resources trigger rolling updates of referencing workloads
**When false:**
❌ Deletions have no effect on referencing pods
#### Default Settings
⚠️ All flags default to `false` (must be enabled explicitly):
- `reloadOnCreate`

View File

@@ -19,7 +19,7 @@ fullnameOverride: ""
image:
name: stakater/reloader
repository: ghcr.io/stakater/reloader
tag: v1.4.13
tag: v1.4.12
# digest: sha256:1234567
pullPolicy: IfNotPresent
@@ -133,7 +133,7 @@ reloader:
labels:
provider: stakater
group: com.stakater.platform
version: v1.4.13
version: v1.4.12
# Support for extra environment variables.
env:
# Open supports Key value pair as environment variables.

View File

@@ -17,7 +17,7 @@ spec:
app: reloader-reloader
spec:
containers:
- image: "ghcr.io/stakater/reloader:v1.4.13"
- image: "ghcr.io/stakater/reloader:v1.4.12"
imagePullPolicy: IfNotPresent
name: reloader-reloader
env:

View File

@@ -141,7 +141,7 @@ spec:
fieldPath: metadata.namespace
- name: RELOADER_DEPLOYMENT_NAME
value: reloader-reloader
image: ghcr.io/stakater/reloader:v1.4.13
image: ghcr.io/stakater/reloader:v1.4.12
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 5

View File

@@ -76,7 +76,7 @@ Note: Rolling upgrade also works in the same way for secrets.
### Hash Value Computation
Reloader uses SHA1 to compute hash value. SHA1 is used because it is efficient and less prone to collision.
Reloader uses SHA512 to compute hash value. SHA512 is used because it is efficient and less prone to collision.
## Monitor All Namespaces
@@ -90,4 +90,4 @@ The output file can then be used to deploy Reloader in specific namespace.
## Compatibility With Helm Install and Upgrade
Reloader has no impact on helm deployment cycle. Reloader only injects an environment variable in `deployment`, `daemonset` or `statefulset`. The environment variable contains the SHA1 value of `ConfigMaps` or `Secrets` data. So if a deployment is created using Helm and Reloader updates the deployment, then next time you upgrade the helm release, Reloader will do nothing except changing that environment variable value in `deployment` , `daemonset` or `statefulset`.
Reloader has no impact on helm deployment cycle. Reloader only injects an environment variable in `deployment`, `daemonset` or `statefulset`. The environment variable contains the SHA512 value of `ConfigMaps` or `Secrets` data. So if a deployment is created using Helm and Reloader updates the deployment, then next time you upgrade the helm release, Reloader will do nothing except changing that environment variable value in `deployment` , `daemonset` or `statefulset`.

View File

@@ -2,10 +2,10 @@
Reloader is inspired from [`configmapcontroller`](https://github.com/fabric8io/configmapcontroller) but there are many ways in which it differs from `configmapcontroller`. Below is the small comparison between these two controllers.
| Reloader | ConfigMap |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Reloader can watch both `Secrets` and `ConfigMaps`. | `configmapcontroller` can only watch changes in `ConfigMaps`. It cannot detect changes in other resources like `Secrets`. |
| Reloader can perform rolling upgrades on `deployments` as well as on `statefulsets` and `daemonsets` | `configmapcontroller` can only perform rolling upgrades on `deployments`. It currently does not support rolling upgrades on `statefulsets` and `daemonsets` |
| Reloader provides both unit test cases and end to end integration test cases for future updates. So one can make sure that new changes do not break any old functionality. | Currently there are not any unit test cases or end to end integration test cases in `configmap-controller`. It adds difficulties for any additional updates in `configmap-controller` and one can not know for sure whether new changes breaks any old functionality or not. |
| Reloader uses SHA1 to encode the change in `ConfigMap` or `Secret`. It then saves the SHA1 value in `STAKATER_FOO_CONFIGMAP` or `STAKATER_FOO_SECRET` environment variable depending upon where the change has happened. The use of SHA1 provides a concise 40 characters encoded value that is very less prone to collision. | `configmap-controller` uses `FABRICB_FOO_REVISION` environment variable to store any change in `ConfigMap` controller. It does not encode it or convert it in suitable hash value to avoid data pollution in deployment. |
| Reloader allows you to customize your own annotation (for both `Secrets` and `ConfigMaps`) using command line flags | `configmap-controller` restricts you to only their provided annotation |
| Reloader | ConfigMap |
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Reloader can watch both `Secrets` and `ConfigMaps`. | `configmapcontroller` can only watch changes in `ConfigMaps`. It cannot detect changes in other resources like `Secrets`. |
| Reloader can perform rolling upgrades on `deployments` as well as on `statefulsets` and `daemonsets` | `configmapcontroller` can only perform rolling upgrades on `deployments`. It currently does not support rolling upgrades on `statefulsets` and `daemonsets` |
| Reloader provides both unit test cases and end to end integration test cases for future updates. So one can make sure that new changes do not break any old functionality. | Currently there are not any unit test cases or end to end integration test cases in `configmap-controller`. It adds difficulties for any additional updates in `configmap-controller` and one can not know for sure whether new changes breaks any old functionality or not. |
| Reloader uses SHA512 to encode the change in `ConfigMap` or `Secret`. It then saves the SHA1 value in `STAKATER_FOO_CONFIGMAP` or `STAKATER_FOO_SECRET` environment variable depending upon where the change has happened. The use of SHA1 provides a concise 40 characters encoded value that is very less prone to collision. | `configmap-controller` uses `FABRICB_FOO_REVISION` environment variable to store any change in `ConfigMap` controller. It does not encode it or convert it in suitable hash value to avoid data pollution in deployment. |
| Reloader allows you to customize your own annotation (for both `Secrets` and `ConfigMaps`) using command line flags | `configmap-controller` restricts you to only their provided annotation |

View File

@@ -6,7 +6,7 @@ Reloader and k8s-trigger-controller are both built for same purpose. So there ar
- Both controllers support change detection in `ConfigMaps` and `Secrets`
- Both controllers support deployment `rollout`
- Reloader controller use SHA1 for hashing
- Reloader controller use SHA512 for hashing
- Both controllers have end to end as well as unit test cases.
## Differences

View File

@@ -1,422 +0,0 @@
# Release Automation Script Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Create a bash script (`scripts/release.sh`) and Makefile wrapper that automates the full Reloader release process using `gh` CLI, replacing manual steps with interactive confirmations.
**Architecture:** Single bash script that orchestrates 7 phases sequentially: validate inputs, create release branch, trigger Init Release workflow + poll/merge its PR, create GitHub release (tag), create helm chart bump branch + PR, merge helm chart PR. Each destructive step prompts for `[y/N]` confirmation. A Makefile target `release-full` wraps the script.
**Tech Stack:** Bash, `gh` CLI, `sed`, `yq` (already in Makefile), `git`
---
### Task 1: Create the release script with input validation and helpers
**Files:**
- Create: `scripts/release.sh`
**Step 1: Create `scripts/release.sh` with argument parsing, validation, and helper functions**
```bash
#!/usr/bin/env bash
set -euo pipefail
REPO="stakater/Reloader"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
confirm() {
local msg="$1"
echo -en "${YELLOW}$msg [y/N]:${NC} "
read -r answer
[[ "$answer" =~ ^[Yy]$ ]]
}
usage() {
cat <<EOF
Usage: $0 <APP_VERSION> <CHART_VERSION>
Automates the full Reloader release process.
Arguments:
APP_VERSION Application version without 'v' prefix (e.g. 1.5.0)
CHART_VERSION Helm chart version (e.g. 2.3.0)
Prerequisites:
- gh CLI authenticated with repo access
- git configured with push access to $REPO
Example:
$0 1.5.0 2.3.0
EOF
exit 1
}
# --- Input validation ---
[[ $# -ne 2 ]] && usage
APP_VERSION="$1"
CHART_VERSION="$2"
# Strip 'v' prefix if provided
APP_VERSION="${APP_VERSION#v}"
CHART_VERSION="${CHART_VERSION#v}"
# Validate semver format (simple check)
if ! [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
error "APP_VERSION '$APP_VERSION' is not valid semver (expected X.Y.Z)"
exit 1
fi
if ! [[ "$CHART_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
error "CHART_VERSION '$CHART_VERSION' is not valid semver (expected X.Y.Z)"
exit 1
fi
# Check prerequisites
if ! command -v gh &> /dev/null; then
error "gh CLI is not installed. Install from https://cli.github.com/"
exit 1
fi
if ! gh auth status &> /dev/null; then
error "gh CLI is not authenticated. Run 'gh auth login' first."
exit 1
fi
RELEASE_BRANCH="release-v${APP_VERSION}"
TAG="v${APP_VERSION}"
info "Release plan:"
info " App version: $APP_VERSION (tag: $TAG)"
info " Chart version: $CHART_VERSION"
info " Release branch: $RELEASE_BRANCH"
echo ""
```
**Step 2: Make it executable and verify it runs**
Run: `chmod +x scripts/release.sh && ./scripts/release.sh`
Expected: Usage message printed, exit 1
Run: `./scripts/release.sh 1.5.0 2.3.0`
Expected: Prints release plan, then continues (will fail at next phase since we haven't written it yet)
**Step 3: Commit**
```bash
git add scripts/release.sh
git commit -m "feat: add release script with input validation and helpers"
```
---
### Task 2: Add Phase 1 — Create release branch
**Files:**
- Modify: `scripts/release.sh` (append after validation block)
**Step 1: Append Phase 1 to the script**
```bash
# =============================================================================
# Phase 1: Create release branch
# =============================================================================
info "Phase 1: Create release branch '$RELEASE_BRANCH' from master"
if git ls-remote --heads origin "$RELEASE_BRANCH" | grep -q "$RELEASE_BRANCH"; then
warn "Branch '$RELEASE_BRANCH' already exists on remote."
if ! confirm "Continue using existing branch?"; then
error "Aborted."
exit 1
fi
else
if ! confirm "Create and push branch '$RELEASE_BRANCH' from master?"; then
error "Aborted."
exit 1
fi
git fetch origin master
git push origin origin/master:refs/heads/"$RELEASE_BRANCH"
info "Branch '$RELEASE_BRANCH' created and pushed."
fi
echo ""
```
**Step 2: Commit**
```bash
git add scripts/release.sh
git commit -m "feat(release): add phase 1 - create release branch"
```
---
### Task 3: Add Phase 2 — Trigger Init Release workflow and poll/merge its PR
**Files:**
- Modify: `scripts/release.sh` (append)
**Step 1: Append Phase 2**
The Init Release workflow creates a PR from a branch prefixed with `update-version` targeting the release branch. We trigger it, poll for the PR, then merge it.
```bash
# =============================================================================
# Phase 2: Trigger Init Release workflow and merge its PR
# =============================================================================
info "Phase 2: Trigger Init Release workflow"
if ! confirm "Trigger 'Init Release' workflow for branch '$RELEASE_BRANCH' with version '$APP_VERSION'?"; then
error "Aborted."
exit 1
fi
gh workflow run init-branch-release.yaml \
--repo "$REPO" \
-f TARGET_BRANCH="$RELEASE_BRANCH" \
-f TARGET_VERSION="$APP_VERSION"
info "Workflow triggered. Waiting for version bump PR to be created..."
# Poll for the PR (created by the workflow targeting the release branch)
MAX_ATTEMPTS=30
SLEEP_INTERVAL=10
PR_NUMBER=""
for i in $(seq 1 $MAX_ATTEMPTS); do
PR_NUMBER=$(gh pr list \
--repo "$REPO" \
--base "$RELEASE_BRANCH" \
--search "Bump version to $APP_VERSION" \
--json number \
--jq '.[0].number // empty' 2>/dev/null || true)
if [[ -n "$PR_NUMBER" ]]; then
info "Found PR #$PR_NUMBER"
break
fi
echo -n "."
sleep "$SLEEP_INTERVAL"
done
if [[ -z "$PR_NUMBER" ]]; then
error "Timed out waiting for Init Release PR. Check workflow status at:"
error " https://github.com/$REPO/actions/workflows/init-branch-release.yaml"
exit 1
fi
info "PR: https://github.com/$REPO/pull/$PR_NUMBER"
if ! confirm "Merge PR #$PR_NUMBER (version bump to $APP_VERSION)?"; then
error "Aborted. PR is still open: https://github.com/$REPO/pull/$PR_NUMBER"
exit 1
fi
gh pr merge "$PR_NUMBER" --repo "$REPO" --merge
info "PR #$PR_NUMBER merged."
echo ""
```
**Step 2: Commit**
```bash
git add scripts/release.sh
git commit -m "feat(release): add phase 2 - trigger init release and merge PR"
```
---
### Task 4: Add Phase 3 — Create GitHub release
**Files:**
- Modify: `scripts/release.sh` (append)
**Step 1: Append Phase 3**
```bash
# =============================================================================
# Phase 3: Create GitHub release
# =============================================================================
info "Phase 3: Create GitHub release '$TAG' targeting '$RELEASE_BRANCH'"
info "This will trigger the release workflow (Docker image builds, GoReleaser)."
if ! confirm "Create GitHub release '$TAG'?"; then
error "Aborted."
exit 1
fi
gh release create "$TAG" \
--repo "$REPO" \
--target "$RELEASE_BRANCH" \
--title "Release $TAG" \
--generate-notes
info "GitHub release created: https://github.com/$REPO/releases/tag/$TAG"
info "Release workflow will run in the background."
echo ""
```
**Step 2: Commit**
```bash
git add scripts/release.sh
git commit -m "feat(release): add phase 3 - create GitHub release"
```
---
### Task 5: Add Phase 4 — Create helm chart bump PR and merge it
**Files:**
- Modify: `scripts/release.sh` (append)
**Step 1: Append Phase 4**
This phase creates a branch from master, bumps chart version + appVersion + image tag, pushes, creates a PR with the `release/helm-chart` label, and merges it.
```bash
# =============================================================================
# Phase 4: Bump Helm chart and create PR
# =============================================================================
info "Phase 4: Bump Helm chart version to $CHART_VERSION (appVersion: v$APP_VERSION)"
HELM_BRANCH="release-helm-chart-v${CHART_VERSION}"
if ! confirm "Create branch '$HELM_BRANCH', bump chart files, and open PR with 'release/helm-chart' label?"; then
error "Aborted."
exit 1
fi
# Create branch from latest master
git fetch origin master
git checkout -b "$HELM_BRANCH" origin/master
# Bump Chart.yaml: version and appVersion
CHART_FILE="deployments/kubernetes/chart/reloader/Chart.yaml"
sed -i "s/^version:.*/version: ${CHART_VERSION}/" "$CHART_FILE"
sed -i "s/^appVersion:.*/appVersion: v${APP_VERSION}/" "$CHART_FILE"
# Bump values.yaml: image.tag
VALUES_FILE="deployments/kubernetes/chart/reloader/values.yaml"
sed -i "s/^\( tag:\).*/\1 v${APP_VERSION}/" "$VALUES_FILE"
# Show changes for review
info "Changes:"
git diff
git add "$CHART_FILE" "$VALUES_FILE"
git commit -m "Bump helm chart to ${CHART_VERSION} and appVersion to v${APP_VERSION}"
git push origin "$HELM_BRANCH"
HELM_PR_URL=$(gh pr create \
--repo "$REPO" \
--base master \
--head "$HELM_BRANCH" \
--title "Bump Helm chart to ${CHART_VERSION} (appVersion v${APP_VERSION})" \
--body "Bump Helm chart version to ${CHART_VERSION} and appVersion to v${APP_VERSION}." \
--label "release/helm-chart")
HELM_PR_NUMBER=$(echo "$HELM_PR_URL" | grep -o '[0-9]*$')
info "Helm chart PR created: $HELM_PR_URL"
if ! confirm "Merge Helm chart PR #$HELM_PR_NUMBER?"; then
error "Aborted. PR is still open: $HELM_PR_URL"
exit 1
fi
gh pr merge "$HELM_PR_NUMBER" --repo "$REPO" --merge
info "Helm chart PR #$HELM_PR_NUMBER merged."
# Return to previous branch
git checkout -
echo ""
info "============================================="
info "Release $TAG complete!"
info "============================================="
info ""
info "Summary:"
info " - Release branch: $RELEASE_BRANCH"
info " - GitHub release: https://github.com/$REPO/releases/tag/$TAG"
info " - Helm chart: $CHART_VERSION (appVersion: v$APP_VERSION)"
info ""
info "The release workflow is running in the background."
info "Monitor at: https://github.com/$REPO/actions"
```
**Step 2: Commit**
```bash
git add scripts/release.sh
git commit -m "feat(release): add phase 4 - helm chart bump PR and merge"
```
---
### Task 6: Add Makefile wrapper
**Files:**
- Modify: `Makefile:157` (append after `update-manifests-version` target, before the yq section)
**Step 1: Add `release-full` target to Makefile**
Add this block after the `update-manifests-version` target (around line 159):
```makefile
.PHONY: release-full
release-full: ## Run the full release process (interactive)
ifndef APP_VERSION
$(error APP_VERSION is required. Usage: make release-full APP_VERSION=X.Y.Z CHART_VERSION=A.B.C)
endif
ifndef CHART_VERSION
$(error CHART_VERSION is required. Usage: make release-full APP_VERSION=X.Y.Z CHART_VERSION=A.B.C)
endif
./scripts/release.sh $(APP_VERSION) $(CHART_VERSION)
```
**Step 2: Verify Makefile target works**
Run: `make release-full`
Expected: Error about APP_VERSION being required
Run: `make release-full APP_VERSION=1.5.0 CHART_VERSION=2.3.0`
Expected: Prints release plan, begins interactive flow
**Step 3: Commit**
```bash
git add Makefile
git commit -m "feat: add release-full Makefile target"
```
---
### Task 7: Final verification and combined commit
**Step 1: Verify complete script syntax**
Run: `bash -n scripts/release.sh`
Expected: No syntax errors
**Step 2: Verify help output**
Run: `./scripts/release.sh`
Expected: Usage message with examples
**Step 3: Verify invalid input handling**
Run: `./scripts/release.sh bad-version also-bad`
Expected: Error about invalid semver
Run: `./scripts/release.sh v1.5.0 2.3.0`
Expected: Strips the 'v' prefix, shows plan with `1.5.0`

4
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/stakater/Reloader
go 1.26
go 1.25.5
require (
github.com/argoproj/argo-rollouts v1.8.3
@@ -15,6 +15,7 @@ require (
k8s.io/apimachinery v0.35.0
k8s.io/client-go v0.35.0
k8s.io/kubectl v0.35.0
k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2
sigs.k8s.io/secrets-store-csi-driver v1.5.5
)
@@ -64,7 +65,6 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect

View File

@@ -1,20 +1,12 @@
package crypto
import (
"crypto/sha1"
"fmt"
"io"
"github.com/sirupsen/logrus"
"crypto/sha512"
"encoding/hex"
)
// GenerateSHA generates SHA from string
func GenerateSHA(data string) string {
hasher := sha1.New()
_, err := io.WriteString(hasher, data)
if err != nil {
logrus.Errorf("Unable to write data in hash writer %v", err)
}
sha := hasher.Sum(nil)
return fmt.Sprintf("%x", sha)
hash := sha512.Sum512_256([]byte(data))
return hex.EncodeToString(hash[:])
}

View File

@@ -7,7 +7,7 @@ import (
// TestGenerateSHA generates the sha from given data and verifies whether it is correct or not
func TestGenerateSHA(t *testing.T) {
data := "www.stakater.com"
sha := "abd4ed82fb04548388a6cf3c339fd9dc84d275df"
sha := "2e9aa975331b22861b4f62b7fcc69b63e001f938361fee3b4ed888adf26a10e3"
result := GenerateSHA(data)
if result != sha {
t.Errorf("Failed to generate SHA")
@@ -18,11 +18,11 @@ func TestGenerateSHA(t *testing.T) {
// This ensures consistent behavior and avoids issues with string matching operations
func TestGenerateSHAEmptyString(t *testing.T) {
result := GenerateSHA("")
expected := "da39a3ee5e6b4b0d3255bfef95601890afd80709"
expected := "c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a"
if result != expected {
t.Errorf("Failed to generate SHA for empty string. Expected: %s, Got: %s", expected, result)
}
if len(result) != 40 {
if len(result) != 64 {
t.Errorf("SHA hash should be 40 characters long, got %d", len(result))
}
}

View File

@@ -1981,7 +1981,7 @@ func TestRollingUpgradeForDeploymentWithPatchAndRetryUsingArs(t *testing.T) {
assert.Equal(t, patchtypes.StrategicMergePatchType, patchType)
assert.NotEmpty(t, bytes)
assert.Contains(t, string(bytes), `{"spec":{"template":{"metadata":{"annotations":{"reloader.stakater.com/last-reloaded-from":`)
assert.Contains(t, string(bytes), `\"hash\":\"3c9a892aeaedc759abc3df9884a37b8be5680382\"`)
assert.Contains(t, string(bytes), `\"hash\":\"fd9e71a362056bfa864d9859e12978f893d330ce8cbf09218b25d015770ad91f\"`)
return nil
}
@@ -2964,7 +2964,7 @@ func TestRollingUpgradeForDaemonSetWithPatchAndRetryUsingArs(t *testing.T) {
assert.Equal(t, patchtypes.StrategicMergePatchType, patchType)
assert.NotEmpty(t, bytes)
assert.Contains(t, string(bytes), `{"spec":{"template":{"metadata":{"annotations":{"reloader.stakater.com/last-reloaded-from":`)
assert.Contains(t, string(bytes), `\"hash\":\"314a2269170750a974d79f02b5b9ee517de7f280\"`)
assert.Contains(t, string(bytes), `\"hash\":\"43bf9e30e7c4e32a8f8673c462b86d0b1ac626cf498afdc0d0108e79ebe7ee0c\"`)
return nil
}
@@ -3227,7 +3227,7 @@ func TestRollingUpgradeForStatefulSetWithPatchAndRetryUsingArs(t *testing.T) {
assert.Equal(t, patchtypes.StrategicMergePatchType, patchType)
assert.NotEmpty(t, bytes)
assert.Contains(t, string(bytes), `{"spec":{"template":{"metadata":{"annotations":{"reloader.stakater.com/last-reloaded-from":`)
assert.Contains(t, string(bytes), `\"hash\":\"f821414d40d8815fb330763f74a4ff7ab651d4fa\"`)
assert.Contains(t, string(bytes), `\"hash\":\"6aa837180bdf6a93306c71a0cf62b4a45c2d5b021578247b3b64d5baea2b84d9\"`)
return nil
}
@@ -3607,7 +3607,7 @@ func TestRollingUpgradeForDeploymentWithPatchAndRetryUsingErs(t *testing.T) {
assert.Equal(t, patchtypes.StrategicMergePatchType, patchType)
assert.NotEmpty(t, bytes)
assert.Contains(t, string(bytes), `{"spec":{"template":{"spec":{"containers":[{"name":`)
assert.Contains(t, string(bytes), `"value":"3c9a892aeaedc759abc3df9884a37b8be5680382"`)
assert.Contains(t, string(bytes), `"value":"fd9e71a362056bfa864d9859e12978f893d330ce8cbf09218b25d015770ad91f"`)
return nil
}
@@ -4502,7 +4502,7 @@ func TestRollingUpgradeForDaemonSetWithPatchAndRetryUsingErs(t *testing.T) {
assert.Equal(t, patchtypes.StrategicMergePatchType, patchType)
assert.NotEmpty(t, bytes)
assert.Contains(t, string(bytes), `{"spec":{"template":{"spec":{"containers":[{"name":`)
assert.Contains(t, string(bytes), `"value":"314a2269170750a974d79f02b5b9ee517de7f280"`)
assert.Contains(t, string(bytes), `"value":"43bf9e30e7c4e32a8f8673c462b86d0b1ac626cf498afdc0d0108e79ebe7ee0c"`)
return nil
}
@@ -4737,7 +4737,7 @@ func TestRollingUpgradeForStatefulSetWithPatchAndRetryUsingErs(t *testing.T) {
assert.Equal(t, patchtypes.StrategicMergePatchType, patchType)
assert.NotEmpty(t, bytes)
assert.Contains(t, string(bytes), `{"spec":{"template":{"spec":{"containers":[{"name":`)
assert.Contains(t, string(bytes), `"value":"f821414d40d8815fb330763f74a4ff7ab651d4fa"`)
assert.Contains(t, string(bytes), `"value":"6aa837180bdf6a93306c71a0cf62b4a45c2d5b021578247b3b64d5baea2b84d9"`)
return nil
}

View File

@@ -45,7 +45,7 @@ func TestHealthz(t *testing.T) {
want := 200
if got != want {
t.Fatalf("got: %d, want: %d", got, want)
t.Fatalf("got: %q, want: %q", got, want)
}
// Have the liveness probe serve a 500
@@ -63,7 +63,7 @@ func TestHealthz(t *testing.T) {
want = 500
if got != want {
t.Fatalf("got: %d, want: %d", got, want)
t.Fatalf("got: %q, want: %q", got, want)
}
}
@@ -89,7 +89,7 @@ func TestRunLeaderElection(t *testing.T) {
want := 500
if got != want {
t.Fatalf("got: %d, want: %d", got, want)
t.Fatalf("got: %q, want: %q", got, want)
}
// Cancel the leader election context, so leadership is released and
@@ -108,7 +108,7 @@ func TestRunLeaderElection(t *testing.T) {
want = 500
if got != want {
t.Fatalf("got: %d, want: %d", got, want)
t.Fatalf("got: %q, want: %q", got, want)
}
}

View File

@@ -18,12 +18,11 @@ import (
var Version = "dev"
var Commit = "unknown"
var BuildDate = "unknown"
var Edition = "oss"
const (
MetaInfoConfigmapName = "reloader-meta-info"
MetaInfoConfigmapLabelKey = "reloader.stakater.com/meta-info"
MetaInfoConfigmapLabelValue = "reloader"
MetaInfoConfigmapLabelValue = "reloader-oss"
)
// MetaInfo contains comprehensive metadata about the Reloader instance.
@@ -48,9 +47,6 @@ type BuildInfo struct {
CommitHash string `json:"commitHash"`
// CommitTime is the timestamp of the Git commit used to build this binary
CommitTime time.Time `json:"commitTime"`
// Edition indicates the edition of Reloader (e.g., OSS, Enterprise)
Edition string `json:"edition"`
}
func NewBuildInfo() *BuildInfo {
@@ -59,7 +55,6 @@ func NewBuildInfo() *BuildInfo {
ReleaseVersion: Version,
CommitHash: Commit,
CommitTime: ParseUTCTime(BuildDate),
Edition: Edition,
}
return metaInfo

View File

@@ -1,250 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
REPO="stakater/Reloader"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
confirm() {
local msg="$1"
echo -en "${YELLOW}$msg [y/N]:${NC} "
read -r answer
[[ "$answer" =~ ^[Yy]$ ]]
}
usage() {
cat <<EOF
Usage: $0 <APP_VERSION> <CHART_VERSION>
Automates the full Reloader release process.
Arguments:
APP_VERSION Application version without 'v' prefix (e.g. 1.5.0)
CHART_VERSION Helm chart version (e.g. 2.3.0)
Prerequisites:
- gh CLI authenticated with repo access
- git configured with push access to $REPO
Example:
$0 1.5.0 2.3.0
EOF
exit 1
}
# --- Input validation ---
[[ $# -ne 2 ]] && usage
APP_VERSION="$1"
CHART_VERSION="$2"
# Strip 'v' prefix if provided
APP_VERSION="${APP_VERSION#v}"
CHART_VERSION="${CHART_VERSION#v}"
# Validate semver format
if ! [[ "$APP_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
error "APP_VERSION '$APP_VERSION' is not valid semver (expected X.Y.Z)"
exit 1
fi
if ! [[ "$CHART_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
error "CHART_VERSION '$CHART_VERSION' is not valid semver (expected X.Y.Z)"
exit 1
fi
# Check prerequisites
if ! command -v gh &> /dev/null; then
error "gh CLI is not installed. Install from https://cli.github.com/"
exit 1
fi
if ! gh auth status &> /dev/null; then
error "gh CLI is not authenticated. Run 'gh auth login' first."
exit 1
fi
RELEASE_BRANCH="release-v${APP_VERSION}"
TAG="v${APP_VERSION}"
info "Release plan:"
info " App version: $APP_VERSION (tag: $TAG)"
info " Chart version: $CHART_VERSION"
info " Release branch: $RELEASE_BRANCH"
echo ""
# =============================================================================
# Phase 1: Create release branch
# =============================================================================
info "Phase 1: Create release branch '$RELEASE_BRANCH' from master"
if git ls-remote --heads origin "$RELEASE_BRANCH" | grep -q "$RELEASE_BRANCH"; then
warn "Branch '$RELEASE_BRANCH' already exists on remote."
if ! confirm "Continue using existing branch?"; then
error "Aborted."
exit 1
fi
else
if ! confirm "Create and push branch '$RELEASE_BRANCH' from master?"; then
error "Aborted."
exit 1
fi
git fetch origin master
git push origin origin/master:refs/heads/"$RELEASE_BRANCH"
info "Branch '$RELEASE_BRANCH' created and pushed."
fi
echo ""
# =============================================================================
# Phase 2: Trigger Init Release workflow and merge its PR
# =============================================================================
info "Phase 2: Trigger Init Release workflow"
if ! confirm "Trigger 'Init Release' workflow for branch '$RELEASE_BRANCH' with version '$APP_VERSION'?"; then
error "Aborted."
exit 1
fi
gh workflow run init-branch-release.yaml \
--repo "$REPO" \
-f TARGET_BRANCH="$RELEASE_BRANCH" \
-f TARGET_VERSION="$APP_VERSION"
info "Workflow triggered. Waiting for version bump PR to be created..."
# Poll for the PR (created by the workflow targeting the release branch)
MAX_ATTEMPTS=30
SLEEP_INTERVAL=10
PR_NUMBER=""
for i in $(seq 1 $MAX_ATTEMPTS); do
PR_NUMBER=$(gh pr list \
--repo "$REPO" \
--base "$RELEASE_BRANCH" \
--search "Bump version to $APP_VERSION" \
--json number \
--jq '.[0].number // empty' 2>/dev/null || true)
if [[ -n "$PR_NUMBER" ]]; then
info "Found PR #$PR_NUMBER"
break
fi
echo -n "."
sleep "$SLEEP_INTERVAL"
done
if [[ -z "$PR_NUMBER" ]]; then
error "Timed out waiting for Init Release PR. Check workflow status at:"
error " https://github.com/$REPO/actions/workflows/init-branch-release.yaml"
exit 1
fi
info "PR: https://github.com/$REPO/pull/$PR_NUMBER"
if ! confirm "Merge PR #$PR_NUMBER (version bump to $APP_VERSION)?"; then
error "Aborted. PR is still open: https://github.com/$REPO/pull/$PR_NUMBER"
exit 1
fi
gh pr merge "$PR_NUMBER" --repo "$REPO" --merge
info "PR #$PR_NUMBER merged."
echo ""
# =============================================================================
# Phase 3: Create GitHub release
# =============================================================================
info "Phase 3: Create GitHub release '$TAG' targeting '$RELEASE_BRANCH'"
info "This will trigger the release workflow (Docker image builds, GoReleaser)."
if ! confirm "Create GitHub release '$TAG'?"; then
error "Aborted."
exit 1
fi
gh release create "$TAG" \
--repo "$REPO" \
--target "$RELEASE_BRANCH" \
--title "Release $TAG" \
--generate-notes
info "GitHub release created: https://github.com/$REPO/releases/tag/$TAG"
info "Release workflow will run in the background."
echo ""
# =============================================================================
# Phase 4: Bump Helm chart and create PR
# =============================================================================
info "Phase 4: Bump Helm chart version to $CHART_VERSION (appVersion: v$APP_VERSION)"
HELM_BRANCH="release-helm-chart-v${CHART_VERSION}"
if ! confirm "Create branch '$HELM_BRANCH', bump chart files, and open PR with 'release/helm-chart' label?"; then
error "Aborted."
exit 1
fi
# Create branch from latest master
git fetch origin master
git checkout -b "$HELM_BRANCH" origin/master
# Bump Chart.yaml: version and appVersion
CHART_FILE="deployments/kubernetes/chart/reloader/Chart.yaml"
sed -i "s/^version:.*/version: ${CHART_VERSION}/" "$CHART_FILE"
sed -i "s/^appVersion:.*/appVersion: v${APP_VERSION}/" "$CHART_FILE"
# Bump values.yaml: image.tag
VALUES_FILE="deployments/kubernetes/chart/reloader/values.yaml"
sed -i "s/^\( tag:\).*/\1 v${APP_VERSION}/" "$VALUES_FILE"
# Show changes for review
info "Changes:"
git diff
git add "$CHART_FILE" "$VALUES_FILE"
git commit -m "Bump helm chart to ${CHART_VERSION} and appVersion to v${APP_VERSION}"
git push origin "$HELM_BRANCH"
HELM_PR_URL=$(gh pr create \
--repo "$REPO" \
--base master \
--head "$HELM_BRANCH" \
--title "Bump Helm chart to ${CHART_VERSION} (appVersion v${APP_VERSION})" \
--body "Bump Helm chart version to ${CHART_VERSION} and appVersion to v${APP_VERSION}." \
--label "release/helm-chart")
HELM_PR_NUMBER=$(echo "$HELM_PR_URL" | grep -o '[0-9]*$')
info "Helm chart PR created: $HELM_PR_URL"
if ! confirm "Merge Helm chart PR #$HELM_PR_NUMBER?"; then
error "Aborted. PR is still open: $HELM_PR_URL"
exit 1
fi
gh pr merge "$HELM_PR_NUMBER" --repo "$REPO" --merge
info "Helm chart PR #$HELM_PR_NUMBER merged."
# Return to previous branch
git checkout -
echo ""
info "============================================="
info "Release $TAG complete!"
info "============================================="
info ""
info "Summary:"
info " - Release branch: $RELEASE_BRANCH"
info " - GitHub release: https://github.com/$REPO/releases/tag/$TAG"
info " - Helm chart: $CHART_VERSION (appVersion: v$APP_VERSION)"
info ""
info "The release workflow is running in the background."
info "Monitor at: https://github.com/$REPO/actions"

View File

@@ -1,6 +1,6 @@
module github.com/stakater/Reloader/test/loadtest
go 1.26
go 1.25
require (
github.com/spf13/cobra v1.8.1