mirror of
https://github.com/Noooste/garage-ui.git
synced 2026-07-27 16:18:56 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34ba8ef851 | |||
| 1ffdfe83c8 | |||
| e0cd59dcdf | |||
| 1c1e4d4b2b | |||
| ae245c8a31 | |||
| 28c186f3eb | |||
| 46aa3752c8 |
@@ -3,7 +3,8 @@ name: chart-release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'garage-ui-chart-v*'
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
||||
@@ -25,87 +25,3 @@ jobs:
|
||||
config-file: release-please-config.json
|
||||
manifest-file: .release-please-manifest.json
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Sync Chart.yaml appVersion on release PR
|
||||
if: ${{ steps.rp.outputs.prs_created == 'true' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
PRS: ${{ steps.rp.outputs.prs }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "$PRS" | jq -c '.[]' | while read -r pr; do
|
||||
branch=$(echo "$pr" | jq -r '.headBranchName')
|
||||
workdir=$(mktemp -d)
|
||||
git clone --branch "$branch" --depth 1 \
|
||||
"https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" "$workdir"
|
||||
cd "$workdir"
|
||||
git config user.email "garage-ui-release-bot[bot]@users.noreply.github.com"
|
||||
git config user.name "garage-ui-release-bot[bot]"
|
||||
|
||||
version=$(jq -r '."."' .release-please-manifest.json)
|
||||
target="v${version}"
|
||||
sed -i -E "s|^appVersion:.*|appVersion: ${target}|" helm/garage-ui/Chart.yaml
|
||||
|
||||
if ! git diff --quiet helm/garage-ui/Chart.yaml; then
|
||||
git add helm/garage-ui/Chart.yaml
|
||||
git commit -m "chore: sync Chart.yaml appVersion to ${target}"
|
||||
git push origin "$branch"
|
||||
fi
|
||||
cd -
|
||||
rm -rf "$workdir"
|
||||
done
|
||||
|
||||
- name: Cut chart patch when backend released without chart
|
||||
if: ${{ steps.rp.outputs.releases_created == 'true' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
REPO: ${{ github.repository }}
|
||||
BACKEND_RELEASED: ${{ steps.rp.outputs.release_created }}
|
||||
CHART_RELEASED: ${{ steps.rp.outputs['helm/garage-ui--release_created'] }}
|
||||
BACKEND_TAG: ${{ steps.rp.outputs.tag_name }}
|
||||
BACKEND_VERSION: ${{ steps.rp.outputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ "$BACKEND_RELEASED" != "true" ] || [ "$CHART_RELEASED" = "true" ]; then
|
||||
echo "Backend not released, or chart already released this cycle. Nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
workdir=$(mktemp -d)
|
||||
git clone \
|
||||
"https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" "$workdir"
|
||||
cd "$workdir"
|
||||
git config user.email "garage-ui-release-bot[bot]@users.noreply.github.com"
|
||||
git config user.name "garage-ui-release-bot[bot]"
|
||||
|
||||
chart_old=$(jq -r '."helm/garage-ui"' .release-please-manifest.json)
|
||||
chart_new=$(echo "$chart_old" | awk -F. '{printf "%d.%d.%d", $1, $2, $3+1}')
|
||||
chart_tag="garage-ui-chart-v${chart_new}"
|
||||
|
||||
jq --arg v "$chart_new" '."helm/garage-ui" = $v' \
|
||||
.release-please-manifest.json > .release-please-manifest.json.tmp
|
||||
mv .release-please-manifest.json.tmp .release-please-manifest.json
|
||||
|
||||
sed -i -E "s|^version:.*|version: ${chart_new}|" helm/garage-ui/Chart.yaml
|
||||
sed -i -E "s|^appVersion:.*|appVersion: ${BACKEND_TAG}|" helm/garage-ui/Chart.yaml
|
||||
|
||||
today=$(date -u +%Y-%m-%d)
|
||||
compare_url="https://github.com/${REPO}/compare/garage-ui-chart-v${chart_old}...${chart_tag}"
|
||||
entry=$(printf '## [%s](%s) (%s)\n\n\n### Miscellaneous Chores\n\n* sync chart appVersion to %s\n' \
|
||||
"$chart_new" "$compare_url" "$today" "$BACKEND_TAG")
|
||||
awk -v entry="$entry" 'NR==1 && /^# Changelog/ { print; print ""; print entry; next } { print }' \
|
||||
helm/garage-ui/CHANGELOG.md > helm/garage-ui/CHANGELOG.md.tmp
|
||||
mv helm/garage-ui/CHANGELOG.md.tmp helm/garage-ui/CHANGELOG.md
|
||||
|
||||
git add .release-please-manifest.json helm/garage-ui/Chart.yaml helm/garage-ui/CHANGELOG.md
|
||||
git commit -m "chore: bump chart to ${chart_new} for backend ${BACKEND_TAG}"
|
||||
git push origin main
|
||||
|
||||
git tag "$chart_tag"
|
||||
git push origin "$chart_tag"
|
||||
|
||||
gh release create "$chart_tag" \
|
||||
--repo "$REPO" \
|
||||
--title "$chart_tag" \
|
||||
--notes "Chart patch synced to backend ${BACKEND_TAG}. No chart template changes."
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
{
|
||||
".": "0.8.2",
|
||||
"helm/garage-ui": "0.6.1"
|
||||
".": "0.8.4"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# Changelog
|
||||
|
||||
## [0.8.4](https://github.com/Noooste/garage-ui/compare/v0.8.3...v0.8.4) (2026-06-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **helm:** track appVersion in release-please and fix badges ([1ffdfe8](https://github.com/Noooste/garage-ui/commit/1ffdfe83c884dc5e39d071343d5269164746c536))
|
||||
|
||||
## [0.8.3](https://github.com/Noooste/garage-ui/compare/v0.8.2...v0.8.3) (2026-06-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** improve API version detection with retry logic for health probes ([46aa375](https://github.com/Noooste/garage-ui/commit/46aa3752c81788787388d1c67e29cef786bdabff))
|
||||
* **helm:** update version badges in README for Garage UI ([28c186f](https://github.com/Noooste/garage-ui/commit/28c186f3eba1a1c111100712f1eaec22a5d18eb2))
|
||||
|
||||
## [0.8.2](https://github.com/Noooste/garage-ui/compare/v0.8.1...v0.8.2) (2026-06-07)
|
||||
|
||||
|
||||
|
||||
@@ -39,8 +39,14 @@ type AdminServiceResult struct {
|
||||
APIVersion string
|
||||
}
|
||||
|
||||
// errProbeNotFound means the probed route returned 404. Garage v2.x also serves
|
||||
// /v1/health, so a 404 on /v2 is the only reliable "this is a v1 server" signal.
|
||||
var errProbeNotFound = errors.New("probe endpoint not found")
|
||||
|
||||
func NewAdminService(cfg *config.GarageConfig, logLevel string) (*AdminServiceResult, error) {
|
||||
if err := probeEndpoint(cfg, "/v2/GetClusterHealth"); err == nil {
|
||||
// retry so a startup fails doesn't lock us to a v1 client.
|
||||
err := probeEndpointWithRetry(cfg, "/v2/GetClusterHealth")
|
||||
if err == nil {
|
||||
logger.Info().Str("api_version", "v2").Msg("Detected Garage admin API v2")
|
||||
svc := NewGarageV2AdminService(cfg, logLevel)
|
||||
return &AdminServiceResult{
|
||||
@@ -50,7 +56,17 @@ func NewAdminService(cfg *config.GarageConfig, logLevel string) (*AdminServiceRe
|
||||
}, nil
|
||||
}
|
||||
|
||||
if err := probeEndpoint(cfg, "/v1/health"); err == nil {
|
||||
// only fall back to v1 on a real 404
|
||||
// other errors mean the server is up but the probe failed transiently; picking v1 against
|
||||
// a v2.x server breaks /v1/status with "v1/ endpoint is no longer supported" (issue #78).
|
||||
if !errors.Is(err, errProbeNotFound) {
|
||||
return nil, fmt.Errorf(
|
||||
"could not detect Garage admin API version at %s: %w. Ensure Garage v1.1+ is running and the admin API is reachable",
|
||||
cfg.AdminEndpoint, err,
|
||||
)
|
||||
}
|
||||
|
||||
if err := probeEndpointWithRetry(cfg, "/v1/health"); err == nil {
|
||||
logger.Info().
|
||||
Str("api_version", "v1").
|
||||
Msg("Detected Garage admin API v1 — cluster statistics and per-node details will be unavailable")
|
||||
@@ -68,6 +84,26 @@ func NewAdminService(cfg *config.GarageConfig, logLevel string) (*AdminServiceRe
|
||||
)
|
||||
}
|
||||
|
||||
const probeAttempts = 4
|
||||
|
||||
// probeEndpointWithRetry retries transient failures with backoff, but returns a
|
||||
// 404 immediately, a missing route won't appear on a retry.
|
||||
func probeEndpointWithRetry(cfg *config.GarageConfig, path string) error {
|
||||
var err error
|
||||
backoff := 250 * time.Millisecond
|
||||
for attempt := range probeAttempts {
|
||||
err = probeEndpoint(cfg, path)
|
||||
if err == nil || errors.Is(err, errProbeNotFound) {
|
||||
return err
|
||||
}
|
||||
if attempt < probeAttempts-1 {
|
||||
time.Sleep(backoff)
|
||||
backoff *= 2
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func probeEndpoint(cfg *config.GarageConfig, path string) error {
|
||||
session := azuretls.NewSession()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
@@ -86,6 +122,9 @@ func probeEndpoint(cfg *config.GarageConfig, path string) error {
|
||||
}
|
||||
defer resp.RawBody.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return fmt.Errorf("probe %s returned status 404: %w", path, errProbeNotFound)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("probe %s returned status %d", path, resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -89,3 +89,61 @@ func TestDetectVersion_Unreachable(t *testing.T) {
|
||||
t.Fatal("expected error for unreachable server")
|
||||
}
|
||||
}
|
||||
|
||||
// Garage v2.x serves /v1/health too, so a transient failure of the /v2 probe
|
||||
// must not cause a permanent downgrade to the (broken on v2.x) v1 client.
|
||||
// Regression test for https://github.com/Noooste/garage-ui/issues/78
|
||||
func TestDetectVersion_V2_TransientProbeFailure(t *testing.T) {
|
||||
var v2Hits int
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/v2/GetClusterHealth":
|
||||
v2Hits++
|
||||
if v2Hits < 3 { // fail the first two attempts, then recover
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"status":"healthy"}`))
|
||||
case "/v1/health": // v2.x still answers this
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"status":"healthy"}`))
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
cfg := &config.GarageConfig{AdminEndpoint: srv.URL, AdminToken: "tok"}
|
||||
result, err := NewAdminService(cfg, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.APIVersion != "v2" {
|
||||
t.Fatalf("expected v2 after transient probe failure, got %s", result.APIVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// A server that answers /v1/health but returns a server error (not 404) for
|
||||
// /v2/GetClusterHealth must NOT be detected as v1, because v2.x servers also
|
||||
// answer /v1/health. Falling through to v1 here is the issue #78 misdetection.
|
||||
func TestDetectVersion_DoesNotDowngradeOnV2ServerError(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/v2/GetClusterHealth":
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
case "/v1/health":
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"status":"healthy"}`))
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
cfg := &config.GarageConfig{AdminEndpoint: srv.URL, AdminToken: "tok"}
|
||||
result, err := NewAdminService(cfg, "")
|
||||
if err == nil && result.APIVersion == "v1" {
|
||||
t.Fatal("must not downgrade to v1 when /v2 returns a server error; v2.x also serves /v1/health")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ name: garage-ui
|
||||
description: A Helm chart for Garage UI - Web interface for Garage S3 object storage
|
||||
icon: https://helm.noste.dev/garage.png
|
||||
type: application
|
||||
version: 0.6.1
|
||||
appVersion: v0.8.2
|
||||
version: 0.8.4 # x-release-please-version
|
||||
appVersion: v0.8.4 # x-release-please-version
|
||||
keywords:
|
||||
- garage
|
||||
- s3
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
A Helm chart for deploying [Garage UI](https://github.com/Noooste/garage-ui), a modern web interface for managing [Garage](https://garagehq.deuxfleurs.fr/) distributed object storage systems.
|
||||
|
||||
[](Chart.yaml)
|
||||
[](Chart.yaml)
|
||||
[](Chart.yaml) <!-- x-release-please-version -->
|
||||
[](Chart.yaml) <!-- x-release-please-version -->
|
||||
|
||||
## Table of Contents
|
||||
|
||||
|
||||
@@ -2,26 +2,16 @@
|
||||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
||||
"bump-minor-pre-major": true,
|
||||
"bump-patch-for-minor-pre-major": false,
|
||||
"separate-pull-requests": false,
|
||||
"packages": {
|
||||
".": {
|
||||
"release-type": "go",
|
||||
"component": "garage-ui",
|
||||
"include-component-in-tag": false,
|
||||
"changelog-path": "CHANGELOG.md",
|
||||
"exclude-paths": [
|
||||
"helm/garage-ui",
|
||||
".github",
|
||||
"docs",
|
||||
"scripts"
|
||||
"extra-files": [
|
||||
{ "type": "generic", "path": "helm/garage-ui/Chart.yaml" },
|
||||
"helm/garage-ui/README.md"
|
||||
]
|
||||
},
|
||||
"helm/garage-ui": {
|
||||
"release-type": "helm",
|
||||
"component": "garage-ui-chart",
|
||||
"package-name": "garage-ui",
|
||||
"include-component-in-tag": true,
|
||||
"changelog-path": "CHANGELOG.md"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user